diff --git a/configs/surface-5.12.config b/configs/surface-5.12.config new file mode 100644 index 000000000..cb2b3c9c4 --- /dev/null +++ b/configs/surface-5.12.config @@ -0,0 +1,60 @@ +# +# Surface Aggregator Module +# +CONFIG_SURFACE_AGGREGATOR=m +CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION=n +CONFIG_SURFACE_AGGREGATOR_BUS=y +CONFIG_SURFACE_AGGREGATOR_CDEV=m +CONFIG_SURFACE_AGGREGATOR_REGISTRY=m + +CONFIG_SURFACE_ACPI_NOTIFY=m +CONFIG_SURFACE_DTX=m +CONFIG_SURFACE_PLATFORM_PROFILE=m + +CONFIG_SURFACE_HID=m +CONFIG_SURFACE_KBD=m + +CONFIG_BATTERY_SURFACE=m +CONFIG_CHARGER_SURFACE=m + +# +# Surface Hotplug +# +CONFIG_SURFACE_HOTPLUG=m + +# +# IPTS touchscreen +# +# This only enables the user interface for IPTS data. +# For the touchscreen to work, you need to install iptsd. +# +CONFIG_MISC_IPTS=m + +# +# Cameras: IPU3 +# +CONFIG_VIDEO_IPU3_IMGU=m +CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_CIO2_BRIDGE=y +CONFIG_INTEL_SKL_INT3472=m + +# +# Cameras: Sensor drivers +# +CONFIG_VIDEO_OV5693=m +CONFIG_VIDEO_OV8865=m + +# +# ALS Sensor for Surface Book 3, Surface Laptop 3, Surface Pro 7 +# +CONFIG_APDS9960=m + +# +# Other Drivers +# +CONFIG_INPUT_SOC_BUTTON_ARRAY=m +CONFIG_SURFACE_3_BUTTON=m +CONFIG_SURFACE_3_POWER_OPREGION=m +CONFIG_SURFACE_PRO3_BUTTON=m +CONFIG_SURFACE_GPE=m +CONFIG_SURFACE_BOOK1_DGPU_SWITCH=m diff --git a/patches/5.12/0001-surface3-oemb.patch b/patches/5.12/0001-surface3-oemb.patch new file mode 100644 index 000000000..3b0a3a796 --- /dev/null +++ b/patches/5.12/0001-surface3-oemb.patch @@ -0,0 +1,101 @@ +From 3c43528f8c4fa28ea93781f553014a2c5b709396 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 fcd1d4fb94d5..ee26a5998b07 100644 +--- a/drivers/platform/surface/surface3-wmi.c ++++ b/drivers/platform/surface/surface3-wmi.c +@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + #endif + { } + }; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index 63a7e052eaa0..9806fd800020 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3694,6 +3694,15 @@ static const struct dmi_system_id dmi_platform_data[] = { + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, ++ { ++ .ident = "Microsoft Surface 3", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)&intel_braswell_platform_data, ++ }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat +diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +index 227424236fd5..1013a57be89a 100644 +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .callback = cht_surface_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + { } + }; + +-- +2.31.1 + diff --git a/patches/5.12/0002-mwifiex.patch b/patches/5.12/0002-mwifiex.patch new file mode 100644 index 000000000..e9e31d6ad --- /dev/null +++ b/patches/5.12/0002-mwifiex.patch @@ -0,0 +1,2520 @@ +From 839c1d7437de97b253482ab0526200330a87b49e Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Mon, 28 Sep 2020 17:46:49 +0900 +Subject: [PATCH] mwifiex: pcie: add DMI-based quirk impl for Surface devices + +This commit adds quirk implementation based on DMI matching with DMI +table for Surface devices. + +This implementation can be used for quirks later. + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/Makefile | 1 + + drivers/net/wireless/marvell/mwifiex/pcie.c | 4 + + drivers/net/wireless/marvell/mwifiex/pcie.h | 1 + + .../wireless/marvell/mwifiex/pcie_quirks.c | 114 ++++++++++++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.h | 11 ++ + 5 files changed, 131 insertions(+) + create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.c + create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.h + +diff --git a/drivers/net/wireless/marvell/mwifiex/Makefile b/drivers/net/wireless/marvell/mwifiex/Makefile +index 162d557b78af..2bd00f40958e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/Makefile ++++ b/drivers/net/wireless/marvell/mwifiex/Makefile +@@ -49,6 +49,7 @@ mwifiex_sdio-y += sdio.o + obj-$(CONFIG_MWIFIEX_SDIO) += mwifiex_sdio.o + + mwifiex_pcie-y += pcie.o ++mwifiex_pcie-y += pcie_quirks.o + obj-$(CONFIG_MWIFIEX_PCIE) += mwifiex_pcie.o + + mwifiex_usb-y += usb.o +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 94228b316df1..02fdce926de5 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -27,6 +27,7 @@ + #include "wmm.h" + #include "11n.h" + #include "pcie.h" ++#include "pcie_quirks.h" + + #define PCIE_VERSION "1.0" + #define DRV_NAME "Marvell mwifiex PCIe" +@@ -410,6 +411,9 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return ret; + } + ++ /* check quirks */ ++ mwifiex_initialize_quirks(card); ++ + if (mwifiex_add_card(card, &card->fw_done, &pcie_ops, + MWIFIEX_PCIE, &pdev->dev)) { + pr_err("%s failed\n", __func__); +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.h b/drivers/net/wireless/marvell/mwifiex/pcie.h +index 5ed613d65709..981e330c77d7 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.h +@@ -244,6 +244,7 @@ struct pcie_service_card { + unsigned long work_flags; + + bool pci_reset_ongoing; ++ unsigned long quirks; + }; + + static inline int +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +new file mode 100644 +index 000000000000..929aee2b0a60 +--- /dev/null ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -0,0 +1,114 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * File for PCIe quirks. ++ */ ++ ++/* The low-level PCI operations will be performed in this file. Therefore, ++ * let's use dev_*() instead of mwifiex_dbg() here to avoid troubles (e.g. ++ * to avoid using mwifiex_adapter struct before init or wifi is powered ++ * down, or causes NULL ptr deref). ++ */ ++ ++#include ++ ++#include "pcie_quirks.h" ++ ++/* quirk table based on DMI matching */ ++static const struct dmi_system_id mwifiex_quirk_table[] = { ++ { ++ .ident = "Surface Pro 4", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Pro 5", ++ .matches = { ++ /* match for SKU here due to generic product name "Surface Pro" */ ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Pro 5 (LTE)", ++ .matches = { ++ /* match for SKU here due to generic product name "Surface Pro" */ ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Pro 6", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Book 1", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Book 2", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Laptop 1", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Laptop 2", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface 3", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Pro 3", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 3"), ++ }, ++ .driver_data = 0, ++ }, ++ {} ++}; ++ ++void mwifiex_initialize_quirks(struct pcie_service_card *card) ++{ ++ struct pci_dev *pdev = card->dev; ++ const struct dmi_system_id *dmi_id; ++ ++ dmi_id = dmi_first_match(mwifiex_quirk_table); ++ if (dmi_id) ++ card->quirks = (uintptr_t)dmi_id->driver_data; ++ ++ if (!card->quirks) ++ dev_info(&pdev->dev, "no quirks enabled\n"); ++} +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +new file mode 100644 +index 000000000000..5326ae7e5671 +--- /dev/null ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -0,0 +1,11 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Header file for PCIe quirks. ++ */ ++ ++#include "pcie.h" ++ ++/* quirks */ ++// quirk flags can be added here ++ ++void mwifiex_initialize_quirks(struct pcie_service_card *card); +-- +2.31.1 + +From acdc19b03d9c4f21fe4a2dfa0cf3c1f8cf85f083 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Tue, 29 Sep 2020 17:25:22 +0900 +Subject: [PATCH] mwifiex: pcie: add reset_d3cold quirk for Surface gen4+ + devices + +To reset mwifiex on Surface gen4+ (Pro 4 or later gen) devices, it +seems that putting the wifi device into D3cold is required according +to errata.inf file on Windows installation (Windows/INF/errata.inf). + +This patch adds a function that performs power-cycle (put into D3cold +then D0) and call the function at the end of reset_prepare(). + +Note: Need to also reset the parent device (bridge) of wifi on SB1; +it might be because the bridge of wifi always reports it's in D3hot. +When I tried to reset only the wifi device (not touching parent), it gave +the following error and the reset failed: + + acpi device:4b: Cannot transition to power state D0 for parent in D3hot + mwifiex_pcie 0000:03:00.0: can't change power state from D3cold to D0 (config space inaccessible) + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 7 ++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 73 +++++++++++++++++-- + .../wireless/marvell/mwifiex/pcie_quirks.h | 3 +- + 3 files changed, 74 insertions(+), 9 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 02fdce926de5..d9acfea395ad 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -528,6 +528,13 @@ static void mwifiex_pcie_reset_prepare(struct pci_dev *pdev) + mwifiex_shutdown_sw(adapter); + clear_bit(MWIFIEX_IFACE_WORK_DEVICE_DUMP, &card->work_flags); + clear_bit(MWIFIEX_IFACE_WORK_CARD_RESET, &card->work_flags); ++ ++ /* For Surface gen4+ devices, we need to put wifi into D3cold right ++ * before performing FLR ++ */ ++ if (card->quirks & QUIRK_FW_RST_D3COLD) ++ mwifiex_pcie_reset_d3cold_quirk(pdev); ++ + mwifiex_dbg(adapter, INFO, "%s, successful\n", __func__); + + card->pci_reset_ongoing = true; +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index 929aee2b0a60..edc739c542fe 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -21,7 +21,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Pro 5", +@@ -30,7 +30,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -39,7 +39,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Pro 6", +@@ -47,7 +47,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Book 1", +@@ -55,7 +55,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Book 2", +@@ -63,7 +63,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Laptop 1", +@@ -71,7 +71,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Laptop 2", +@@ -79,7 +79,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface 3", +@@ -111,4 +111,61 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + + if (!card->quirks) + dev_info(&pdev->dev, "no quirks enabled\n"); ++ if (card->quirks & QUIRK_FW_RST_D3COLD) ++ dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++} ++ ++static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) ++{ ++ dev_info(&pdev->dev, "putting into D3cold...\n"); ++ ++ pci_save_state(pdev); ++ if (pci_is_enabled(pdev)) ++ pci_disable_device(pdev); ++ pci_set_power_state(pdev, PCI_D3cold); ++} ++ ++static int mwifiex_pcie_set_power_d0(struct pci_dev *pdev) ++{ ++ int ret; ++ ++ dev_info(&pdev->dev, "putting into D0...\n"); ++ ++ pci_set_power_state(pdev, PCI_D0); ++ ret = pci_enable_device(pdev); ++ if (ret) { ++ dev_err(&pdev->dev, "pci_enable_device failed\n"); ++ return ret; ++ } ++ pci_restore_state(pdev); ++ ++ return 0; ++} ++ ++int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) ++{ ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); ++ int ret; ++ ++ /* Power-cycle (put into D3cold then D0) */ ++ dev_info(&pdev->dev, "Using reset_d3cold quirk to perform FW reset\n"); ++ ++ /* We need to perform power-cycle also for bridge of wifi because ++ * on some devices (e.g. Surface Book 1), the OS for some reasons ++ * can't know the real power state of the bridge. ++ * When tried to power-cycle only wifi, the reset failed with the ++ * following dmesg log: ++ * "Cannot transition to power state D0 for parent in D3hot". ++ */ ++ mwifiex_pcie_set_power_d3cold(pdev); ++ mwifiex_pcie_set_power_d3cold(parent_pdev); ++ ++ ret = mwifiex_pcie_set_power_d0(parent_pdev); ++ if (ret) ++ return ret; ++ ret = mwifiex_pcie_set_power_d0(pdev); ++ if (ret) ++ return ret; ++ ++ return 0; + } +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 5326ae7e5671..8b9dcb5070d8 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -6,6 +6,7 @@ + #include "pcie.h" + + /* quirks */ +-// quirk flags can be added here ++#define QUIRK_FW_RST_D3COLD BIT(0) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); ++int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.31.1 + +From 609432b3d8ffc0875aa1662633324ae5d202e41f Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +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 | 77 ++++++++++++++++++- + .../wireless/marvell/mwifiex/pcie_quirks.h | 5 ++ + 3 files changed, 91 insertions(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index d9acfea395ad..6e049236ae1a 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -2969,6 +2969,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; + ++ /* On Surface 3, reset_wsid method removes then re-probes card by ++ * itself. So, need to place it here and skip performing any other ++ * reset-related works. ++ */ ++ if (card->quirks & QUIRK_FW_RST_WSID_S3) { ++ mwifiex_pcie_reset_wsid_quirk(card->dev); ++ /* skip performing any other reset-related works */ ++ return; ++ } ++ + /* We can't afford to wait here; remove() might be waiting on us. If we + * can't grab the device lock, maybe we'll get another chance later. + */ +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index edc739c542fe..f0a6fa0a7ae5 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -9,10 +9,21 @@ + * down, or causes NULL ptr deref). + */ + ++#include + #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,7 +98,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, + }, + { + .ident = "Surface Pro 3", +@@ -113,6 +124,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "no quirks enabled\n"); + if (card->quirks & QUIRK_FW_RST_D3COLD) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++ if (card->quirks & QUIRK_FW_RST_WSID_S3) ++ dev_info(&pdev->dev, ++ "quirk reset_wsid for Surface 3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +@@ -169,3 +183,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) + + return 0; + } ++ ++int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) ++{ ++ acpi_handle handle; ++ union acpi_object *obj; ++ acpi_status status; ++ ++ dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); ++ ++ status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); ++ if (ACPI_FAILURE(status)) { ++ dev_err(&pdev->dev, "No ACPI handle for path %s\n", ++ ACPI_WSID_PATH); ++ return -ENODEV; ++ } ++ ++ if (!acpi_has_method(handle, "_DSM")) { ++ dev_err(&pdev->dev, "_DSM method not found\n"); ++ return -ENODEV; ++ } ++ ++ if (!acpi_check_dsm(handle, &wsid_dsm_guid, ++ WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { ++ dev_err(&pdev->dev, ++ "_DSM method doesn't support wifi power off func\n"); ++ return -ENODEV; ++ } ++ ++ if (!acpi_check_dsm(handle, &wsid_dsm_guid, ++ WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { ++ dev_err(&pdev->dev, ++ "_DSM method doesn't support wifi power on func\n"); ++ return -ENODEV; ++ } ++ ++ /* card will be removed immediately after this call on Surface 3 */ ++ dev_info(&pdev->dev, "turning wifi off...\n"); ++ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, ++ WSID_REV, WSID_FUNC_WIFI_PWR_OFF, ++ NULL); ++ if (!obj) { ++ dev_err(&pdev->dev, ++ "device _DSM execution failed for turning wifi off\n"); ++ return -EIO; ++ } ++ ACPI_FREE(obj); ++ ++ /* card will be re-probed immediately after this call on Surface 3 */ ++ dev_info(&pdev->dev, "turning wifi on...\n"); ++ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, ++ WSID_REV, WSID_FUNC_WIFI_PWR_ON, ++ NULL); ++ if (!obj) { ++ dev_err(&pdev->dev, ++ "device _DSM execution failed for turning wifi on\n"); ++ return -EIO; ++ } ++ ACPI_FREE(obj); ++ ++ return 0; ++} +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 8b9dcb5070d8..3ef7440418e3 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -7,6 +7,11 @@ + + /* quirks */ + #define QUIRK_FW_RST_D3COLD BIT(0) ++/* Surface 3 and Surface Pro 3 have the same _DSM method but need to ++ * be handled differently. Currently, only S3 is supported. ++ */ ++#define QUIRK_FW_RST_WSID_S3 BIT(1) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); ++int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); +-- +2.31.1 + +From 24a1ae55645ca4d7d2bde47d1943b4535e656566 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +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 f0a6fa0a7ae5..34dcd84f02a6 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -100,6 +100,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + }, + .driver_data = (void *)QUIRK_FW_RST_WSID_S3, + }, ++ { ++ .ident = "Surface 3", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, ++ }, + { + .ident = "Surface Pro 3", + .matches = { +-- +2.31.1 + +From 0bfa4441da97c18a79e9466e4f796b5ff5f084d9 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +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 6e049236ae1a..d027c875d7a0 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -379,6 +379,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) + { + struct pcie_service_card *card; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + int ret; + + pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", +@@ -420,6 +421,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; + } + ++ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing ++ * after suspend ++ */ ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ parent_pdev->bridge_d3 = false; ++ + return 0; + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index 34dcd84f02a6..a2aeb2af907e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -32,7 +32,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5", +@@ -41,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -50,7 +52,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 6", +@@ -58,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 1", +@@ -66,7 +70,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 2", +@@ -74,7 +79,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 1", +@@ -82,7 +88,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 2", +@@ -90,7 +97,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface 3", +@@ -136,6 +144,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + if (card->quirks & QUIRK_FW_RST_WSID_S3) + dev_info(&pdev->dev, + "quirk reset_wsid for Surface 3 enabled\n"); ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ dev_info(&pdev->dev, ++ "quirk no_brigde_d3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 3ef7440418e3..a95ebac06e13 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -11,6 +11,7 @@ + * be handled differently. Currently, only S3 is supported. + */ + #define QUIRK_FW_RST_WSID_S3 BIT(1) ++#define QUIRK_NO_BRIDGE_D3 BIT(2) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.31.1 + +From b0c566722c6b7ce45b68f67c6b94554fbc3f12fb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 3 Nov 2020 13:28:04 +0100 +Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface + devices + +The most recent firmware of the 88W8897 card reports a hardcoded LTR +value to the system during initialization, probably as an (unsuccessful) +attempt of the developers to fix firmware crashes. This LTR value +prevents most of the Microsoft Surface devices from entering deep +powersaving states (either platform C-State 10 or S0ix state), because +the exit latency of that state would be higher than what the card can +tolerate. + +Turns out the card works just the same (including the firmware crashes) +no matter if that hardcoded LTR value is reported or not, so it's kind +of useless and only prevents us from saving power. + +To get rid of those hardcoded LTR reports, it's possible to reset the +PCI bridge device after initializing the cards firmware. I'm not exactly +sure why that works, maybe the power management subsystem of the PCH +resets its stored LTR values when doing a function level reset of the +bridge device. Doing the reset once after starting the wifi firmware +works very well, probably because the firmware only reports that LTR +value a single time during firmware startup. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index d027c875d7a0..8a99e243aff2 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1757,9 +1757,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) + static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; ++ struct pci_dev *pdev = card->dev; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + ++ /* Trigger a function level reset of the PCI bridge device, this makes ++ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value ++ * that prevents the system from entering package C10 and S0ix powersaving ++ * states. ++ * We need to do it here because it must happen after firmware ++ * initialization and this function is called after that is done. ++ */ ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ pci_reset_function(parent_pdev); ++ + /* Write the RX ring read pointer in to reg->rx_rdptr */ + if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | + tx_wrap)) { +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index a2aeb2af907e..6885575826a6 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -33,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5", +@@ -43,7 +44,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -53,7 +55,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 6", +@@ -62,7 +65,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 1", +@@ -71,7 +75,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 2", +@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 1", +@@ -89,7 +95,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 2", +@@ -98,7 +105,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface 3", +@@ -147,6 +155,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + if (card->quirks & QUIRK_NO_BRIDGE_D3) + dev_info(&pdev->dev, + "quirk no_brigde_d3 enabled\n"); ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index a95ebac06e13..4ec2ae72f632 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -12,6 +12,7 @@ + */ + #define QUIRK_FW_RST_WSID_S3 BIT(1) + #define QUIRK_NO_BRIDGE_D3 BIT(2) ++#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.31.1 + +From 36605f7215e9625de1b5f23253fc60af3ae93c04 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 25 Mar 2021 11:33:02 +0100 +Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell + 88W8897 + +The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) +is used in a lot of Microsoft Surface devices, and all those devices +suffer from very low 2.4GHz wifi connection speeds while bluetooth is +enabled. The reason for that is that the default passive scanning +interval for Bluetooth Low Energy devices is quite high in Linux +(interval of 60 msec and scan window of 30 msec, see hci_core.c), and +the Marvell chip is known for its bad bt+wifi coexisting performance. + +So decrease that passive scan interval and make the scan window shorter +on this particular device to allow for spending more time transmitting +wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and +the new scan window is 6.25 msec (0xa * 0,625 msec). + +This change has a very large impact on the 2.4GHz wifi speeds and gets +it up to performance comparable with the Windows driver, which seems to +apply a similar quirk. + +The interval and window length were tested and found to work very well +with a lot of Bluetooth Low Energy devices, including the Surface Pen, a +Bluetooth Speaker and two modern Bluetooth headphones. All devices were +discovered immediately after turning them on. Even lower values were +also tested, but they introduced longer delays until devices get +discovered. + +Patchset: mwifiex +--- + drivers/bluetooth/btusb.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +index 5cbfbd948f67..824512fafa23 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -61,6 +61,7 @@ static struct usb_driver btusb_driver; + #define BTUSB_VALID_LE_STATES 0x800000 + #define BTUSB_QCA_WCN6855 0x1000000 + #define BTUSB_INTEL_NEWGEN 0x2000000 ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(26) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -357,6 +358,7 @@ static const struct usb_device_id blacklist_table[] = { + { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, ++ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, + + /* Intel Bluetooth devices */ + { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_NEW | +@@ -4713,6 +4715,19 @@ static int btusb_probe(struct usb_interface *intf, + if (id->driver_info & BTUSB_MARVELL) + hdev->set_bdaddr = btusb_set_bdaddr_marvell; + ++ /* The Marvell 88W8897 combined wifi and bluetooth card is known for ++ * very bad bt+wifi coexisting performance. ++ * ++ * Decrease the passive BT Low Energy scan interval a bit ++ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter ++ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly ++ * higher wifi throughput while passively scanning for BT LE devices. ++ */ ++ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { ++ hdev->le_scan_interval = 0x0190; ++ hdev->le_scan_window = 0x000a; ++ } ++ + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && + (id->driver_info & BTUSB_MEDIATEK)) { + hdev->setup = btusb_mtk_setup; +-- +2.31.1 + +From 85492c085d47cdd5499f0f2123577c8fc23dad4e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 11 Nov 2020 12:31:26 +0100 +Subject: [PATCH] mwifiex: Small cleanup for handling virtual interface type + changes + +Handle the obvious invalid virtual interface type changes with a general +check instead of looking at the individual change. + +For type changes from P2P_CLIENT to P2P_GO and the other way round, this +changes the behavior slightly: We now still do nothing, but return +-EOPNOTSUPP instead of 0. Now that behavior was incorrect before and +still is, because type changes between these two types are actually +possible and supported, which we'll fix in a following commit. + +Patchset: mwifiex +--- + .../net/wireless/marvell/mwifiex/cfg80211.c | 39 +++++++------------ + 1 file changed, 14 insertions(+), 25 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index a2ed268ce0da..789de1b0c5b1 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -1141,6 +1141,20 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return -EBUSY; + } + ++ if (type == NL80211_IFTYPE_UNSPECIFIED) { ++ mwifiex_dbg(priv->adapter, INFO, ++ "%s: no new type specified, keeping old type %d\n", ++ dev->name, curr_iftype); ++ return 0; ++ } ++ ++ if (curr_iftype == type) { ++ mwifiex_dbg(priv->adapter, INFO, ++ "%s: interface already is of type %d\n", ++ dev->name, curr_iftype); ++ return 0; ++ } ++ + switch (curr_iftype) { + case NL80211_IFTYPE_ADHOC: + switch (type) { +@@ -1160,12 +1174,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + case NL80211_IFTYPE_AP: + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); +- case NL80211_IFTYPE_UNSPECIFIED: +- mwifiex_dbg(priv->adapter, INFO, +- "%s: kept type as IBSS\n", dev->name); +- fallthrough; +- case NL80211_IFTYPE_ADHOC: /* This shouldn't happen */ +- return 0; + default: + mwifiex_dbg(priv->adapter, ERROR, + "%s: changing to %d not supported\n", +@@ -1191,12 +1199,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + case NL80211_IFTYPE_AP: + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); +- case NL80211_IFTYPE_UNSPECIFIED: +- mwifiex_dbg(priv->adapter, INFO, +- "%s: kept type as STA\n", dev->name); +- fallthrough; +- case NL80211_IFTYPE_STATION: /* This shouldn't happen */ +- return 0; + default: + mwifiex_dbg(priv->adapter, ERROR, + "%s: changing to %d not supported\n", +@@ -1214,12 +1216,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + case NL80211_IFTYPE_P2P_GO: + return mwifiex_change_vif_to_p2p(dev, curr_iftype, + type, params); +- case NL80211_IFTYPE_UNSPECIFIED: +- mwifiex_dbg(priv->adapter, INFO, +- "%s: kept type as AP\n", dev->name); +- fallthrough; +- case NL80211_IFTYPE_AP: /* This shouldn't happen */ +- return 0; + default: + mwifiex_dbg(priv->adapter, ERROR, + "%s: changing to %d not supported\n", +@@ -1254,13 +1250,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return -EFAULT; + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); +- case NL80211_IFTYPE_UNSPECIFIED: +- mwifiex_dbg(priv->adapter, INFO, +- "%s: kept type as P2P\n", dev->name); +- fallthrough; +- case NL80211_IFTYPE_P2P_CLIENT: +- case NL80211_IFTYPE_P2P_GO: +- return 0; + default: + mwifiex_dbg(priv->adapter, ERROR, + "%s: changing to %d not supported\n", +-- +2.31.1 + +From f1fea75c641564a6c18d4d9a953879fd027c0e04 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 10 Nov 2020 12:49:56 +0100 +Subject: [PATCH] mwifiex: Use non-posted PCI register writes + +On the 88W8897 card it's very important the TX ring write pointer is +updated correctly to its new value before setting the TX ready +interrupt, otherwise the firmware appears to crash (probably because +it's trying to DMA-read from the wrong place). + +Since PCI uses "posted writes" when writing to a register, it's not +guaranteed that a write will happen immediately. That means the pointer +might be outdated when setting the TX ready interrupt, leading to +firmware crashes especially when ASPM L1 and L1 substates are enabled +(because of the higher link latency, the write will probably take +longer). + +So fix those firmware crashes by always forcing non-posted writes. We do +that by simply reading back the register after writing it, just as a lot +of other drivers do. + +There are two reproducers that are fixed with this patch: + +1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled +substates are platform dependent), the firmware crashes and eventually a +command timeout appears in the logs. That crash is fixed by using a +non-posted write in mwifiex_pcie_send_data(). + +2) When sending lots of commands to the card, waking it up from sleep in +very quick intervals, the firmware eventually crashes. That crash +appears to be fixed by some other non-posted write included here. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 8a99e243aff2..84b1d30e07e4 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -237,6 +237,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) + + iowrite32(data, card->pci_mmap1 + reg); + ++ /* Do a read-back, which makes the write non-posted, ensuring the ++ * completion before returning. ++ * The firmware of the 88W8897 card is buggy and this avoids crashes. ++ */ ++ ioread32(card->pci_mmap1 + reg); ++ + return 0; + } + +-- +2.31.1 + +From 3933d77762a306930dfb0ba1ff6242c1395f7690 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 11 Nov 2020 12:44:39 +0100 +Subject: [PATCH] mwifiex: Use function to check whether interface type change + is allowed + +Instead of bailing out in the function which is supposed to do the type +change, detect invalid changes beforehand using a generic function and +return an error if the change is not allowed. + +Patchset: mwifiex +--- + .../net/wireless/marvell/mwifiex/cfg80211.c | 139 ++++++++++++------ + 1 file changed, 92 insertions(+), 47 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 789de1b0c5b1..13698818e58a 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -939,6 +939,76 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, + return 0; + } + ++static bool ++is_vif_type_change_allowed(struct mwifiex_adapter *adapter, ++ enum nl80211_iftype old_iftype, ++ enum nl80211_iftype new_iftype) ++{ ++ switch (old_iftype) { ++ case NL80211_IFTYPE_ADHOC: ++ switch (new_iftype) { ++ case NL80211_IFTYPE_STATION: ++ return true; ++ case NL80211_IFTYPE_P2P_CLIENT: ++ case NL80211_IFTYPE_P2P_GO: ++ return adapter->curr_iface_comb.p2p_intf != ++ adapter->iface_limit.p2p_intf; ++ case NL80211_IFTYPE_AP: ++ return adapter->curr_iface_comb.uap_intf != ++ adapter->iface_limit.uap_intf; ++ default: ++ return false; ++ } ++ ++ case NL80211_IFTYPE_STATION: ++ switch (new_iftype) { ++ case NL80211_IFTYPE_ADHOC: ++ return true; ++ case NL80211_IFTYPE_P2P_CLIENT: ++ case NL80211_IFTYPE_P2P_GO: ++ return adapter->curr_iface_comb.p2p_intf != ++ adapter->iface_limit.p2p_intf; ++ case NL80211_IFTYPE_AP: ++ return adapter->curr_iface_comb.uap_intf != ++ adapter->iface_limit.uap_intf; ++ default: ++ return false; ++ } ++ ++ case NL80211_IFTYPE_AP: ++ switch (new_iftype) { ++ case NL80211_IFTYPE_ADHOC: ++ case NL80211_IFTYPE_STATION: ++ return adapter->curr_iface_comb.sta_intf != ++ adapter->iface_limit.sta_intf; ++ case NL80211_IFTYPE_P2P_CLIENT: ++ case NL80211_IFTYPE_P2P_GO: ++ return adapter->curr_iface_comb.p2p_intf != ++ adapter->iface_limit.p2p_intf; ++ default: ++ return false; ++ } ++ ++ case NL80211_IFTYPE_P2P_CLIENT: ++ case NL80211_IFTYPE_P2P_GO: ++ switch (new_iftype) { ++ case NL80211_IFTYPE_ADHOC: ++ case NL80211_IFTYPE_STATION: ++ return true; ++ case NL80211_IFTYPE_AP: ++ return adapter->curr_iface_comb.uap_intf != ++ adapter->iface_limit.uap_intf; ++ default: ++ return false; ++ } ++ ++ default: ++ break; ++ } ++ ++ return false; ++} ++ + static int + mwifiex_change_vif_to_p2p(struct net_device *dev, + enum nl80211_iftype curr_iftype, +@@ -955,13 +1025,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, + + adapter = priv->adapter; + +- if (adapter->curr_iface_comb.p2p_intf == +- adapter->iface_limit.p2p_intf) { +- mwifiex_dbg(adapter, ERROR, +- "cannot create multiple P2P ifaces\n"); +- return -1; +- } +- + mwifiex_dbg(adapter, INFO, + "%s: changing role to p2p\n", dev->name); + +@@ -1027,15 +1090,6 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, + + adapter = priv->adapter; + +- if ((curr_iftype != NL80211_IFTYPE_P2P_CLIENT && +- curr_iftype != NL80211_IFTYPE_P2P_GO) && +- (adapter->curr_iface_comb.sta_intf == +- adapter->iface_limit.sta_intf)) { +- mwifiex_dbg(adapter, ERROR, +- "cannot create multiple station/adhoc ifaces\n"); +- return -1; +- } +- + if (type == NL80211_IFTYPE_STATION) + mwifiex_dbg(adapter, INFO, + "%s: changing role to station\n", dev->name); +@@ -1086,13 +1140,6 @@ mwifiex_change_vif_to_ap(struct net_device *dev, + + adapter = priv->adapter; + +- if (adapter->curr_iface_comb.uap_intf == +- adapter->iface_limit.uap_intf) { +- mwifiex_dbg(adapter, ERROR, +- "cannot create multiple AP ifaces\n"); +- return -1; +- } +- + mwifiex_dbg(adapter, INFO, + "%s: changing role to AP\n", dev->name); + +@@ -1155,6 +1202,13 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return 0; + } + ++ if (!is_vif_type_change_allowed(priv->adapter, curr_iftype, type)) { ++ mwifiex_dbg(priv->adapter, ERROR, ++ "%s: change from type %d to %d is not allowed\n", ++ dev->name, curr_iftype, type); ++ return -EOPNOTSUPP; ++ } ++ + switch (curr_iftype) { + case NL80211_IFTYPE_ADHOC: + switch (type) { +@@ -1175,12 +1229,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); + default: +- mwifiex_dbg(priv->adapter, ERROR, +- "%s: changing to %d not supported\n", +- dev->name, type); +- return -EOPNOTSUPP; ++ goto errnotsupp; + } +- break; ++ + case NL80211_IFTYPE_STATION: + switch (type) { + case NL80211_IFTYPE_ADHOC: +@@ -1200,12 +1251,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); + default: +- mwifiex_dbg(priv->adapter, ERROR, +- "%s: changing to %d not supported\n", +- dev->name, type); +- return -EOPNOTSUPP; ++ goto errnotsupp; + } +- break; ++ + case NL80211_IFTYPE_AP: + switch (type) { + case NL80211_IFTYPE_ADHOC: +@@ -1217,12 +1265,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return mwifiex_change_vif_to_p2p(dev, curr_iftype, + type, params); + default: +- mwifiex_dbg(priv->adapter, ERROR, +- "%s: changing to %d not supported\n", +- dev->name, type); +- return -EOPNOTSUPP; ++ goto errnotsupp; + } +- break; ++ + case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_GO: + switch (type) { +@@ -1251,21 +1296,21 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); + default: +- mwifiex_dbg(priv->adapter, ERROR, +- "%s: changing to %d not supported\n", +- dev->name, type); +- return -EOPNOTSUPP; ++ goto errnotsupp; + } +- break; ++ + default: +- mwifiex_dbg(priv->adapter, ERROR, +- "%s: unknown iftype: %d\n", +- dev->name, dev->ieee80211_ptr->iftype); +- return -EOPNOTSUPP; ++ goto errnotsupp; + } + + + return 0; ++ ++errnotsupp: ++ mwifiex_dbg(priv->adapter, ERROR, ++ "unsupported interface type transition: %d to %d\n", ++ curr_iftype, type); ++ return -EOPNOTSUPP; + } + + static void +-- +2.31.1 + +From 29e7e389e5242dcfa8a622ea3ca01a44824990fc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 11 Nov 2020 13:33:04 +0100 +Subject: [PATCH] mwifiex: Run SET_BSS_MODE when changing from P2P to STATION + vif-type + +We currently handle changing from the P2P to the STATION virtual +interface type slightly different than changing from P2P to ADHOC: When +changing to STATION, we don't send the SET_BSS_MODE command. We do send +that command on all other type-changes though, and it probably makes +sense to send the command since after all we just changed our BSS_MODE. +Looking at prior changes to this part of the code, it seems that this is +simply a leftover from old refactorings. + +Since sending the SET_BSS_MODE command is the only difference between +mwifiex_change_vif_to_sta_adhoc() and the current code, we can now use +mwifiex_change_vif_to_sta_adhoc() for both switching to ADHOC and +STATION interface type. + +This does not fix any particular bug and just "looked right", so there's +a small chance it might be a regression. + +Patchset: mwifiex +--- + .../net/wireless/marvell/mwifiex/cfg80211.c | 22 ++++--------------- + 1 file changed, 4 insertions(+), 18 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 13698818e58a..f5b9f1d26114 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -1270,29 +1270,15 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + + case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_GO: ++ if (mwifiex_cfg80211_deinit_p2p(priv)) ++ return -EFAULT; ++ + switch (type) { +- case NL80211_IFTYPE_STATION: +- if (mwifiex_cfg80211_deinit_p2p(priv)) +- return -EFAULT; +- priv->adapter->curr_iface_comb.p2p_intf--; +- priv->adapter->curr_iface_comb.sta_intf++; +- dev->ieee80211_ptr->iftype = type; +- if (mwifiex_deinit_priv_params(priv)) +- return -1; +- if (mwifiex_init_new_priv_params(priv, dev, type)) +- return -1; +- if (mwifiex_sta_init_cmd(priv, false, false)) +- return -1; +- break; + case NL80211_IFTYPE_ADHOC: +- if (mwifiex_cfg80211_deinit_p2p(priv)) +- return -EFAULT; ++ case NL80211_IFTYPE_STATION: + return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, + type, params); +- break; + case NL80211_IFTYPE_AP: +- if (mwifiex_cfg80211_deinit_p2p(priv)) +- return -EFAULT; + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); + default: +-- +2.31.1 + +From 278002e59e1bfad3f9f849fb2402547d0ff445db Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 11 Nov 2020 14:42:54 +0100 +Subject: [PATCH] mwifiex: Use helper function for counting interface types + +Use a small helper function to increment and decrement the counter of +the interface types we currently manage. This makes the code that +actually changes and sets up the interface type a bit less messy and +also helps avoiding mistakes in case someone increments/decrements a +counter wrongly. + +Patchset: mwifiex +--- + .../net/wireless/marvell/mwifiex/cfg80211.c | 110 ++++++------------ + 1 file changed, 35 insertions(+), 75 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index f5b9f1d26114..44cff715bf29 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -1009,6 +1009,32 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, + return false; + } + ++static void ++update_vif_type_counter(struct mwifiex_adapter *adapter, ++ enum nl80211_iftype iftype, ++ int change) ++{ ++ switch (iftype) { ++ case NL80211_IFTYPE_UNSPECIFIED: ++ case NL80211_IFTYPE_ADHOC: ++ case NL80211_IFTYPE_STATION: ++ adapter->curr_iface_comb.sta_intf += change; ++ break; ++ case NL80211_IFTYPE_AP: ++ adapter->curr_iface_comb.uap_intf += change; ++ break; ++ case NL80211_IFTYPE_P2P_CLIENT: ++ case NL80211_IFTYPE_P2P_GO: ++ adapter->curr_iface_comb.p2p_intf += change; ++ break; ++ default: ++ mwifiex_dbg(adapter, ERROR, ++ "%s: Unsupported iftype passed: %d\n", ++ __func__, iftype); ++ break; ++ } ++} ++ + static int + mwifiex_change_vif_to_p2p(struct net_device *dev, + enum nl80211_iftype curr_iftype, +@@ -1056,19 +1082,8 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, + if (mwifiex_sta_init_cmd(priv, false, false)) + return -1; + +- switch (curr_iftype) { +- case NL80211_IFTYPE_STATION: +- case NL80211_IFTYPE_ADHOC: +- adapter->curr_iface_comb.sta_intf--; +- break; +- case NL80211_IFTYPE_AP: +- adapter->curr_iface_comb.uap_intf--; +- break; +- default: +- break; +- } +- +- adapter->curr_iface_comb.p2p_intf++; ++ update_vif_type_counter(adapter, curr_iftype, -1); ++ update_vif_type_counter(adapter, type, +1); + dev->ieee80211_ptr->iftype = type; + + return 0; +@@ -1107,20 +1122,10 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, + if (mwifiex_sta_init_cmd(priv, false, false)) + return -1; + +- switch (curr_iftype) { +- case NL80211_IFTYPE_P2P_CLIENT: +- case NL80211_IFTYPE_P2P_GO: +- adapter->curr_iface_comb.p2p_intf--; +- break; +- case NL80211_IFTYPE_AP: +- adapter->curr_iface_comb.uap_intf--; +- break; +- default: +- break; +- } +- +- adapter->curr_iface_comb.sta_intf++; ++ update_vif_type_counter(adapter, curr_iftype, -1); ++ update_vif_type_counter(adapter, type, +1); + dev->ieee80211_ptr->iftype = type; ++ + return 0; + } + +@@ -1153,20 +1158,8 @@ mwifiex_change_vif_to_ap(struct net_device *dev, + if (mwifiex_sta_init_cmd(priv, false, false)) + return -1; + +- switch (curr_iftype) { +- case NL80211_IFTYPE_P2P_CLIENT: +- case NL80211_IFTYPE_P2P_GO: +- adapter->curr_iface_comb.p2p_intf--; +- break; +- case NL80211_IFTYPE_STATION: +- case NL80211_IFTYPE_ADHOC: +- adapter->curr_iface_comb.sta_intf--; +- break; +- default: +- break; +- } +- +- adapter->curr_iface_comb.uap_intf++; ++ update_vif_type_counter(adapter, curr_iftype, -1); ++ update_vif_type_counter(adapter, type, +1); + dev->ieee80211_ptr->iftype = type; + return 0; + } +@@ -3131,23 +3124,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, + mwifiex_dev_debugfs_init(priv); + #endif + +- switch (type) { +- case NL80211_IFTYPE_UNSPECIFIED: +- case NL80211_IFTYPE_STATION: +- case NL80211_IFTYPE_ADHOC: +- adapter->curr_iface_comb.sta_intf++; +- break; +- case NL80211_IFTYPE_AP: +- adapter->curr_iface_comb.uap_intf++; +- break; +- case NL80211_IFTYPE_P2P_CLIENT: +- adapter->curr_iface_comb.p2p_intf++; +- break; +- default: +- /* This should be dead code; checked above */ +- mwifiex_dbg(adapter, ERROR, "type not supported\n"); +- return ERR_PTR(-EINVAL); +- } ++ update_vif_type_counter(adapter, type, +1); + + return &priv->wdev; + +@@ -3213,24 +3190,7 @@ int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) + /* Clear the priv in adapter */ + priv->netdev = NULL; + +- switch (priv->bss_mode) { +- case NL80211_IFTYPE_UNSPECIFIED: +- case NL80211_IFTYPE_STATION: +- case NL80211_IFTYPE_ADHOC: +- adapter->curr_iface_comb.sta_intf--; +- break; +- case NL80211_IFTYPE_AP: +- adapter->curr_iface_comb.uap_intf--; +- break; +- case NL80211_IFTYPE_P2P_CLIENT: +- case NL80211_IFTYPE_P2P_GO: +- adapter->curr_iface_comb.p2p_intf--; +- break; +- default: +- mwifiex_dbg(adapter, ERROR, +- "del_virtual_intf: type not supported\n"); +- break; +- } ++ update_vif_type_counter(adapter, priv->bss_mode, -1); + + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + +-- +2.31.1 + +From 86ce2f912a9ee6b579e33dee1a13279511bcf34a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Fri, 26 Mar 2021 15:56:58 +0100 +Subject: [PATCH] mwifiex: Update virtual interface counters right after + setting bss_type + +In mwifiex_init_new_priv_params() we update our private driver state to +reflect the currently selected virtual interface type. Most notably we +set the bss_mode to the mode we're going to put the firmware in. + +Now after we updated the driver state we actually start talking to the +firmware and instruct it to set up the new mode. Those commands can and +will sometimes fail, in which case we return with an error from +mwifiex_change_vif_to_*. We currently update our virtual interface type +counters after this return, which means the code is never reached when a +firmware error happens and we never update the counters. Since we have +updated our bss_mode earlier though, the counters now no longer reflect +the actual state of the driver. + +This will break things on the next virtual interface change, because the +virtual interface type we're switching away from didn't get its counter +incremented, and we end up decrementing a 0-counter. + +To fix this, simply update the virtual interface type counters right +after updating our driver structures, so that they are always in sync. + +Patchset: mwifiex +--- + .../net/wireless/marvell/mwifiex/cfg80211.c | 25 +++++++++++-------- + 1 file changed, 14 insertions(+), 11 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 44cff715bf29..e637129a411f 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -1059,6 +1059,10 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, + if (mwifiex_init_new_priv_params(priv, dev, type)) + return -1; + ++ update_vif_type_counter(adapter, curr_iftype, -1); ++ update_vif_type_counter(adapter, type, +1); ++ dev->ieee80211_ptr->iftype = type; ++ + switch (type) { + case NL80211_IFTYPE_P2P_CLIENT: + if (mwifiex_cfg80211_init_p2p_client(priv)) +@@ -1082,10 +1086,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, + if (mwifiex_sta_init_cmd(priv, false, false)) + return -1; + +- update_vif_type_counter(adapter, curr_iftype, -1); +- update_vif_type_counter(adapter, type, +1); +- dev->ieee80211_ptr->iftype = type; +- + return 0; + } + +@@ -1116,16 +1116,17 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, + return -1; + if (mwifiex_init_new_priv_params(priv, dev, type)) + return -1; ++ ++ update_vif_type_counter(adapter, curr_iftype, -1); ++ update_vif_type_counter(adapter, type, +1); ++ dev->ieee80211_ptr->iftype = type; ++ + if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, + HostCmd_ACT_GEN_SET, 0, NULL, true)) + return -1; + if (mwifiex_sta_init_cmd(priv, false, false)) + return -1; + +- update_vif_type_counter(adapter, curr_iftype, -1); +- update_vif_type_counter(adapter, type, +1); +- dev->ieee80211_ptr->iftype = type; +- + return 0; + } + +@@ -1152,15 +1153,17 @@ mwifiex_change_vif_to_ap(struct net_device *dev, + return -1; + if (mwifiex_init_new_priv_params(priv, dev, type)) + return -1; ++ ++ update_vif_type_counter(adapter, curr_iftype, -1); ++ update_vif_type_counter(adapter, type, +1); ++ dev->ieee80211_ptr->iftype = type; ++ + if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, + HostCmd_ACT_GEN_SET, 0, NULL, true)) + return -1; + if (mwifiex_sta_init_cmd(priv, false, false)) + return -1; + +- update_vif_type_counter(adapter, curr_iftype, -1); +- update_vif_type_counter(adapter, type, +1); +- dev->ieee80211_ptr->iftype = type; + return 0; + } + /* +-- +2.31.1 + +From 851692b24cb46c2f5450d78583d6b148ad4f0f59 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 11 Nov 2020 13:42:40 +0100 +Subject: [PATCH] mwifiex: Allow switching interface type from P2P_CLIENT to + P2P_GO + +It's possible to change virtual interface type between P2P_CLIENT and +P2P_GO, the card supports that just fine, and it happens for example +when using miracast with the miraclecast software. + +So allow type changes between P2P_CLIENT and P2P_GO and simply call into +mwifiex_change_vif_to_p2p(), which handles this just fine. We have to +call mwifiex_cfg80211_deinit_p2p() before though to make sure the old +p2p mode is properly uninitialized. + +Patchset: mwifiex +--- + .../net/wireless/marvell/mwifiex/cfg80211.c | 36 +++++++++++++++++++ + 1 file changed, 36 insertions(+) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index e637129a411f..395573db6405 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -990,11 +990,26 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, + } + + case NL80211_IFTYPE_P2P_CLIENT: ++ switch (new_iftype) { ++ case NL80211_IFTYPE_ADHOC: ++ case NL80211_IFTYPE_STATION: ++ return true; ++ case NL80211_IFTYPE_P2P_GO: ++ return true; ++ case NL80211_IFTYPE_AP: ++ return adapter->curr_iface_comb.uap_intf != ++ adapter->iface_limit.uap_intf; ++ default: ++ return false; ++ } ++ + case NL80211_IFTYPE_P2P_GO: + switch (new_iftype) { + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_STATION: + return true; ++ case NL80211_IFTYPE_P2P_CLIENT: ++ return true; + case NL80211_IFTYPE_AP: + return adapter->curr_iface_comb.uap_intf != + adapter->iface_limit.uap_intf; +@@ -1265,6 +1280,24 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + } + + case NL80211_IFTYPE_P2P_CLIENT: ++ if (mwifiex_cfg80211_deinit_p2p(priv)) ++ return -EFAULT; ++ ++ switch (type) { ++ case NL80211_IFTYPE_ADHOC: ++ case NL80211_IFTYPE_STATION: ++ return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, ++ type, params); ++ case NL80211_IFTYPE_P2P_GO: ++ return mwifiex_change_vif_to_p2p(dev, curr_iftype, ++ type, params); ++ case NL80211_IFTYPE_AP: ++ return mwifiex_change_vif_to_ap(dev, curr_iftype, type, ++ params); ++ default: ++ goto errnotsupp; ++ } ++ + case NL80211_IFTYPE_P2P_GO: + if (mwifiex_cfg80211_deinit_p2p(priv)) + return -EFAULT; +@@ -1274,6 +1307,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + case NL80211_IFTYPE_STATION: + return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, + type, params); ++ case NL80211_IFTYPE_P2P_CLIENT: ++ return mwifiex_change_vif_to_p2p(dev, curr_iftype, ++ type, params); + case NL80211_IFTYPE_AP: + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); +-- +2.31.1 + +From 42a9b01e72f6ced95f6b27101ad9db53bd4a0215 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Fri, 26 Mar 2021 15:31:08 +0100 +Subject: [PATCH] mwifiex: Handle interface type changes from AP to STATION + +Looks like this case was simply overseen, so handle it, too. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/cfg80211.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 395573db6405..90a757aa0b25 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -1268,6 +1268,7 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + case NL80211_IFTYPE_AP: + switch (type) { + case NL80211_IFTYPE_ADHOC: ++ case NL80211_IFTYPE_STATION: + return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, + type, params); + break; +-- +2.31.1 + +From 0c02cc80b25f90ab410103a493d73fd023f05abb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Fri, 26 Mar 2021 15:32:16 +0100 +Subject: [PATCH] mwifiex: Properly initialize private structure on interface + type changes + +When creating a new virtual interface in mwifiex_add_virtual_intf(), we +update our internal driver states like bss_type, bss_priority, bss_role +and bss_mode to reflect the mode the firmware will be set to. + +When switching virtual interface mode using +mwifiex_init_new_priv_params() though, we currently only update bss_mode +and bss_role. In order for the interface mode switch to actually work, +we also need to update bss_type to its proper value, so do that. + +This fixes a crash of the firmware (because the driver tries to execute +commands that are invalid in AP mode) when switching from station mode +to AP mode. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 90a757aa0b25..0c01d8f9048e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -908,16 +908,20 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, + switch (type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: +- priv->bss_role = MWIFIEX_BSS_ROLE_STA; ++ priv->bss_role = MWIFIEX_BSS_ROLE_STA; ++ priv->bss_type = MWIFIEX_BSS_TYPE_STA; + break; + case NL80211_IFTYPE_P2P_CLIENT: +- priv->bss_role = MWIFIEX_BSS_ROLE_STA; ++ priv->bss_role = MWIFIEX_BSS_ROLE_STA; ++ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; + break; + case NL80211_IFTYPE_P2P_GO: +- priv->bss_role = MWIFIEX_BSS_ROLE_UAP; ++ priv->bss_role = MWIFIEX_BSS_ROLE_UAP; ++ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; + break; + case NL80211_IFTYPE_AP: + priv->bss_role = MWIFIEX_BSS_ROLE_UAP; ++ priv->bss_type = MWIFIEX_BSS_TYPE_UAP; + break; + default: + mwifiex_dbg(adapter, ERROR, +-- +2.31.1 + +From d2574c9f7fd3d0efc7bf8c07f8415c8bf18b9b74 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Sat, 27 Mar 2021 12:19:14 +0100 +Subject: [PATCH] mwifiex: Fix copy-paste mistake when creating virtual + interface + +The BSS priority here for a new P2P_CLIENT device was accidentally set +to an enum that's certainly not meant for this. Since +MWIFIEX_BSS_ROLE_STA is 0 anyway, we can just set the bss_priority to 0 +instead here. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 0c01d8f9048e..8c472b2d982a 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -3057,7 +3057,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, + priv->bss_type = MWIFIEX_BSS_TYPE_P2P; + + priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II; +- priv->bss_priority = MWIFIEX_BSS_ROLE_STA; ++ priv->bss_priority = 0; + priv->bss_role = MWIFIEX_BSS_ROLE_STA; + priv->bss_started = 0; + +-- +2.31.1 + +From 8a39e3cce26f0df0242d7fc5b8f91c3116c406e8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Sun, 28 Mar 2021 21:10:06 +0200 +Subject: [PATCH] mwifiex: Try waking the firmware until we get an interrupt + +It seems that the firmware of the 88W8897 card sometimes ignores or +misses when we try to wake it up by reading the firmware status +register. This leads to the firmware wakeup timeout expiring and the +driver resetting the card because we assume the firmware has hung up or +crashed (unfortunately that's not unlikely with this card). + +Turns out that most of the time the firmware actually didn't hang up, +but simply "missed" our wakeup request and doesn't send us an AWAKE +event. + +Trying again to read the firmware status register after a short timeout +usually makes the firmware wake we up as expected, so add a small retry +loop to mwifiex_pm_wakeup_card() that looks at the interrupt status to +check whether the card woke up. + +The number of tries and timeout lengths for this were determined +experimentally: The firmware usually takes about 500 us to wake up +after we attempt to read the status register. In some cases where the +firmware is very busy (for example while doing a bluetooth scan) it +might even miss our requests for multiple milliseconds, which is why +after 15 tries the waiting time gets increased to 10 ms. The maximum +number of tries it took to wake the firmware when testing this was +around 20, so a maximum number of 50 tries should give us plenty of +safety margin. + +A good reproducer for this issue is letting the firmware sleep and wake +up in very short intervals, for example by pinging an device on the +network every 0.1 seconds. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 29 ++++++++++++++++----- + 1 file changed, 23 insertions(+), 6 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 84b1d30e07e4..88d30ec6d57d 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -665,6 +665,7 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; ++ int n_tries = 0; + + mwifiex_dbg(adapter, EVENT, + "event: Wakeup device...\n"); +@@ -672,12 +673,28 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) + if (reg->sleep_cookie) + mwifiex_pcie_dev_wakeup_delay(adapter); + +- /* Accessing fw_status register will wakeup device */ +- if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { +- mwifiex_dbg(adapter, ERROR, +- "Writing fw_status register failed\n"); +- return -1; +- } ++ /* Access the fw_status register to wake up the device. ++ * Since the 88W8897 firmware sometimes appears to ignore or miss ++ * that wakeup request, we continue trying until we receive an ++ * interrupt from the card. ++ */ ++ do { ++ if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { ++ mwifiex_dbg(adapter, ERROR, ++ "Writing fw_status register failed\n"); ++ return -1; ++ } ++ ++ n_tries++; ++ ++ if (n_tries <= 15) ++ usleep_range(400, 700); ++ else ++ msleep(10); ++ } while (n_tries <= 50 && READ_ONCE(adapter->int_status) == 0); ++ ++ mwifiex_dbg(adapter, EVENT, ++ "event: Tried %d times until firmware woke up\n", n_tries); + + if (reg->sleep_cookie) { + mwifiex_pcie_dev_wakeup_delay(adapter); +-- +2.31.1 + +From 2eb1eb70eceb55d17d4948ee8b95159bac444c2a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 13 Apr 2021 14:30:28 +0200 +Subject: [PATCH] mwifiex: Deactive host sleep using HSCFG after it was + activated manually + +When powersaving (so either wifi powersaving or deep sleep, depending on +which state the firmware is in) is disabled, the way the firmware goes +into host sleep is different: Usually the firmware implicitely enters +host sleep on the next SLEEP event we get when we configured host sleep +via HSCFG before. When powersaving is disabled though, there are no +SLEEP events, the way we enter host sleep in that case is different: The +firmware will send us a HS_ACT_REQ event and after that we "manually" +make the firmware enter host sleep by sending it another HSCFG command +with the action HS_ACTIVATE. + +Now waking up from host sleep appears to be different depending on +whether powersaving is enabled again: When powersaving is enabled, the +firmware implicitely leaves host sleep as soon as it wakes up and sends +us an AWAKE event. When powersaving is disabled though, it apparently +doesn't implicitely leave host sleep, but instead we need to send it a +HSCFG command with the HS_CONFIGURE action and the HS_CFG_CANCEL +condition. We didn't do that so far, which is why waking up from host +sleep was broken when powersaving is disabled. + +So add some additional state to mwifiex_adapter where we keep track of +whether host sleep was activated manually via HS_ACTIVATE, and if that +was the case, deactivate it manually again via HS_CFG_CANCEL. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/cmdevt.c | 21 +++++++++++++++++++ + drivers/net/wireless/marvell/mwifiex/main.c | 18 ++++++++++++++++ + drivers/net/wireless/marvell/mwifiex/main.h | 1 + + .../net/wireless/marvell/mwifiex/sta_cmd.c | 4 ++++ + 4 files changed, 44 insertions(+) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cmdevt.c b/drivers/net/wireless/marvell/mwifiex/cmdevt.c +index 3a11342a6bde..5487df8f994d 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cmdevt.c ++++ b/drivers/net/wireless/marvell/mwifiex/cmdevt.c +@@ -608,6 +608,11 @@ int mwifiex_send_cmd(struct mwifiex_private *priv, u16 cmd_no, + return -1; + } + ++ if (priv->adapter->hs_activated_manually && ++ cmd_no != HostCmd_CMD_802_11_HS_CFG_ENH) { ++ mwifiex_cancel_hs(priv, MWIFIEX_ASYNC_CMD); ++ priv->adapter->hs_activated_manually = false; ++ } + + /* Get a new command node */ + cmd_node = mwifiex_get_cmd_node(adapter); +@@ -714,6 +719,15 @@ mwifiex_insert_cmd_to_pending_q(struct mwifiex_adapter *adapter, + } + } + ++ /* Same with exit host sleep cmd, luckily that can't happen at the same time as EXIT_PS */ ++ if (command == HostCmd_CMD_802_11_HS_CFG_ENH) { ++ struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = ++ &host_cmd->params.opt_hs_cfg; ++ ++ if (le16_to_cpu(hs_cfg->action) == HS_ACTIVATE) ++ add_tail = false; ++ } ++ + spin_lock_bh(&adapter->cmd_pending_q_lock); + if (add_tail) + list_add_tail(&cmd_node->list, &adapter->cmd_pending_q); +@@ -1216,6 +1230,13 @@ mwifiex_process_hs_config(struct mwifiex_adapter *adapter) + __func__); + + adapter->if_ops.wakeup(adapter); ++ ++ if (adapter->hs_activated_manually) { ++ mwifiex_cancel_hs(mwifiex_get_priv (adapter, MWIFIEX_BSS_ROLE_ANY), ++ MWIFIEX_ASYNC_CMD); ++ adapter->hs_activated_manually = false; ++ } ++ + adapter->hs_activated = false; + clear_bit(MWIFIEX_IS_HS_CONFIGURED, &adapter->work_flags); + clear_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags); +diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c +index 529dfd8b7ae8..56e4385927b4 100644 +--- a/drivers/net/wireless/marvell/mwifiex/main.c ++++ b/drivers/net/wireless/marvell/mwifiex/main.c +@@ -401,6 +401,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) + !adapter->scan_processing) && + !adapter->data_sent && + !skb_queue_empty(&adapter->tx_data_q)) { ++ if (adapter->hs_activated_manually) { ++ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), ++ MWIFIEX_ASYNC_CMD); ++ adapter->hs_activated_manually = false; ++ } ++ + mwifiex_process_tx_queue(adapter); + if (adapter->hs_activated) { + clear_bit(MWIFIEX_IS_HS_CONFIGURED, +@@ -418,6 +424,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) + !mwifiex_bypass_txlist_empty(adapter) && + !mwifiex_is_tdls_chan_switching + (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { ++ if (adapter->hs_activated_manually) { ++ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), ++ MWIFIEX_ASYNC_CMD); ++ adapter->hs_activated_manually = false; ++ } ++ + mwifiex_process_bypass_tx(adapter); + if (adapter->hs_activated) { + clear_bit(MWIFIEX_IS_HS_CONFIGURED, +@@ -434,6 +446,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) + !adapter->data_sent && !mwifiex_wmm_lists_empty(adapter) && + !mwifiex_is_tdls_chan_switching + (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { ++ if (adapter->hs_activated_manually) { ++ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), ++ MWIFIEX_ASYNC_CMD); ++ adapter->hs_activated_manually = false; ++ } ++ + mwifiex_wmm_process_tx(adapter); + if (adapter->hs_activated) { + clear_bit(MWIFIEX_IS_HS_CONFIGURED, +diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h +index 5923c5c14c8d..90012cbcfd15 100644 +--- a/drivers/net/wireless/marvell/mwifiex/main.h ++++ b/drivers/net/wireless/marvell/mwifiex/main.h +@@ -986,6 +986,7 @@ struct mwifiex_adapter { + struct timer_list wakeup_timer; + struct mwifiex_hs_config_param hs_cfg; + u8 hs_activated; ++ u8 hs_activated_manually; + u16 hs_activate_wait_q_woken; + wait_queue_head_t hs_activate_wait_q; + u8 event_body[MAX_EVENT_SIZE]; +diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c +index d3a968ef21ef..76db9a7b8199 100644 +--- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c ++++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c +@@ -396,6 +396,10 @@ mwifiex_cmd_802_11_hs_cfg(struct mwifiex_private *priv, + if (hs_activate) { + hs_cfg->action = cpu_to_le16(HS_ACTIVATE); + hs_cfg->params.hs_activate.resp_ctrl = cpu_to_le16(RESP_NEEDED); ++ ++ adapter->hs_activated_manually = true; ++ mwifiex_dbg(priv->adapter, CMD, ++ "cmd: Activating host sleep manually\n"); + } else { + hs_cfg->action = cpu_to_le16(HS_CONFIGURE); + hs_cfg->params.hs_config.conditions = hscfg_param->conditions; +-- +2.31.1 + +From 2d7964c9df5107c53d241816afd826683052d4d2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 13 Apr 2021 14:23:05 +0200 +Subject: [PATCH] mwifiex: Add quirk to disable deep sleep with certain + hardware revision + +The 88W8897 pcie card with the hardware revision 20 apparently has a +hardware issue where the card wakes up from deep sleep randomly and very +often, somewhat depending on the card activity, maybe the hardware has a +floating wakeup pin or something. + +Those continuous wakeups prevent the card from entering host sleep when +the computer suspends. And because the host won't answer to events from +the card anymore while it's suspended, the firmwares internal +powersaving state machine seems to get confused and the card can't sleep +anymore at all after that. + +Since we can't work around that hardware bug in the firmware, let's +get the hardware revision string from the firmware and match it with +known bad revisions. Then disable auto deep sleep for those revisions, +which makes sure we no longer get those spurious wakeups. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/main.c | 14 ++++++++++++++ + drivers/net/wireless/marvell/mwifiex/main.h | 1 + + .../net/wireless/marvell/mwifiex/sta_cmdresp.c | 16 ++++++++++++++++ + 3 files changed, 31 insertions(+) + +diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c +index 56e4385927b4..a6be59ac1e27 100644 +--- a/drivers/net/wireless/marvell/mwifiex/main.c ++++ b/drivers/net/wireless/marvell/mwifiex/main.c +@@ -226,6 +226,19 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter) + return 0; + } + ++static void maybe_quirk_fw_disable_ds(struct mwifiex_adapter *adapter) ++{ ++ struct mwifiex_private *priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA); ++ struct mwifiex_ver_ext ver_ext; ++ ++ set_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &adapter->work_flags); ++ ++ memset(&ver_ext, 0, sizeof(ver_ext)); ++ ver_ext.version_str_sel = 1; ++ mwifiex_send_cmd(priv, HostCmd_CMD_VERSION_EXT, ++ HostCmd_ACT_GEN_GET, 0, &ver_ext, false); ++} ++ + /* + * The main process. + * +@@ -356,6 +369,7 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) + if (adapter->hw_status == MWIFIEX_HW_STATUS_INIT_DONE) { + adapter->hw_status = MWIFIEX_HW_STATUS_READY; + mwifiex_init_fw_complete(adapter); ++ maybe_quirk_fw_disable_ds(adapter); + } + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h +index 90012cbcfd15..1e829d84b1f6 100644 +--- a/drivers/net/wireless/marvell/mwifiex/main.h ++++ b/drivers/net/wireless/marvell/mwifiex/main.h +@@ -524,6 +524,7 @@ enum mwifiex_adapter_work_flags { + MWIFIEX_IS_SUSPENDED, + MWIFIEX_IS_HS_CONFIGURED, + MWIFIEX_IS_HS_ENABLING, ++ MWIFIEX_IS_REQUESTING_FW_VEREXT, + }; + + struct mwifiex_band_config { +diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c +index 6b5d35d9e69f..8e49ebca1847 100644 +--- a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c ++++ b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c +@@ -708,6 +708,22 @@ static int mwifiex_ret_ver_ext(struct mwifiex_private *priv, + { + struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; + ++ if (test_and_clear_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) { ++ if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", 128) == 0) { ++ struct mwifiex_ds_auto_ds auto_ds = { ++ .auto_ds = DEEP_SLEEP_OFF, ++ }; ++ ++ mwifiex_dbg(priv->adapter, MSG, ++ "Bad HW revision detected, disabling deep sleep\n"); ++ ++ mwifiex_send_cmd(priv, HostCmd_CMD_802_11_PS_MODE_ENH, ++ DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false); ++ } ++ ++ return 0; ++ } ++ + if (version_ext) { + version_ext->version_str_sel = ver_ext->version_str_sel; + memcpy(version_ext->version_str, ver_ext->version_str, +-- +2.31.1 + +From 3efbf69305a003ee7ad0b09157be0ed6307b63e5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 11 Nov 2020 15:17:07 +0100 +Subject: [PATCH] mwifiex: Don't log error on suspend if wake-on-wlan is + disabled + +It's not an error if someone chooses to put their computer to sleep, not +wanting it to wake up because the person next door has just discovered +what a magic packet is. So change the loglevel of this annoying message +from ERROR to INFO. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 8c472b2d982a..153025d1b2fa 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -3497,7 +3497,7 @@ static int mwifiex_cfg80211_suspend(struct wiphy *wiphy, + } + + if (!wowlan) { +- mwifiex_dbg(adapter, ERROR, ++ mwifiex_dbg(adapter, INFO, + "None of the WOWLAN triggers enabled\n"); + ret = 0; + goto done; +-- +2.31.1 + +From 24eace3d82040069a68e13d6a80053ae1d597306 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Sun, 28 Mar 2021 21:42:54 +0200 +Subject: [PATCH] mwifiex: Log an error on command failure during key-material + upload + +Sometimes the KEY_MATERIAL command can fail with the 88W8897 firmware +(when this happens exactly seems pretty random). This appears to prevent +the access point from starting, so it seems like a good idea to log an +error in that case. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 153025d1b2fa..ef6ce3f63aec 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -519,8 +519,14 @@ mwifiex_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, + encrypt_key.is_igtk_def_key = true; + eth_broadcast_addr(encrypt_key.mac_addr); + +- return mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, +- HostCmd_ACT_GEN_SET, true, &encrypt_key, true); ++ if (mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, ++ HostCmd_ACT_GEN_SET, true, &encrypt_key, true)) { ++ mwifiex_dbg(priv->adapter, ERROR, ++ "Sending KEY_MATERIAL command failed\n"); ++ return -1; ++ } ++ ++ return 0; + } + + /* +-- +2.31.1 + +From 0d3a1e277248feaf3da3cd21f0ce68bfeec46f65 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 13 Apr 2021 12:44:03 +0200 +Subject: [PATCH] mwifiex: Fix an incorrect comment + +We're sending DELBA requests here, not ADDBA requests. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/11n.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c +index 6696bce56178..b0695432b26a 100644 +--- a/drivers/net/wireless/marvell/mwifiex/11n.c ++++ b/drivers/net/wireless/marvell/mwifiex/11n.c +@@ -125,7 +125,7 @@ int mwifiex_ret_11n_delba(struct mwifiex_private *priv, + tx_ba_tbl->ra); + } else { /* + * In case of failure, recreate the deleted stream in case +- * we initiated the ADDBA ++ * we initiated the DELBA + */ + if (!INITIATOR_BIT(del_ba_param_set)) + return 0; +-- +2.31.1 + +From 06335e34442a195630c19a55abba6f7b4dc3b7ba Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 13 Apr 2021 12:45:59 +0200 +Subject: [PATCH] mwifiex: Send DELBA requests according to spec + +We're currently failing to set the initiator bit for DELBA requests: +While we set the bit on our del_ba_param_set bitmask, we forget to +actually copy that bitmask over to the command struct, which means we +never actually set the initiator bit. + +Fix that and copy the bitmask over to the host_cmd_ds_11n_delba command +struct. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/11n.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c +index b0695432b26a..9ff2058bcd7e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/11n.c ++++ b/drivers/net/wireless/marvell/mwifiex/11n.c +@@ -657,14 +657,15 @@ int mwifiex_send_delba(struct mwifiex_private *priv, int tid, u8 *peer_mac, + uint16_t del_ba_param_set; + + memset(&delba, 0, sizeof(delba)); +- delba.del_ba_param_set = cpu_to_le16(tid << DELBA_TID_POS); + +- del_ba_param_set = le16_to_cpu(delba.del_ba_param_set); ++ del_ba_param_set = tid << DELBA_TID_POS; ++ + if (initiator) + del_ba_param_set |= IEEE80211_DELBA_PARAM_INITIATOR_MASK; + else + del_ba_param_set &= ~IEEE80211_DELBA_PARAM_INITIATOR_MASK; + ++ delba.del_ba_param_set = cpu_to_le16(del_ba_param_set); + memcpy(&delba.peer_mac_addr, peer_mac, ETH_ALEN); + + /* We don't wait for the response of this command */ +-- +2.31.1 + +From c8196c059e85bacc463ebb7055359735bd20ad0a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 13 Apr 2021 12:57:41 +0200 +Subject: [PATCH] mwifiex: Ignore BTCOEX events from the firmware + +The firmware of the pcie 88W8897 chip sends those events very +unreliably, which means we sometimes end up actually capping the window +size while bluetooth is disabled, artifically limiting wifi speeds even +though it's not needed. + +Since we can't fix the firmware, let's just ignore those events, it +seems that the Windows driver also doesn't change the rx/tx block ack +buffer sizes when bluetooth gets enabled or disabled, so this is +consistent with the Windows driver. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/sta_event.c | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/sta_event.c b/drivers/net/wireless/marvell/mwifiex/sta_event.c +index 68c63268e2e6..933111a3511c 100644 +--- a/drivers/net/wireless/marvell/mwifiex/sta_event.c ++++ b/drivers/net/wireless/marvell/mwifiex/sta_event.c +@@ -1057,9 +1057,7 @@ int mwifiex_process_sta_event(struct mwifiex_private *priv) + adapter->event_skb); + break; + case EVENT_BT_COEX_WLAN_PARA_CHANGE: +- dev_dbg(adapter->dev, "EVENT: BT coex wlan param update\n"); +- mwifiex_bt_coex_wlan_param_update_event(priv, +- adapter->event_skb); ++ dev_dbg(adapter->dev, "EVENT: ignoring BT coex wlan param update\n"); + break; + case EVENT_RXBA_SYNC: + dev_dbg(adapter->dev, "EVENT: RXBA_SYNC\n"); +-- +2.31.1 + diff --git a/patches/5.12/0003-ath10k.patch b/patches/5.12/0003-ath10k.patch new file mode 100644 index 000000000..a06978a21 --- /dev/null +++ b/patches/5.12/0003-ath10k.patch @@ -0,0 +1,121 @@ +From 19d746dc9980d9109b93c9424138d3966c8a67cf 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 2f9be182fbfb..84ae17af3f98 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -35,6 +35,9 @@ static bool skip_otp; + static bool rawmode; + static bool fw_diag_log; + ++static char *override_board = ""; ++static char *override_board2 = ""; ++ + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | + BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); + +@@ -47,6 +50,9 @@ module_param(rawmode, bool, 0644); + module_param(fw_diag_log, bool, 0644); + module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); + ++module_param(override_board, charp, 0644); ++module_param(override_board2, charp, 0644); ++ + MODULE_PARM_DESC(debug_mask, "Debugging mask"); + MODULE_PARM_DESC(uart_print, "Uart target debugging"); + MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); +@@ -55,6 +61,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); + MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); + MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); + ++MODULE_PARM_DESC(override_board, "Override for board.bin file"); ++MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); ++ + static const struct ath10k_hw_params ath10k_hw_params_list[] = { + { + .id = QCA988X_HW_2_0_VERSION, +@@ -826,6 +835,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) + return 0; + } + ++static const char *ath10k_override_board_fw_file(struct ath10k *ar, ++ const char *file) ++{ ++ if (strcmp(file, "board.bin") == 0) { ++ if (strcmp(override_board, "") == 0) ++ return file; ++ ++ if (strcmp(override_board, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", ++ override_board); ++ ++ return override_board; ++ } ++ ++ if (strcmp(file, "board-2.bin") == 0) { ++ if (strcmp(override_board2, "") == 0) ++ return file; ++ ++ if (strcmp(override_board2, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", ++ override_board2); ++ ++ return override_board2; ++ } ++ ++ return file; ++} ++ + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + const char *dir, + const char *file) +@@ -840,6 +885,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + if (dir == NULL) + dir = "."; + ++ /* HACK: Override board.bin and board-2.bin files if specified. ++ * ++ * Some Surface devices perform better with a different board ++ * configuration. To this end, one would need to replace the board.bin ++ * file with the modified config and remove the board-2.bin file. ++ * Unfortunately, that's not a solution that we can easily package. So ++ * we add module options to perform these overrides here. ++ */ ++ ++ file = ath10k_override_board_fw_file(ar, file); ++ if (!file) ++ return ERR_PTR(-ENOENT); ++ + snprintf(filename, sizeof(filename), "%s/%s", dir, file); + ret = firmware_request_nowarn(&fw, filename, ar->dev); + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", +-- +2.31.1 + diff --git a/patches/5.12/0004-ipts.patch b/patches/5.12/0004-ipts.patch new file mode 100644 index 000000000..bafb20a1d --- /dev/null +++ b/patches/5.12/0004-ipts.patch @@ -0,0 +1,1503 @@ +From 744ada53b8de5b3aab134a0593f09d2845b10a24 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 cb34925e10f1..2b3f8073a3ec 100644 +--- a/drivers/misc/mei/hw-me-regs.h ++++ b/drivers/misc/mei/hw-me-regs.h +@@ -92,6 +92,7 @@ + #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ + + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ ++#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ + + #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ + +diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +index c3393b383e59..0098f98426c1 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -96,6 +96,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_H, MEI_ME_PCH15_SPS_CFG)}, +-- +2.31.1 + +From 714d09089cdf91c6f2f54f04d08be663884777eb Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +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 f532c59bb59b..cc973f55f5ca 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -461,4 +461,5 @@ source "drivers/misc/bcm-vk/Kconfig" + source "drivers/misc/cardreader/Kconfig" + source "drivers/misc/habanalabs/Kconfig" + source "drivers/misc/uacce/Kconfig" ++source "drivers/misc/ipts/Kconfig" + endmenu +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index 99b6f15a3c70..9470a93d7fa4 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -56,3 +56,4 @@ obj-$(CONFIG_HABANA_AI) += habanalabs/ + obj-$(CONFIG_UACCE) += uacce/ + obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o + obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o ++obj-$(CONFIG_MISC_IPTS) += ipts/ +diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig +new file mode 100644 +index 000000000000..83e2a930c396 +--- /dev/null ++++ b/drivers/misc/ipts/Kconfig +@@ -0,0 +1,17 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config MISC_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ depends on INTEL_MEI ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. ++ ++ Building this driver alone will not give you a working touchscreen. ++ It only exposed a userspace API that can be used by a daemon to ++ receive and process data from the touchscreen hardware. +diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile +new file mode 100644 +index 000000000000..8f58b9adbc94 +--- /dev/null ++++ b/drivers/misc/ipts/Makefile +@@ -0,0 +1,12 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_MISC_IPTS) += ipts.o ++ipts-objs := control.o ++ipts-objs += mei.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += uapi.o ++ +diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h +new file mode 100644 +index 000000000000..f4b06a2d3f72 +--- /dev/null ++++ b/drivers/misc/ipts/context.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef _IPTS_CONTEXT_H_ ++#define _IPTS_CONTEXT_H_ ++ ++#include ++#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.31.1 + diff --git a/patches/5.12/0005-surface-sam-over-hid.patch b/patches/5.12/0005-surface-sam-over-hid.patch new file mode 100644 index 000000000..19655e6ad --- /dev/null +++ b/patches/5.12/0005-surface-sam-over-hid.patch @@ -0,0 +1,335 @@ +From e72c7d0b6c7b5ee1f99a749ff56379bb67033ec3 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +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 8ceaa88dd78f..deceed0d76c6 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -570,6 +570,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, + return (ret == 1) ? 0 : -EIO; + } + ++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, ++ u8 *data, u8 data_len) ++{ ++ struct i2c_msg msgs[1]; ++ int ret = AE_OK; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = client->flags; ++ msgs[0].len = data_len + 1; ++ msgs[0].buf = data; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ ++ if (ret < 0) { ++ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* 1 transfer must have completed successfully */ ++ return (ret == 1) ? 0 : -EIO; ++} ++ + static acpi_status + i2c_acpi_space_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, +@@ -671,6 +693,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, + } + break; + ++ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: ++ if (action == ACPI_READ) { ++ dev_warn(&adapter->dev, ++ "protocol 0x%02x not supported for client 0x%02x\n", ++ accessor_type, client->addr); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } else { ++ status = acpi_gsb_i2c_write_raw_bytes(client, ++ gsb->data, info->access_length); ++ } ++ break; ++ + default: + dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); +-- +2.31.1 + +From 597647585a2c0f369f47a3f5c1e58a1251b8fa97 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 13 Feb 2021 16:41:18 +0100 +Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch + +Add driver exposing the discrete GPU power-switch of the Microsoft +Surface Book 1 to user-space. + +On the Surface Book 1, the dGPU power is controlled via the Surface +System Aggregator Module (SAM). The specific SAM-over-HID command for +this is exposed via ACPI. This module provides a simple driver exposing +the ACPI call via a sysfs parameter to user-space, so that users can +easily power-on/-off the dGPU. + +Patchset: surface-sam-over-hid +--- + drivers/platform/surface/Kconfig | 7 + + drivers/platform/surface/Makefile | 1 + + .../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++ + 3 files changed, 170 insertions(+) + create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c + +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index 0847b2dc97bf..fd45940ab6ce 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -77,6 +77,13 @@ config SURFACE_AGGREGATOR_CDEV + The provided interface is intended for debugging and development only, + and should not be used otherwise. + ++config SURFACE_BOOK1_DGPU_SWITCH ++ tristate "Surface Book 1 dGPU Switch Driver" ++ depends on SYSFS ++ help ++ This driver provides a sysfs switch to set the power-state of the ++ discrete GPU found on the Microsoft Surface Book 1. ++ + config SURFACE_GPE + tristate "Surface GPE/Lid Support Driver" + depends on DMI +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 990424c5f0c9..6b69175598ab 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -10,6 +10,7 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o + obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o + obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ + obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o ++obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o + obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o +diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c +new file mode 100644 +index 000000000000..8b816ed8f35c +--- /dev/null ++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c +@@ -0,0 +1,162 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#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.31.1 + diff --git a/patches/5.12/0006-surface-sam.patch b/patches/5.12/0006-surface-sam.patch new file mode 100644 index 000000000..dd16e3833 --- /dev/null +++ b/patches/5.12/0006-surface-sam.patch @@ -0,0 +1,7402 @@ +From d85a26f2eb7a2ed68e4c56030c030bd151f90eeb Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 12 Feb 2021 12:54:34 +0100 +Subject: [PATCH] platform/surface: Set up Surface Aggregator device registry + +The Surface System Aggregator Module (SSAM) subsystem provides various +functionalities, which are separated by spreading them across multiple +devices and corresponding drivers. Parts of that functionality / some of +those devices, however, can (as far as we currently know) not be +auto-detected by conventional means. While older (specifically 5th- and +6th-)generation models do advertise most of their functionality via +standard platform devices in ACPI, newer generations do not. + +As we are currently also not aware of any feasible way to query said +functionalities dynamically, this poses a problem. There is, however, a +device in ACPI that seems to be used by Windows for identifying +different Surface models: The Windows Surface Integration Device (WSID). +This device seems to have a HID corresponding to the overall set of +functionalities SSAM provides for the associated model. + +This commit introduces a registry providing non-detectable device +information via software nodes. In addition, a SSAM platform hub driver +is introduced, which takes care of creating and managing the SSAM +devices specified in this registry. This approach allows for a +hierarchical setup akin to ACPI and is easily extendable, e.g. via +firmware node properties. + +Note that this commit only provides the basis for the platform hub and +registry, and does not add any content to it. The registry will be +expanded in subsequent commits. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210212115439.1525216-2-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + MAINTAINERS | 1 + + drivers/platform/surface/Kconfig | 27 ++ + drivers/platform/surface/Makefile | 1 + + .../surface/surface_aggregator_registry.c | 284 ++++++++++++++++++ + 4 files changed, 313 insertions(+) + create mode 100644 drivers/platform/surface/surface_aggregator_registry.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index 9450e052f1b1..f6c524630575 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -11904,6 +11904,7 @@ F: Documentation/driver-api/surface_aggregator/ + F: drivers/platform/surface/aggregator/ + F: drivers/platform/surface/surface_acpi_notify.c + F: drivers/platform/surface/surface_aggregator_cdev.c ++F: drivers/platform/surface/surface_aggregator_registry.c + F: include/linux/surface_acpi_notify.h + F: include/linux/surface_aggregator/ + F: include/uapi/linux/surface_aggregator/ +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index fd45940ab6ce..c51c55204b5f 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -77,6 +77,33 @@ config SURFACE_AGGREGATOR_CDEV + The provided interface is intended for debugging and development only, + and should not be used otherwise. + ++config SURFACE_AGGREGATOR_REGISTRY ++ tristate "Surface System Aggregator Module Device Registry" ++ depends on SURFACE_AGGREGATOR ++ depends on SURFACE_AGGREGATOR_BUS ++ help ++ Device-registry and device-hubs for Surface System Aggregator Module ++ (SSAM) devices. ++ ++ Provides a module and driver which act as a device-registry for SSAM ++ client devices that cannot be detected automatically, e.g. via ACPI. ++ Such devices are instead provided via this registry and attached via ++ device hubs, also provided in this module. ++ ++ Devices provided via this registry are: ++ - Platform profile (performance-/cooling-mode) device (5th- and later ++ generations). ++ - Battery/AC devices (7th-generation). ++ - HID input devices (7th-generation). ++ ++ Select M (recommended) or Y here if you want support for the above ++ mentioned devices on the corresponding Surface models. Without this ++ module, the respective devices will not be instantiated and thus any ++ functionality provided by them will be missing, even when drivers for ++ these devices are present. In other words, this module only provides ++ the respective client devices. Drivers for these devices still need to ++ be selected via the other options. ++ + config SURFACE_BOOK1_DGPU_SWITCH + tristate "Surface Book 1 dGPU Switch Driver" + depends on SYSFS +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 6b69175598ab..ed12676f06e6 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -10,6 +10,7 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o + obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o + obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ + obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o ++obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +new file mode 100644 +index 000000000000..a051d941ad96 +--- /dev/null ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -0,0 +1,284 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Surface System Aggregator Module (SSAM) client device registry. ++ * ++ * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that ++ * cannot be auto-detected. Provides device-hubs and performs instantiation ++ * for these devices. ++ * ++ * Copyright (C) 2020-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++ ++/* -- Device registry. ------------------------------------------------------ */ ++ ++/* ++ * SSAM device names follow the SSAM module alias, meaning they are prefixed ++ * with 'ssam:', followed by domain, category, target ID, instance ID, and ++ * function, each encoded as two-digit hexadecimal, separated by ':'. In other ++ * words, it follows the scheme ++ * ++ * ssam:dd:cc:tt:ii:ff ++ * ++ * Where, 'dd', 'cc', 'tt', 'ii', and 'ff' are the two-digit hexadecimal ++ * values mentioned above, respectively. ++ */ ++ ++/* Root node. */ ++static const struct software_node ssam_node_root = { ++ .name = "ssam_platform_hub", ++}; ++ ++/* Devices for Surface Book 2. */ ++static const struct software_node *ssam_node_group_sb2[] = { ++ &ssam_node_root, ++ NULL, ++}; ++ ++/* Devices for Surface Book 3. */ ++static const struct software_node *ssam_node_group_sb3[] = { ++ &ssam_node_root, ++ NULL, ++}; ++ ++/* Devices for Surface Laptop 1. */ ++static const struct software_node *ssam_node_group_sl1[] = { ++ &ssam_node_root, ++ NULL, ++}; ++ ++/* Devices for Surface Laptop 2. */ ++static const struct software_node *ssam_node_group_sl2[] = { ++ &ssam_node_root, ++ NULL, ++}; ++ ++/* Devices for Surface Laptop 3. */ ++static const struct software_node *ssam_node_group_sl3[] = { ++ &ssam_node_root, ++ NULL, ++}; ++ ++/* Devices for Surface Laptop Go. */ ++static const struct software_node *ssam_node_group_slg1[] = { ++ &ssam_node_root, ++ NULL, ++}; ++ ++/* Devices for Surface Pro 5. */ ++static const struct software_node *ssam_node_group_sp5[] = { ++ &ssam_node_root, ++ NULL, ++}; ++ ++/* Devices for Surface Pro 6. */ ++static const struct software_node *ssam_node_group_sp6[] = { ++ &ssam_node_root, ++ NULL, ++}; ++ ++/* Devices for Surface Pro 7. */ ++static const struct software_node *ssam_node_group_sp7[] = { ++ &ssam_node_root, ++ NULL, ++}; ++ ++ ++/* -- Device registry helper functions. ------------------------------------- */ ++ ++static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) ++{ ++ u8 d, tc, tid, iid, fn; ++ int n; ++ ++ n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); ++ if (n != 5) ++ return -EINVAL; ++ ++ uid->domain = d; ++ uid->category = tc; ++ uid->target = tid; ++ uid->instance = iid; ++ uid->function = fn; ++ ++ return 0; ++} ++ ++static int ssam_hub_remove_devices_fn(struct device *dev, void *data) ++{ ++ if (!is_ssam_device(dev)) ++ return 0; ++ ++ ssam_device_remove(to_ssam_device(dev)); ++ return 0; ++} ++ ++static void ssam_hub_remove_devices(struct device *parent) ++{ ++ device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn); ++} ++ ++static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, ++ struct fwnode_handle *node) ++{ ++ struct ssam_device_uid uid; ++ struct ssam_device *sdev; ++ int status; ++ ++ status = ssam_uid_from_string(fwnode_get_name(node), &uid); ++ if (status) ++ return status; ++ ++ sdev = ssam_device_alloc(ctrl, uid); ++ if (!sdev) ++ return -ENOMEM; ++ ++ sdev->dev.parent = parent; ++ sdev->dev.fwnode = node; ++ ++ status = ssam_device_add(sdev); ++ if (status) ++ ssam_device_put(sdev); ++ ++ return status; ++} ++ ++static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl, ++ struct fwnode_handle *node) ++{ ++ struct fwnode_handle *child; ++ int status; ++ ++ fwnode_for_each_child_node(node, child) { ++ /* ++ * Try to add the device specified in the firmware node. If ++ * this fails with -EINVAL, the node does not specify any SSAM ++ * device, so ignore it and continue with the next one. ++ */ ++ ++ status = ssam_hub_add_device(parent, ctrl, child); ++ if (status && status != -EINVAL) ++ goto err; ++ } ++ ++ return 0; ++err: ++ ssam_hub_remove_devices(parent); ++ return status; ++} ++ ++ ++/* -- SSAM platform/meta-hub driver. ---------------------------------------- */ ++ ++static const struct acpi_device_id ssam_platform_hub_match[] = { ++ /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ ++ { "MSHW0081", (unsigned long)ssam_node_group_sp5 }, ++ ++ /* Surface Pro 6 (OMBR >= 0x10) */ ++ { "MSHW0111", (unsigned long)ssam_node_group_sp6 }, ++ ++ /* Surface Pro 7 */ ++ { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, ++ ++ /* Surface Book 2 */ ++ { "MSHW0107", (unsigned long)ssam_node_group_sb2 }, ++ ++ /* Surface Book 3 */ ++ { "MSHW0117", (unsigned long)ssam_node_group_sb3 }, ++ ++ /* Surface Laptop 1 */ ++ { "MSHW0086", (unsigned long)ssam_node_group_sl1 }, ++ ++ /* Surface Laptop 2 */ ++ { "MSHW0112", (unsigned long)ssam_node_group_sl2 }, ++ ++ /* Surface Laptop 3 (13", Intel) */ ++ { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, ++ ++ /* Surface Laptop 3 (15", AMD) */ ++ { "MSHW0110", (unsigned long)ssam_node_group_sl3 }, ++ ++ /* Surface Laptop Go 1 */ ++ { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, ++ ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match); ++ ++static int ssam_platform_hub_probe(struct platform_device *pdev) ++{ ++ const struct software_node **nodes; ++ struct ssam_controller *ctrl; ++ struct fwnode_handle *root; ++ int status; ++ ++ nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev); ++ if (!nodes) ++ return -ENODEV; ++ ++ /* ++ * As we're adding the SSAM client devices as children under this device ++ * and not the SSAM controller, we need to add a device link to the ++ * controller to ensure that we remove all of our devices before the ++ * controller is removed. This also guarantees proper ordering for ++ * suspend/resume of the devices on this hub. ++ */ ++ ctrl = ssam_client_bind(&pdev->dev); ++ if (IS_ERR(ctrl)) ++ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); ++ ++ status = software_node_register_node_group(nodes); ++ if (status) ++ return status; ++ ++ root = software_node_fwnode(&ssam_node_root); ++ if (!root) { ++ software_node_unregister_node_group(nodes); ++ return -ENOENT; ++ } ++ ++ set_secondary_fwnode(&pdev->dev, root); ++ ++ status = ssam_hub_add_devices(&pdev->dev, ctrl, root); ++ if (status) { ++ set_secondary_fwnode(&pdev->dev, NULL); ++ software_node_unregister_node_group(nodes); ++ } ++ ++ platform_set_drvdata(pdev, nodes); ++ return status; ++} ++ ++static int ssam_platform_hub_remove(struct platform_device *pdev) ++{ ++ const struct software_node **nodes = platform_get_drvdata(pdev); ++ ++ ssam_hub_remove_devices(&pdev->dev); ++ set_secondary_fwnode(&pdev->dev, NULL); ++ software_node_unregister_node_group(nodes); ++ return 0; ++} ++ ++static struct platform_driver ssam_platform_hub_driver = { ++ .probe = ssam_platform_hub_probe, ++ .remove = ssam_platform_hub_remove, ++ .driver = { ++ .name = "surface_aggregator_platform_hub", ++ .acpi_match_table = ssam_platform_hub_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(ssam_platform_hub_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.31.1 + +From fc0cef48bcf0326c3cecd52fc22bdec318b7d95c Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 12 Feb 2021 12:54:35 +0100 +Subject: [PATCH] platform/surface: aggregator_registry: Add base device hub + +The Surface Book 3 has a detachable base part. While the top part +(so-called clipboard) contains the CPU, touchscreen, and primary +battery, the base contains, among other things, a keyboard, touchpad, +and secondary battery. + +Those devices do not react well to being accessed when the base part is +detached and should thus be removed and added in sync with the base. To +facilitate this, we introduce a virtual base device hub, which +automatically removes or adds the devices registered under it. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210212115439.1525216-3-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 261 +++++++++++++++++- + 1 file changed, 260 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index a051d941ad96..6c23d75a044c 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -11,9 +11,12 @@ + + #include + #include ++#include + #include ++#include + #include + #include ++#include + + #include + #include +@@ -38,6 +41,12 @@ static const struct software_node ssam_node_root = { + .name = "ssam_platform_hub", + }; + ++/* Base device hub (devices attached to Surface Book 3 base). */ ++static const struct software_node ssam_node_hub_base = { ++ .name = "ssam:00:00:02:00:00", ++ .parent = &ssam_node_root, ++}; ++ + /* Devices for Surface Book 2. */ + static const struct software_node *ssam_node_group_sb2[] = { + &ssam_node_root, +@@ -47,6 +56,7 @@ static const struct software_node *ssam_node_group_sb2[] = { + /* Devices for Surface Book 3. */ + static const struct software_node *ssam_node_group_sb3[] = { + &ssam_node_root, ++ &ssam_node_hub_base, + NULL, + }; + +@@ -177,6 +187,230 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c + } + + ++/* -- SSAM base-hub driver. ------------------------------------------------- */ ++ ++enum ssam_base_hub_state { ++ SSAM_BASE_HUB_UNINITIALIZED, ++ SSAM_BASE_HUB_CONNECTED, ++ SSAM_BASE_HUB_DISCONNECTED, ++}; ++ ++struct ssam_base_hub { ++ struct ssam_device *sdev; ++ ++ struct mutex lock; /* Guards state update checks and transitions. */ ++ enum ssam_base_hub_state state; ++ ++ struct ssam_event_notifier notif; ++}; ++ ++static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x0d, ++ .instance_id = 0x00, ++}); ++ ++#define SSAM_BAS_OPMODE_TABLET 0x00 ++#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c ++ ++static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state) ++{ ++ u8 opmode; ++ int status; ++ ++ status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); ++ if (status < 0) { ++ dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); ++ return status; ++ } ++ ++ if (opmode != SSAM_BAS_OPMODE_TABLET) ++ *state = SSAM_BASE_HUB_CONNECTED; ++ else ++ *state = SSAM_BASE_HUB_DISCONNECTED; ++ ++ return 0; ++} ++ ++static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct ssam_base_hub *hub = dev_get_drvdata(dev); ++ bool connected; ++ ++ mutex_lock(&hub->lock); ++ connected = hub->state == SSAM_BASE_HUB_CONNECTED; ++ mutex_unlock(&hub->lock); ++ ++ return sysfs_emit(buf, "%d\n", connected); ++} ++ ++static struct device_attribute ssam_base_hub_attr_state = ++ __ATTR(state, 0444, ssam_base_hub_state_show, NULL); ++ ++static struct attribute *ssam_base_hub_attrs[] = { ++ &ssam_base_hub_attr_state.attr, ++ NULL, ++}; ++ ++const struct attribute_group ssam_base_hub_group = { ++ .attrs = ssam_base_hub_attrs, ++}; ++ ++static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new) ++{ ++ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); ++ int status = 0; ++ ++ lockdep_assert_held(&hub->lock); ++ ++ if (hub->state == new) ++ return 0; ++ hub->state = new; ++ ++ if (hub->state == SSAM_BASE_HUB_CONNECTED) ++ status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); ++ else ++ ssam_hub_remove_devices(&hub->sdev->dev); ++ ++ if (status) ++ dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); ++ ++ return status; ++} ++ ++static int ssam_base_hub_update(struct ssam_base_hub *hub) ++{ ++ enum ssam_base_hub_state state; ++ int status; ++ ++ mutex_lock(&hub->lock); ++ ++ status = ssam_base_hub_query_state(hub, &state); ++ if (!status) ++ status = __ssam_base_hub_update(hub, state); ++ ++ mutex_unlock(&hub->lock); ++ return status; ++} ++ ++static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) ++{ ++ struct ssam_base_hub *hub; ++ struct ssam_device *sdev; ++ enum ssam_base_hub_state new; ++ ++ hub = container_of(nf, struct ssam_base_hub, notif); ++ sdev = hub->sdev; ++ ++ if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) ++ return 0; ++ ++ if (event->length < 1) { ++ dev_err(&sdev->dev, "unexpected payload size: %u\n", ++ event->length); ++ return 0; ++ } ++ ++ if (event->data[0]) ++ new = SSAM_BASE_HUB_CONNECTED; ++ else ++ new = SSAM_BASE_HUB_DISCONNECTED; ++ ++ mutex_lock(&hub->lock); ++ __ssam_base_hub_update(hub, new); ++ mutex_unlock(&hub->lock); ++ ++ /* ++ * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and ++ * consumed by the detachment system driver. We're just a (more or less) ++ * silent observer. ++ */ ++ return 0; ++} ++ ++static int __maybe_unused ssam_base_hub_resume(struct device *dev) ++{ ++ return ssam_base_hub_update(dev_get_drvdata(dev)); ++} ++static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); ++ ++static int ssam_base_hub_probe(struct ssam_device *sdev) ++{ ++ struct ssam_base_hub *hub; ++ int status; ++ ++ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); ++ if (!hub) ++ return -ENOMEM; ++ ++ mutex_init(&hub->lock); ++ ++ hub->sdev = sdev; ++ hub->state = SSAM_BASE_HUB_UNINITIALIZED; ++ ++ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ ++ hub->notif.base.fn = ssam_base_hub_notif; ++ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; ++ hub->notif.event.id.target_category = SSAM_SSH_TC_BAS, ++ hub->notif.event.id.instance = 0, ++ hub->notif.event.mask = SSAM_EVENT_MASK_NONE; ++ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; ++ ++ ssam_device_set_drvdata(sdev, hub); ++ ++ status = ssam_notifier_register(sdev->ctrl, &hub->notif); ++ if (status) ++ goto err_register; ++ ++ status = ssam_base_hub_update(hub); ++ if (status) ++ goto err_update; ++ ++ status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); ++ if (status) ++ goto err_update; ++ ++ return 0; ++ ++err_update: ++ ssam_notifier_unregister(sdev->ctrl, &hub->notif); ++ ssam_hub_remove_devices(&sdev->dev); ++err_register: ++ mutex_destroy(&hub->lock); ++ return status; ++} ++ ++static void ssam_base_hub_remove(struct ssam_device *sdev) ++{ ++ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); ++ ++ sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); ++ ++ ssam_notifier_unregister(sdev->ctrl, &hub->notif); ++ ssam_hub_remove_devices(&sdev->dev); ++ ++ mutex_destroy(&hub->lock); ++} ++ ++static const struct ssam_device_id ssam_base_hub_match[] = { ++ { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, ++ { }, ++}; ++ ++static struct ssam_device_driver ssam_base_hub_driver = { ++ .probe = ssam_base_hub_probe, ++ .remove = ssam_base_hub_remove, ++ .match_table = ssam_base_hub_match, ++ .driver = { ++ .name = "surface_aggregator_base_hub", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .pm = &ssam_base_hub_pm_ops, ++ }, ++}; ++ ++ + /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ + + static const struct acpi_device_id ssam_platform_hub_match[] = { +@@ -277,7 +511,32 @@ static struct platform_driver ssam_platform_hub_driver = { + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + }; +-module_platform_driver(ssam_platform_hub_driver); ++ ++ ++/* -- Module initialization. ------------------------------------------------ */ ++ ++static int __init ssam_device_hub_init(void) ++{ ++ int status; ++ ++ status = platform_driver_register(&ssam_platform_hub_driver); ++ if (status) ++ return status; ++ ++ status = ssam_device_driver_register(&ssam_base_hub_driver); ++ if (status) ++ platform_driver_unregister(&ssam_platform_hub_driver); ++ ++ return status; ++} ++module_init(ssam_device_hub_init); ++ ++static void __exit ssam_device_hub_exit(void) ++{ ++ ssam_device_driver_unregister(&ssam_base_hub_driver); ++ platform_driver_unregister(&ssam_platform_hub_driver); ++} ++module_exit(ssam_device_hub_exit); + + MODULE_AUTHOR("Maximilian Luz "); + MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); +-- +2.31.1 + +From 9d99facf87190ec57305a908829037887d2a324d Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 12 Feb 2021 12:54:36 +0100 +Subject: [PATCH] platform/surface: aggregator_registry: Add battery subsystem + devices + +Add battery subsystem (TC=0x02) devices (battery and AC) to the SSAM +device registry. These devices need to be registered for 7th-generation +Surface models. On 5th- and 6th-generation models, these devices are +handled via the standard ACPI battery/AC interface, which in turn +accesses the same SSAM interface via the Surface ACPI Notify (SAN) +driver. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210212115439.1525216-4-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 27 +++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 6c23d75a044c..cde279692842 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -47,6 +47,24 @@ static const struct software_node ssam_node_hub_base = { + .parent = &ssam_node_root, + }; + ++/* AC adapter. */ ++static const struct software_node ssam_node_bat_ac = { ++ .name = "ssam:01:02:01:01:01", ++ .parent = &ssam_node_root, ++}; ++ ++/* Primary battery. */ ++static const struct software_node ssam_node_bat_main = { ++ .name = "ssam:01:02:01:01:00", ++ .parent = &ssam_node_root, ++}; ++ ++/* Secondary battery (Surface Book 3). */ ++static const struct software_node ssam_node_bat_sb3base = { ++ .name = "ssam:01:02:02:01:00", ++ .parent = &ssam_node_hub_base, ++}; ++ + /* Devices for Surface Book 2. */ + static const struct software_node *ssam_node_group_sb2[] = { + &ssam_node_root, +@@ -57,6 +75,9 @@ static const struct software_node *ssam_node_group_sb2[] = { + static const struct software_node *ssam_node_group_sb3[] = { + &ssam_node_root, + &ssam_node_hub_base, ++ &ssam_node_bat_ac, ++ &ssam_node_bat_main, ++ &ssam_node_bat_sb3base, + NULL, + }; + +@@ -75,12 +96,16 @@ static const struct software_node *ssam_node_group_sl2[] = { + /* Devices for Surface Laptop 3. */ + static const struct software_node *ssam_node_group_sl3[] = { + &ssam_node_root, ++ &ssam_node_bat_ac, ++ &ssam_node_bat_main, + NULL, + }; + + /* Devices for Surface Laptop Go. */ + static const struct software_node *ssam_node_group_slg1[] = { + &ssam_node_root, ++ &ssam_node_bat_ac, ++ &ssam_node_bat_main, + NULL, + }; + +@@ -99,6 +124,8 @@ static const struct software_node *ssam_node_group_sp6[] = { + /* Devices for Surface Pro 7. */ + static const struct software_node *ssam_node_group_sp7[] = { + &ssam_node_root, ++ &ssam_node_bat_ac, ++ &ssam_node_bat_main, + NULL, + }; + +-- +2.31.1 + +From 225f6f6b8848bb6c4752510251791ce7cd7e8ed6 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 12 Feb 2021 12:54:37 +0100 +Subject: [PATCH] platform/surface: aggregator_registry: Add platform profile + device + +Add the SSAM platform profile device to the SSAM device registry. This +device is accessible under the thermal subsystem (TC=0x03) and needs to +be registered for all Surface models. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210212115439.1525216-5-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index cde279692842..33904613dd4b 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -65,9 +65,16 @@ static const struct software_node ssam_node_bat_sb3base = { + .parent = &ssam_node_hub_base, + }; + ++/* Platform profile / performance-mode device. */ ++static const struct software_node ssam_node_tmp_pprof = { ++ .name = "ssam:01:03:01:00:01", ++ .parent = &ssam_node_root, ++}; ++ + /* Devices for Surface Book 2. */ + static const struct software_node *ssam_node_group_sb2[] = { + &ssam_node_root, ++ &ssam_node_tmp_pprof, + NULL, + }; + +@@ -78,18 +85,21 @@ static const struct software_node *ssam_node_group_sb3[] = { + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_bat_sb3base, ++ &ssam_node_tmp_pprof, + NULL, + }; + + /* Devices for Surface Laptop 1. */ + static const struct software_node *ssam_node_group_sl1[] = { + &ssam_node_root, ++ &ssam_node_tmp_pprof, + NULL, + }; + + /* Devices for Surface Laptop 2. */ + static const struct software_node *ssam_node_group_sl2[] = { + &ssam_node_root, ++ &ssam_node_tmp_pprof, + NULL, + }; + +@@ -98,6 +108,7 @@ static const struct software_node *ssam_node_group_sl3[] = { + &ssam_node_root, + &ssam_node_bat_ac, + &ssam_node_bat_main, ++ &ssam_node_tmp_pprof, + NULL, + }; + +@@ -106,18 +117,21 @@ static const struct software_node *ssam_node_group_slg1[] = { + &ssam_node_root, + &ssam_node_bat_ac, + &ssam_node_bat_main, ++ &ssam_node_tmp_pprof, + NULL, + }; + + /* Devices for Surface Pro 5. */ + static const struct software_node *ssam_node_group_sp5[] = { + &ssam_node_root, ++ &ssam_node_tmp_pprof, + NULL, + }; + + /* Devices for Surface Pro 6. */ + static const struct software_node *ssam_node_group_sp6[] = { + &ssam_node_root, ++ &ssam_node_tmp_pprof, + NULL, + }; + +@@ -126,6 +140,7 @@ static const struct software_node *ssam_node_group_sp7[] = { + &ssam_node_root, + &ssam_node_bat_ac, + &ssam_node_bat_main, ++ &ssam_node_tmp_pprof, + NULL, + }; + +-- +2.31.1 + +From 982bd482ea8323218ee40a4ff42b8d7f50f30859 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 12 Feb 2021 12:54:38 +0100 +Subject: [PATCH] platform/surface: aggregator_registry: Add DTX device + +Add the detachment system (DTX) SSAM device for the Surface Book 3. This +device is accessible under the base (TC=0x11) subsystem. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210212115439.1525216-6-luzmaximilian@gmail.com +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 33904613dd4b..dc044d06828b 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -71,6 +71,12 @@ static const struct software_node ssam_node_tmp_pprof = { + .parent = &ssam_node_root, + }; + ++/* DTX / detachment-system device (Surface Book 3). */ ++static const struct software_node ssam_node_bas_dtx = { ++ .name = "ssam:01:11:01:00:00", ++ .parent = &ssam_node_root, ++}; ++ + /* Devices for Surface Book 2. */ + static const struct software_node *ssam_node_group_sb2[] = { + &ssam_node_root, +@@ -86,6 +92,7 @@ static const struct software_node *ssam_node_group_sb3[] = { + &ssam_node_bat_main, + &ssam_node_bat_sb3base, + &ssam_node_tmp_pprof, ++ &ssam_node_bas_dtx, + NULL, + }; + +-- +2.31.1 + +From 78b63edb2081fd123019b0f61a6d09db47ad4643 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 12 Feb 2021 12:54:39 +0100 +Subject: [PATCH] platform/surface: aggregator_registry: Add HID subsystem + devices + +Add HID subsystem (TC=0x15) devices. These devices need to be registered +for 7th-generation Surface models. On previous generations, these +devices are either provided as platform devices via ACPI (Surface Laptop +1 and 2) or implemented as standard USB device. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210212115439.1525216-7-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 49 +++++++++++++++++++ + 1 file changed, 49 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index dc044d06828b..caee90d135c5 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -77,6 +77,48 @@ static const struct software_node ssam_node_bas_dtx = { + .parent = &ssam_node_root, + }; + ++/* HID keyboard. */ ++static const struct software_node ssam_node_hid_main_keyboard = { ++ .name = "ssam:01:15:02:01:00", ++ .parent = &ssam_node_root, ++}; ++ ++/* HID touchpad. */ ++static const struct software_node ssam_node_hid_main_touchpad = { ++ .name = "ssam:01:15:02:03:00", ++ .parent = &ssam_node_root, ++}; ++ ++/* HID device instance 5 (unknown HID device). */ ++static const struct software_node ssam_node_hid_main_iid5 = { ++ .name = "ssam:01:15:02:05:00", ++ .parent = &ssam_node_root, ++}; ++ ++/* HID keyboard (base hub). */ ++static const struct software_node ssam_node_hid_base_keyboard = { ++ .name = "ssam:01:15:02:01:00", ++ .parent = &ssam_node_hub_base, ++}; ++ ++/* HID touchpad (base hub). */ ++static const struct software_node ssam_node_hid_base_touchpad = { ++ .name = "ssam:01:15:02:03:00", ++ .parent = &ssam_node_hub_base, ++}; ++ ++/* HID device instance 5 (unknown HID device, base hub). */ ++static const struct software_node ssam_node_hid_base_iid5 = { ++ .name = "ssam:01:15:02:05:00", ++ .parent = &ssam_node_hub_base, ++}; ++ ++/* HID device instance 6 (unknown HID device, base hub). */ ++static const struct software_node ssam_node_hid_base_iid6 = { ++ .name = "ssam:01:15:02:06:00", ++ .parent = &ssam_node_hub_base, ++}; ++ + /* Devices for Surface Book 2. */ + static const struct software_node *ssam_node_group_sb2[] = { + &ssam_node_root, +@@ -93,6 +135,10 @@ static const struct software_node *ssam_node_group_sb3[] = { + &ssam_node_bat_sb3base, + &ssam_node_tmp_pprof, + &ssam_node_bas_dtx, ++ &ssam_node_hid_base_keyboard, ++ &ssam_node_hid_base_touchpad, ++ &ssam_node_hid_base_iid5, ++ &ssam_node_hid_base_iid6, + NULL, + }; + +@@ -116,6 +162,9 @@ static const struct software_node *ssam_node_group_sl3[] = { + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_tmp_pprof, ++ &ssam_node_hid_main_keyboard, ++ &ssam_node_hid_main_touchpad, ++ &ssam_node_hid_main_iid5, + NULL, + }; + +-- +2.31.1 + +From 5b1f25eec0165e7a745cc87e4a45e3dbafb36016 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 11 Feb 2021 21:17:03 +0100 +Subject: [PATCH] platform/surface: Add platform profile driver + +Add a driver to provide platform profile support on 5th- and later +generation Microsoft Surface devices with a Surface System Aggregator +Module. On those devices, the platform profile can be used to influence +cooling behavior and power consumption. + +For example, the default 'quiet' profile limits fan noise and in turn +sacrifices performance of the discrete GPU found on Surface Books. Its +full performance can only be unlocked on the 'performance' profile. + +Signed-off-by: Maximilian Luz +Reviewed-by: Hans de Goede +Link: https://lore.kernel.org/r/20210211201703.658240-5-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + MAINTAINERS | 6 + + drivers/platform/surface/Kconfig | 22 ++ + drivers/platform/surface/Makefile | 1 + + .../surface/surface_platform_profile.c | 190 ++++++++++++++++++ + 4 files changed, 219 insertions(+) + create mode 100644 drivers/platform/surface/surface_platform_profile.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index f6c524630575..fce5cdcefc0b 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -11889,6 +11889,12 @@ L: platform-driver-x86@vger.kernel.org + S: Maintained + F: drivers/platform/surface/surface_hotplug.c + ++MICROSOFT SURFACE PLATFORM PROFILE DRIVER ++M: Maximilian Luz ++L: platform-driver-x86@vger.kernel.org ++S: Maintained ++F: drivers/platform/surface/surface_platform_profile.c ++ + MICROSOFT SURFACE PRO 3 BUTTON DRIVER + M: Chen Yu + L: platform-driver-x86@vger.kernel.org +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index c51c55204b5f..6fb304da845f 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -139,6 +139,28 @@ config SURFACE_HOTPLUG + Select M or Y here, if you want to (fully) support hot-plugging of + dGPU devices on the Surface Book 2 and/or 3 during D3cold. + ++config SURFACE_PLATFORM_PROFILE ++ tristate "Surface Platform Profile Driver" ++ depends on SURFACE_AGGREGATOR_REGISTRY ++ select ACPI_PLATFORM_PROFILE ++ help ++ Provides support for the ACPI platform profile on 5th- and later ++ generation Microsoft Surface devices. ++ ++ More specifically, this driver provides ACPI platform profile support ++ on Microsoft Surface devices with a Surface System Aggregator Module ++ (SSAM) connected via the Surface Serial Hub (SSH / SAM-over-SSH). In ++ other words, this driver provides platform profile support on the ++ Surface Pro 5, Surface Book 2, Surface Laptop, Surface Laptop Go and ++ later. On those devices, the platform profile can significantly ++ influence cooling behavior, e.g. setting it to 'quiet' (default) or ++ 'low-power' can significantly limit performance of the discrete GPU on ++ Surface Books, while in turn leading to lower power consumption and/or ++ less fan noise. ++ ++ Select M or Y here, if you want to include ACPI platform profile ++ support on the above mentioned devices. ++ + config SURFACE_PRO3_BUTTON + tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet" + depends on INPUT +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index ed12676f06e6..f7187bae1729 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -14,4 +14,5 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o ++obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o + obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o +diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c +new file mode 100644 +index 000000000000..0081b01a5b0f +--- /dev/null ++++ b/drivers/platform/surface/surface_platform_profile.c +@@ -0,0 +1,190 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Surface Platform Profile / Performance Mode driver for Surface System ++ * Aggregator Module (thermal subsystem). ++ * ++ * Copyright (C) 2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++enum ssam_tmp_profile { ++ SSAM_TMP_PROFILE_NORMAL = 1, ++ SSAM_TMP_PROFILE_BATTERY_SAVER = 2, ++ SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3, ++ SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4, ++}; ++ ++struct ssam_tmp_profile_info { ++ __le32 profile; ++ __le16 unknown1; ++ __le16 unknown2; ++} __packed; ++ ++struct ssam_tmp_profile_device { ++ struct ssam_device *sdev; ++ struct platform_profile_handler handler; ++}; ++ ++static SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, { ++ .target_category = SSAM_SSH_TC_TMP, ++ .command_id = 0x02, ++}); ++ ++static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, { ++ .target_category = SSAM_SSH_TC_TMP, ++ .command_id = 0x03, ++}); ++ ++static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p) ++{ ++ struct ssam_tmp_profile_info info; ++ int status; ++ ++ status = ssam_retry(__ssam_tmp_profile_get, sdev, &info); ++ if (status < 0) ++ return status; ++ ++ *p = le32_to_cpu(info.profile); ++ return 0; ++} ++ ++static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p) ++{ ++ __le32 profile_le = cpu_to_le32(p); ++ ++ return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le); ++} ++ ++static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p) ++{ ++ switch (p) { ++ case SSAM_TMP_PROFILE_NORMAL: ++ return PLATFORM_PROFILE_BALANCED; ++ ++ case SSAM_TMP_PROFILE_BATTERY_SAVER: ++ return PLATFORM_PROFILE_LOW_POWER; ++ ++ case SSAM_TMP_PROFILE_BETTER_PERFORMANCE: ++ return PLATFORM_PROFILE_BALANCED_PERFORMANCE; ++ ++ case SSAM_TMP_PROFILE_BEST_PERFORMANCE: ++ return PLATFORM_PROFILE_PERFORMANCE; ++ ++ default: ++ dev_err(&sdev->dev, "invalid performance profile: %d", p); ++ return -EINVAL; ++ } ++} ++ ++static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p) ++{ ++ switch (p) { ++ case PLATFORM_PROFILE_LOW_POWER: ++ return SSAM_TMP_PROFILE_BATTERY_SAVER; ++ ++ case PLATFORM_PROFILE_BALANCED: ++ return SSAM_TMP_PROFILE_NORMAL; ++ ++ case PLATFORM_PROFILE_BALANCED_PERFORMANCE: ++ return SSAM_TMP_PROFILE_BETTER_PERFORMANCE; ++ ++ case PLATFORM_PROFILE_PERFORMANCE: ++ return SSAM_TMP_PROFILE_BEST_PERFORMANCE; ++ ++ default: ++ /* This should have already been caught by platform_profile_store(). */ ++ WARN(true, "unsupported platform profile"); ++ return -EOPNOTSUPP; ++ } ++} ++ ++static int ssam_platform_profile_get(struct platform_profile_handler *pprof, ++ enum platform_profile_option *profile) ++{ ++ struct ssam_tmp_profile_device *tpd; ++ enum ssam_tmp_profile tp; ++ int status; ++ ++ tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); ++ ++ status = ssam_tmp_profile_get(tpd->sdev, &tp); ++ if (status) ++ return status; ++ ++ status = convert_ssam_to_profile(tpd->sdev, tp); ++ if (status < 0) ++ return status; ++ ++ *profile = status; ++ return 0; ++} ++ ++static int ssam_platform_profile_set(struct platform_profile_handler *pprof, ++ enum platform_profile_option profile) ++{ ++ struct ssam_tmp_profile_device *tpd; ++ int tp; ++ ++ tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); ++ ++ tp = convert_profile_to_ssam(tpd->sdev, profile); ++ if (tp < 0) ++ return tp; ++ ++ return ssam_tmp_profile_set(tpd->sdev, tp); ++} ++ ++static int surface_platform_profile_probe(struct ssam_device *sdev) ++{ ++ struct ssam_tmp_profile_device *tpd; ++ ++ tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL); ++ if (!tpd) ++ return -ENOMEM; ++ ++ tpd->sdev = sdev; ++ ++ tpd->handler.profile_get = ssam_platform_profile_get; ++ tpd->handler.profile_set = ssam_platform_profile_set; ++ ++ set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices); ++ set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices); ++ set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices); ++ set_bit(PLATFORM_PROFILE_PERFORMANCE, tpd->handler.choices); ++ ++ platform_profile_register(&tpd->handler); ++ return 0; ++} ++ ++static void surface_platform_profile_remove(struct ssam_device *sdev) ++{ ++ platform_profile_remove(); ++} ++ ++static const struct ssam_device_id ssam_platform_profile_match[] = { ++ { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match); ++ ++static struct ssam_device_driver surface_platform_profile = { ++ .probe = surface_platform_profile_probe, ++ .remove = surface_platform_profile_remove, ++ .match_table = ssam_platform_profile_match, ++ .driver = { ++ .name = "surface_platform_profile", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(surface_platform_profile); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.31.1 + +From 3587540017845f99f6d311bb59c6c26824bf9237 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 4 Mar 2021 20:05:24 +0100 +Subject: [PATCH] platform/surface: aggregator: Make SSAM_DEFINE_SYNC_REQUEST_x + define static functions + +The SSAM_DEFINE_SYNC_REQUEST_x() macros are intended to reduce +boiler-plate code for SSAM request definitions by defining a wrapper +function for the specified request. The client device variants of those +macros, i.e. SSAM_DEFINE_SYNC_REQUEST_CL_x() in particular rely on the +multi-device (MD) variants, e.g.: + + #define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \ + SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \ + int name(struct ssam_device *sdev, rtype *ret) \ + { \ + return __raw_##name(sdev->ctrl, sdev->uid.target, \ + sdev->uid.instance, ret); \ + } + +This now creates the problem that it is not possible to declare the +generated functions static via + + static SSAM_DEFINE_SYNC_REQUEST_CL_R(...) + +as this will only apply to the function defined by the multi-device +macro, i.e. SSAM_DEFINE_SYNC_REQUEST_MD_R(). Thus compiling with +`-Wmissing-prototypes' rightfully complains that there is a 'static' +keyword missing. + +To solve this, make all SSAM_DEFINE_SYNC_REQUEST_x() macros define +static functions. Non-client-device macros are also changed for +consistency. In general, we expect those functions to be only used +locally in the respective drivers for the corresponding interfaces, so +having to define a wrapper function to be able to export this should be +the odd case out. + +Reported-by: kernel test robot +Fixes: b78b4982d763 ("platform/surface: Add platform profile driver") +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210304190524.1172197-1-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../driver-api/surface_aggregator/client.rst | 4 +- + .../platform/surface/aggregator/controller.c | 10 +-- + .../surface/surface_aggregator_registry.c | 2 +- + .../surface/surface_platform_profile.c | 4 +- + include/linux/surface_aggregator/controller.h | 74 +++++++++---------- + include/linux/surface_aggregator/device.h | 31 ++++---- + 6 files changed, 63 insertions(+), 62 deletions(-) + +diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst +index 26d13085a117..e519d374c378 100644 +--- a/Documentation/driver-api/surface_aggregator/client.rst ++++ b/Documentation/driver-api/surface_aggregator/client.rst +@@ -248,7 +248,7 @@ This example defines a function + + .. code-block:: c + +- int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg); ++ static int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg); + + executing the specified request, with the controller passed in when calling + said function. In this example, the argument is provided via the ``arg`` +@@ -296,7 +296,7 @@ This invocation of the macro defines a function + + .. code-block:: c + +- int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret); ++ static int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret); + + executing the specified request, using the device IDs and controller given + in the client device. The full list of such macros for client devices is: +diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c +index 5bcb59ed579d..aa6f37b4f46e 100644 +--- a/drivers/platform/surface/aggregator/controller.c ++++ b/drivers/platform/surface/aggregator/controller.c +@@ -1750,35 +1750,35 @@ EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer); + + /* -- Internal SAM requests. ------------------------------------------------ */ + +-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { ++SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x13, + .instance_id = 0x00, + }); + +-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { ++SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x15, + .instance_id = 0x00, + }); + +-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { ++SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x16, + .instance_id = 0x00, + }); + +-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { ++SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x33, + .instance_id = 0x00, + }); + +-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { ++SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x34, +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index caee90d135c5..cdb4a95af3e8 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -302,7 +302,7 @@ struct ssam_base_hub { + struct ssam_event_notifier notif; + }; + +-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { ++SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { + .target_category = SSAM_SSH_TC_BAS, + .target_id = 0x01, + .command_id = 0x0d, +diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c +index 0081b01a5b0f..6373d3b5eb7f 100644 +--- a/drivers/platform/surface/surface_platform_profile.c ++++ b/drivers/platform/surface/surface_platform_profile.c +@@ -32,12 +32,12 @@ struct ssam_tmp_profile_device { + struct platform_profile_handler handler; + }; + +-static SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, { ++SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, { + .target_category = SSAM_SSH_TC_TMP, + .command_id = 0x02, + }); + +-static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, { ++SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, { + .target_category = SSAM_SSH_TC_TMP, + .command_id = 0x03, + }); +diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h +index f4b1ba887384..0806796eabcb 100644 +--- a/include/linux/surface_aggregator/controller.h ++++ b/include/linux/surface_aggregator/controller.h +@@ -344,16 +344,16 @@ struct ssam_request_spec_md { + * request has been fully completed. The required transport buffer will be + * allocated on the stack. + * +- * The generated function is defined as ``int name(struct ssam_controller +- * *ctrl)``, returning the status of the request, which is zero on success and +- * negative on failure. The ``ctrl`` parameter is the controller via which the +- * request is being sent. ++ * The generated function is defined as ``static int name(struct ++ * ssam_controller *ctrl)``, returning the status of the request, which is ++ * zero on success and negative on failure. The ``ctrl`` parameter is the ++ * controller via which the request is being sent. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ + #define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...) \ +- int name(struct ssam_controller *ctrl) \ ++ static int name(struct ssam_controller *ctrl) \ + { \ + struct ssam_request_spec s = (struct ssam_request_spec)spec; \ + struct ssam_request rqst; \ +@@ -383,17 +383,17 @@ struct ssam_request_spec_md { + * returning once the request has been fully completed. The required transport + * buffer will be allocated on the stack. + * +- * The generated function is defined as ``int name(struct ssam_controller +- * *ctrl, const atype *arg)``, returning the status of the request, which is +- * zero on success and negative on failure. The ``ctrl`` parameter is the +- * controller via which the request is sent. The request argument is specified +- * via the ``arg`` pointer. ++ * The generated function is defined as ``static int name(struct ++ * ssam_controller *ctrl, const atype *arg)``, returning the status of the ++ * request, which is zero on success and negative on failure. The ``ctrl`` ++ * parameter is the controller via which the request is sent. The request ++ * argument is specified via the ``arg`` pointer. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ + #define SSAM_DEFINE_SYNC_REQUEST_W(name, atype, spec...) \ +- int name(struct ssam_controller *ctrl, const atype *arg) \ ++ static int name(struct ssam_controller *ctrl, const atype *arg) \ + { \ + struct ssam_request_spec s = (struct ssam_request_spec)spec; \ + struct ssam_request rqst; \ +@@ -424,17 +424,17 @@ struct ssam_request_spec_md { + * request itself, returning once the request has been fully completed. The + * required transport buffer will be allocated on the stack. + * +- * The generated function is defined as ``int name(struct ssam_controller +- * *ctrl, rtype *ret)``, returning the status of the request, which is zero on +- * success and negative on failure. The ``ctrl`` parameter is the controller +- * via which the request is sent. The request's return value is written to the +- * memory pointed to by the ``ret`` parameter. ++ * The generated function is defined as ``static int name(struct ++ * ssam_controller *ctrl, rtype *ret)``, returning the status of the request, ++ * which is zero on success and negative on failure. The ``ctrl`` parameter is ++ * the controller via which the request is sent. The request's return value is ++ * written to the memory pointed to by the ``ret`` parameter. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ + #define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...) \ +- int name(struct ssam_controller *ctrl, rtype *ret) \ ++ static int name(struct ssam_controller *ctrl, rtype *ret) \ + { \ + struct ssam_request_spec s = (struct ssam_request_spec)spec; \ + struct ssam_request rqst; \ +@@ -483,17 +483,17 @@ struct ssam_request_spec_md { + * returning once the request has been fully completed. The required transport + * buffer will be allocated on the stack. + * +- * The generated function is defined as ``int name(struct ssam_controller +- * *ctrl, u8 tid, u8 iid)``, returning the status of the request, which is +- * zero on success and negative on failure. The ``ctrl`` parameter is the +- * controller via which the request is sent, ``tid`` the target ID for the +- * request, and ``iid`` the instance ID. ++ * The generated function is defined as ``static int name(struct ++ * ssam_controller *ctrl, u8 tid, u8 iid)``, returning the status of the ++ * request, which is zero on success and negative on failure. The ``ctrl`` ++ * parameter is the controller via which the request is sent, ``tid`` the ++ * target ID for the request, and ``iid`` the instance ID. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ + #define SSAM_DEFINE_SYNC_REQUEST_MD_N(name, spec...) \ +- int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \ ++ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \ + { \ + struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ + struct ssam_request rqst; \ +@@ -524,18 +524,18 @@ struct ssam_request_spec_md { + * the request itself, returning once the request has been fully completed. + * The required transport buffer will be allocated on the stack. + * +- * The generated function is defined as ``int name(struct ssam_controller +- * *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the status of the +- * request, which is zero on success and negative on failure. The ``ctrl`` +- * parameter is the controller via which the request is sent, ``tid`` the +- * target ID for the request, and ``iid`` the instance ID. The request argument +- * is specified via the ``arg`` pointer. ++ * The generated function is defined as ``static int name(struct ++ * ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the ++ * status of the request, which is zero on success and negative on failure. ++ * The ``ctrl`` parameter is the controller via which the request is sent, ++ * ``tid`` the target ID for the request, and ``iid`` the instance ID. The ++ * request argument is specified via the ``arg`` pointer. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ + #define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, atype, spec...) \ +- int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)\ ++ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg) \ + { \ + struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ + struct ssam_request rqst; \ +@@ -567,18 +567,18 @@ struct ssam_request_spec_md { + * execution of the request itself, returning once the request has been fully + * completed. The required transport buffer will be allocated on the stack. + * +- * The generated function is defined as ``int name(struct ssam_controller +- * *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status of the request, +- * which is zero on success and negative on failure. The ``ctrl`` parameter is +- * the controller via which the request is sent, ``tid`` the target ID for the +- * request, and ``iid`` the instance ID. The request's return value is written +- * to the memory pointed to by the ``ret`` parameter. ++ * The generated function is defined as ``static int name(struct ++ * ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status ++ * of the request, which is zero on success and negative on failure. The ++ * ``ctrl`` parameter is the controller via which the request is sent, ``tid`` ++ * the target ID for the request, and ``iid`` the instance ID. The request's ++ * return value is written to the memory pointed to by the ``ret`` parameter. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ + #define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...) \ +- int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \ ++ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \ + { \ + struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ + struct ssam_request rqst; \ +diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h +index 02f3e06c0a60..4441ad667c3f 100644 +--- a/include/linux/surface_aggregator/device.h ++++ b/include/linux/surface_aggregator/device.h +@@ -336,17 +336,18 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); + * request has been fully completed. The required transport buffer will be + * allocated on the stack. + * +- * The generated function is defined as ``int name(struct ssam_device *sdev)``, +- * returning the status of the request, which is zero on success and negative +- * on failure. The ``sdev`` parameter specifies both the target device of the +- * request and by association the controller via which the request is sent. ++ * The generated function is defined as ``static int name(struct ssam_device ++ * *sdev)``, returning the status of the request, which is zero on success and ++ * negative on failure. The ``sdev`` parameter specifies both the target ++ * device of the request and by association the controller via which the ++ * request is sent. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ + #define SSAM_DEFINE_SYNC_REQUEST_CL_N(name, spec...) \ + SSAM_DEFINE_SYNC_REQUEST_MD_N(__raw_##name, spec) \ +- int name(struct ssam_device *sdev) \ ++ static int name(struct ssam_device *sdev) \ + { \ + return __raw_##name(sdev->ctrl, sdev->uid.target, \ + sdev->uid.instance); \ +@@ -368,19 +369,19 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); + * itself, returning once the request has been fully completed. The required + * transport buffer will be allocated on the stack. + * +- * The generated function is defined as ``int name(struct ssam_device *sdev, +- * const atype *arg)``, returning the status of the request, which is zero on +- * success and negative on failure. The ``sdev`` parameter specifies both the +- * target device of the request and by association the controller via which +- * the request is sent. The request's argument is specified via the ``arg`` +- * pointer. ++ * The generated function is defined as ``static int name(struct ssam_device ++ * *sdev, const atype *arg)``, returning the status of the request, which is ++ * zero on success and negative on failure. The ``sdev`` parameter specifies ++ * both the target device of the request and by association the controller via ++ * which the request is sent. The request's argument is specified via the ++ * ``arg`` pointer. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ + #define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, atype, spec...) \ + SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, atype, spec) \ +- int name(struct ssam_device *sdev, const atype *arg) \ ++ static int name(struct ssam_device *sdev, const atype *arg) \ + { \ + return __raw_##name(sdev->ctrl, sdev->uid.target, \ + sdev->uid.instance, arg); \ +@@ -402,8 +403,8 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); + * itself, returning once the request has been fully completed. The required + * transport buffer will be allocated on the stack. + * +- * The generated function is defined as ``int name(struct ssam_device *sdev, +- * rtype *ret)``, returning the status of the request, which is zero on ++ * The generated function is defined as ``static int name(struct ssam_device ++ * *sdev, rtype *ret)``, returning the status of the request, which is zero on + * success and negative on failure. The ``sdev`` parameter specifies both the + * target device of the request and by association the controller via which + * the request is sent. The request's return value is written to the memory +@@ -414,7 +415,7 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); + */ + #define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \ + SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \ +- int name(struct ssam_device *sdev, rtype *ret) \ ++ static int name(struct ssam_device *sdev, rtype *ret) \ + { \ + return __raw_##name(sdev->ctrl, sdev->uid.target, \ + sdev->uid.instance, ret); \ +-- +2.31.1 + +From 3e6957d1939ac4fd7a32df8a3805dcd32243875d Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Mon, 8 Mar 2021 19:48:17 +0100 +Subject: [PATCH] platform/surface: Add DTX driver + +The Microsoft Surface Book series devices consist of a so-called +clipboard part (containing the CPU, touchscreen, and primary battery) +and a base part (containing keyboard, secondary battery, and optional +discrete GPU). These parts can be separated, i.e. the clipboard can be +detached and used as tablet. + +This detachment process is initiated by pressing a button. On the +Surface Book 2 and 3 (targeted with this commit), the Surface Aggregator +Module (i.e. the embedded controller on those devices) attempts to send +a notification to any listening client driver and waits for further +instructions (i.e. whether the detachment process should continue or be +aborted). If it does not receive a response in a certain time-frame, the +detachment process (by default) continues and the clipboard can be +physically separated. In other words, (by default and) without a driver, +the detachment process takes about 10 seconds to complete. + +This commit introduces a driver for this detachment system (called DTX). +This driver allows a user-space daemon to control and influence the +detachment behavior. Specifically, it forwards any detachment requests +to user-space, allows user-space to make such requests itself, and +allows handling of those requests. Requests can be handled by either +aborting, continuing/allowing, or delaying (i.e. resetting the timeout +via a heartbeat commend). The user-space API is implemented via the +/dev/surface/dtx miscdevice. + +In addition, user-space can change the default behavior on timeout from +allowing detachment to disallowing it, which is useful if the (optional) +discrete GPU is in use. + +Furthermore, this driver allows user-space to receive notifications +about the state of the base, specifically when it is physically removed +(as opposed to detachment requested), in what manner it is connected +(i.e. in reverse-/tent-/studio- or laptop-mode), and what type of base +is connected. Based on this information, the driver also provides a +simple tablet-mode switch (aliasing all modes without keyboard access, +i.e. tablet-mode and studio-mode to its reported tablet-mode). + +An implementation of such a user-space daemon, allowing configuration of +detachment behavior via scripts (e.g. safely unmounting USB devices +connected to the base before continuing) can be found at [1]. + +[1]: https://github.com/linux-surface/surface-dtx-daemon + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210308184819.437438-2-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../userspace-api/ioctl/ioctl-number.rst | 2 + + MAINTAINERS | 7 + + drivers/platform/surface/Kconfig | 16 + + drivers/platform/surface/Makefile | 1 + + drivers/platform/surface/surface_dtx.c | 1201 +++++++++++++++++ + include/uapi/linux/surface_aggregator/dtx.h | 146 ++ + 6 files changed, 1373 insertions(+) + create mode 100644 drivers/platform/surface/surface_dtx.c + create mode 100644 include/uapi/linux/surface_aggregator/dtx.h + +diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst +index 599bd4493944..1c28b8ef6677 100644 +--- a/Documentation/userspace-api/ioctl/ioctl-number.rst ++++ b/Documentation/userspace-api/ioctl/ioctl-number.rst +@@ -327,6 +327,8 @@ Code Seq# Include File Comments + 0xA4 00-1F uapi/asm/sgx.h + 0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator + ++0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver ++ + 0xAA 00-3F linux/uapi/linux/userfaultfd.h + 0xAB 00-1F linux/nbd.h + 0xAC 00-1F linux/raw.h +diff --git a/MAINTAINERS b/MAINTAINERS +index fce5cdcefc0b..3917e7363520 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -11868,6 +11868,13 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch] + F: include/linux/cciss*.h + F: include/uapi/linux/cciss*.h + ++MICROSOFT SURFACE DTX DRIVER ++M: Maximilian Luz ++L: platform-driver-x86@vger.kernel.org ++S: Maintained ++F: drivers/platform/surface/surface_dtx.c ++F: include/uapi/linux/surface_aggregator/dtx.h ++ + MICROSOFT SURFACE GPE LID SUPPORT DRIVER + M: Maximilian Luz + L: platform-driver-x86@vger.kernel.org +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index 6fb304da845f..41d67bb250fd 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -111,6 +111,22 @@ config SURFACE_BOOK1_DGPU_SWITCH + This driver provides a sysfs switch to set the power-state of the + discrete GPU found on the Microsoft Surface Book 1. + ++config SURFACE_DTX ++ tristate "Surface DTX (Detachment System) Driver" ++ depends on SURFACE_AGGREGATOR ++ depends on INPUT ++ help ++ Driver for the Surface Book clipboard detachment system (DTX). ++ ++ On the Surface Book series devices, the display part containing the ++ CPU (called the clipboard) can be detached from the base (containing a ++ battery, the keyboard, and, optionally, a discrete GPU) by (if ++ necessary) unlocking and opening the latch connecting both parts. ++ ++ This driver provides a user-space interface that can influence the ++ behavior of this process, which includes the option to abort it in ++ case the base is still in use or speed it up in case it is not. ++ + config SURFACE_GPE + tristate "Surface GPE/Lid Support Driver" + depends on DMI +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index f7187bae1729..0cc63440328d 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ + obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o ++obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o + obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o +diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c +new file mode 100644 +index 000000000000..1301fab0ea14 +--- /dev/null ++++ b/drivers/platform/surface/surface_dtx.c +@@ -0,0 +1,1201 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Surface Book (gen. 2 and later) detachment system (DTX) driver. ++ * ++ * Provides a user-space interface to properly handle clipboard/tablet ++ * (containing screen and processor) detachment from the base of the device ++ * (containing the keyboard and optionally a discrete GPU). Allows to ++ * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in ++ * use), or request detachment via user-space. ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++ ++/* -- SSAM interface. ------------------------------------------------------- */ ++ ++enum sam_event_cid_bas { ++ SAM_EVENT_CID_DTX_CONNECTION = 0x0c, ++ SAM_EVENT_CID_DTX_REQUEST = 0x0e, ++ SAM_EVENT_CID_DTX_CANCEL = 0x0f, ++ SAM_EVENT_CID_DTX_LATCH_STATUS = 0x11, ++}; ++ ++enum ssam_bas_base_state { ++ SSAM_BAS_BASE_STATE_DETACH_SUCCESS = 0x00, ++ SSAM_BAS_BASE_STATE_ATTACHED = 0x01, ++ SSAM_BAS_BASE_STATE_NOT_FEASIBLE = 0x02, ++}; ++ ++enum ssam_bas_latch_status { ++ SSAM_BAS_LATCH_STATUS_CLOSED = 0x00, ++ SSAM_BAS_LATCH_STATUS_OPENED = 0x01, ++ SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN = 0x02, ++ SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN = 0x03, ++ SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE = 0x04, ++}; ++ ++enum ssam_bas_cancel_reason { ++ SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE = 0x00, /* Low battery. */ ++ SSAM_BAS_CANCEL_REASON_TIMEOUT = 0x02, ++ SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN = 0x03, ++ SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN = 0x04, ++ SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE = 0x05, ++}; ++ ++struct ssam_bas_base_info { ++ u8 state; ++ u8 base_id; ++} __packed; ++ ++static_assert(sizeof(struct ssam_bas_base_info) == 2); ++ ++SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x06, ++ .instance_id = 0x00, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x07, ++ .instance_id = 0x00, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x08, ++ .instance_id = 0x00, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x09, ++ .instance_id = 0x00, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x0a, ++ .instance_id = 0x00, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x0b, ++ .instance_id = 0x00, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x0c, ++ .instance_id = 0x00, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x0d, ++ .instance_id = 0x00, ++}); ++ ++SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x11, ++ .instance_id = 0x00, ++}); ++ ++ ++/* -- Main structures. ------------------------------------------------------ */ ++ ++enum sdtx_device_state { ++ SDTX_DEVICE_SHUTDOWN_BIT = BIT(0), ++ SDTX_DEVICE_DIRTY_BASE_BIT = BIT(1), ++ SDTX_DEVICE_DIRTY_MODE_BIT = BIT(2), ++ SDTX_DEVICE_DIRTY_LATCH_BIT = BIT(3), ++}; ++ ++struct sdtx_device { ++ struct kref kref; ++ struct rw_semaphore lock; /* Guards device and controller reference. */ ++ ++ struct device *dev; ++ struct ssam_controller *ctrl; ++ unsigned long flags; ++ ++ struct miscdevice mdev; ++ wait_queue_head_t waitq; ++ struct mutex write_lock; /* Guards order of events/notifications. */ ++ struct rw_semaphore client_lock; /* Guards client list. */ ++ struct list_head client_list; ++ ++ struct delayed_work state_work; ++ struct { ++ struct ssam_bas_base_info base; ++ u8 device_mode; ++ u8 latch_status; ++ } state; ++ ++ struct delayed_work mode_work; ++ struct input_dev *mode_switch; ++ ++ struct ssam_event_notifier notif; ++}; ++ ++enum sdtx_client_state { ++ SDTX_CLIENT_EVENTS_ENABLED_BIT = BIT(0), ++}; ++ ++struct sdtx_client { ++ struct sdtx_device *ddev; ++ struct list_head node; ++ unsigned long flags; ++ ++ struct fasync_struct *fasync; ++ ++ struct mutex read_lock; /* Guards FIFO buffer read access. */ ++ DECLARE_KFIFO(buffer, u8, 512); ++}; ++ ++static void __sdtx_device_release(struct kref *kref) ++{ ++ struct sdtx_device *ddev = container_of(kref, struct sdtx_device, kref); ++ ++ mutex_destroy(&ddev->write_lock); ++ kfree(ddev); ++} ++ ++static struct sdtx_device *sdtx_device_get(struct sdtx_device *ddev) ++{ ++ if (ddev) ++ kref_get(&ddev->kref); ++ ++ return ddev; ++} ++ ++static void sdtx_device_put(struct sdtx_device *ddev) ++{ ++ if (ddev) ++ kref_put(&ddev->kref, __sdtx_device_release); ++} ++ ++ ++/* -- Firmware value translations. ------------------------------------------ */ ++ ++static u16 sdtx_translate_base_state(struct sdtx_device *ddev, u8 state) ++{ ++ switch (state) { ++ case SSAM_BAS_BASE_STATE_ATTACHED: ++ return SDTX_BASE_ATTACHED; ++ ++ case SSAM_BAS_BASE_STATE_DETACH_SUCCESS: ++ return SDTX_BASE_DETACHED; ++ ++ case SSAM_BAS_BASE_STATE_NOT_FEASIBLE: ++ return SDTX_DETACH_NOT_FEASIBLE; ++ ++ default: ++ dev_err(ddev->dev, "unknown base state: %#04x\n", state); ++ return SDTX_UNKNOWN(state); ++ } ++} ++ ++static u16 sdtx_translate_latch_status(struct sdtx_device *ddev, u8 status) ++{ ++ switch (status) { ++ case SSAM_BAS_LATCH_STATUS_CLOSED: ++ return SDTX_LATCH_CLOSED; ++ ++ case SSAM_BAS_LATCH_STATUS_OPENED: ++ return SDTX_LATCH_OPENED; ++ ++ case SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN: ++ return SDTX_ERR_FAILED_TO_OPEN; ++ ++ case SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN: ++ return SDTX_ERR_FAILED_TO_REMAIN_OPEN; ++ ++ case SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE: ++ return SDTX_ERR_FAILED_TO_CLOSE; ++ ++ default: ++ dev_err(ddev->dev, "unknown latch status: %#04x\n", status); ++ return SDTX_UNKNOWN(status); ++ } ++} ++ ++static u16 sdtx_translate_cancel_reason(struct sdtx_device *ddev, u8 reason) ++{ ++ switch (reason) { ++ case SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE: ++ return SDTX_DETACH_NOT_FEASIBLE; ++ ++ case SSAM_BAS_CANCEL_REASON_TIMEOUT: ++ return SDTX_DETACH_TIMEDOUT; ++ ++ case SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN: ++ return SDTX_ERR_FAILED_TO_OPEN; ++ ++ case SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN: ++ return SDTX_ERR_FAILED_TO_REMAIN_OPEN; ++ ++ case SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE: ++ return SDTX_ERR_FAILED_TO_CLOSE; ++ ++ default: ++ dev_err(ddev->dev, "unknown cancel reason: %#04x\n", reason); ++ return SDTX_UNKNOWN(reason); ++ } ++} ++ ++ ++/* -- IOCTLs. --------------------------------------------------------------- */ ++ ++static int sdtx_ioctl_get_base_info(struct sdtx_device *ddev, ++ struct sdtx_base_info __user *buf) ++{ ++ struct ssam_bas_base_info raw; ++ struct sdtx_base_info info; ++ int status; ++ ++ lockdep_assert_held_read(&ddev->lock); ++ ++ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &raw); ++ if (status < 0) ++ return status; ++ ++ info.state = sdtx_translate_base_state(ddev, raw.state); ++ info.base_id = SDTX_BASE_TYPE_SSH(raw.base_id); ++ ++ if (copy_to_user(buf, &info, sizeof(info))) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static int sdtx_ioctl_get_device_mode(struct sdtx_device *ddev, u16 __user *buf) ++{ ++ u8 mode; ++ int status; ++ ++ lockdep_assert_held_read(&ddev->lock); ++ ++ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); ++ if (status < 0) ++ return status; ++ ++ return put_user(mode, buf); ++} ++ ++static int sdtx_ioctl_get_latch_status(struct sdtx_device *ddev, u16 __user *buf) ++{ ++ u8 latch; ++ int status; ++ ++ lockdep_assert_held_read(&ddev->lock); ++ ++ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); ++ if (status < 0) ++ return status; ++ ++ return put_user(sdtx_translate_latch_status(ddev, latch), buf); ++} ++ ++static long __surface_dtx_ioctl(struct sdtx_client *client, unsigned int cmd, unsigned long arg) ++{ ++ struct sdtx_device *ddev = client->ddev; ++ ++ lockdep_assert_held_read(&ddev->lock); ++ ++ switch (cmd) { ++ case SDTX_IOCTL_EVENTS_ENABLE: ++ set_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); ++ return 0; ++ ++ case SDTX_IOCTL_EVENTS_DISABLE: ++ clear_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); ++ return 0; ++ ++ case SDTX_IOCTL_LATCH_LOCK: ++ return ssam_retry(ssam_bas_latch_lock, ddev->ctrl); ++ ++ case SDTX_IOCTL_LATCH_UNLOCK: ++ return ssam_retry(ssam_bas_latch_unlock, ddev->ctrl); ++ ++ case SDTX_IOCTL_LATCH_REQUEST: ++ return ssam_retry(ssam_bas_latch_request, ddev->ctrl); ++ ++ case SDTX_IOCTL_LATCH_CONFIRM: ++ return ssam_retry(ssam_bas_latch_confirm, ddev->ctrl); ++ ++ case SDTX_IOCTL_LATCH_HEARTBEAT: ++ return ssam_retry(ssam_bas_latch_heartbeat, ddev->ctrl); ++ ++ case SDTX_IOCTL_LATCH_CANCEL: ++ return ssam_retry(ssam_bas_latch_cancel, ddev->ctrl); ++ ++ case SDTX_IOCTL_GET_BASE_INFO: ++ return sdtx_ioctl_get_base_info(ddev, (struct sdtx_base_info __user *)arg); ++ ++ case SDTX_IOCTL_GET_DEVICE_MODE: ++ return sdtx_ioctl_get_device_mode(ddev, (u16 __user *)arg); ++ ++ case SDTX_IOCTL_GET_LATCH_STATUS: ++ return sdtx_ioctl_get_latch_status(ddev, (u16 __user *)arg); ++ ++ default: ++ return -EINVAL; ++ } ++} ++ ++static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ struct sdtx_client *client = file->private_data; ++ long status; ++ ++ if (down_read_killable(&client->ddev->lock)) ++ return -ERESTARTSYS; ++ ++ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) { ++ up_read(&client->ddev->lock); ++ return -ENODEV; ++ } ++ ++ status = __surface_dtx_ioctl(client, cmd, arg); ++ ++ up_read(&client->ddev->lock); ++ return status; ++} ++ ++ ++/* -- File operations. ------------------------------------------------------ */ ++ ++static int surface_dtx_open(struct inode *inode, struct file *file) ++{ ++ struct sdtx_device *ddev = container_of(file->private_data, struct sdtx_device, mdev); ++ struct sdtx_client *client; ++ ++ /* Initialize client. */ ++ client = kzalloc(sizeof(*client), GFP_KERNEL); ++ if (!client) ++ return -ENOMEM; ++ ++ client->ddev = sdtx_device_get(ddev); ++ ++ INIT_LIST_HEAD(&client->node); ++ ++ mutex_init(&client->read_lock); ++ INIT_KFIFO(client->buffer); ++ ++ file->private_data = client; ++ ++ /* Attach client. */ ++ down_write(&ddev->client_lock); ++ ++ /* ++ * Do not add a new client if the device has been shut down. Note that ++ * it's enough to hold the client_lock here as, during shutdown, we ++ * only acquire that lock and remove clients after marking the device ++ * as shut down. ++ */ ++ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { ++ up_write(&ddev->client_lock); ++ sdtx_device_put(client->ddev); ++ kfree(client); ++ return -ENODEV; ++ } ++ ++ list_add_tail(&client->node, &ddev->client_list); ++ up_write(&ddev->client_lock); ++ ++ stream_open(inode, file); ++ return 0; ++} ++ ++static int surface_dtx_release(struct inode *inode, struct file *file) ++{ ++ struct sdtx_client *client = file->private_data; ++ ++ /* Detach client. */ ++ down_write(&client->ddev->client_lock); ++ list_del(&client->node); ++ up_write(&client->ddev->client_lock); ++ ++ /* Free client. */ ++ sdtx_device_put(client->ddev); ++ mutex_destroy(&client->read_lock); ++ kfree(client); ++ ++ return 0; ++} ++ ++static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs) ++{ ++ struct sdtx_client *client = file->private_data; ++ struct sdtx_device *ddev = client->ddev; ++ unsigned int copied; ++ int status = 0; ++ ++ if (down_read_killable(&ddev->lock)) ++ return -ERESTARTSYS; ++ ++ /* Make sure we're not shut down. */ ++ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { ++ up_read(&ddev->lock); ++ return -ENODEV; ++ } ++ ++ do { ++ /* Check availability, wait if necessary. */ ++ if (kfifo_is_empty(&client->buffer)) { ++ up_read(&ddev->lock); ++ ++ if (file->f_flags & O_NONBLOCK) ++ return -EAGAIN; ++ ++ status = wait_event_interruptible(ddev->waitq, ++ !kfifo_is_empty(&client->buffer) || ++ test_bit(SDTX_DEVICE_SHUTDOWN_BIT, ++ &ddev->flags)); ++ if (status < 0) ++ return status; ++ ++ if (down_read_killable(&client->ddev->lock)) ++ return -ERESTARTSYS; ++ ++ /* Need to check that we're not shut down again. */ ++ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { ++ up_read(&ddev->lock); ++ return -ENODEV; ++ } ++ } ++ ++ /* Try to read from FIFO. */ ++ if (mutex_lock_interruptible(&client->read_lock)) { ++ up_read(&ddev->lock); ++ return -ERESTARTSYS; ++ } ++ ++ status = kfifo_to_user(&client->buffer, buf, count, &copied); ++ mutex_unlock(&client->read_lock); ++ ++ if (status < 0) { ++ up_read(&ddev->lock); ++ return status; ++ } ++ ++ /* We might not have gotten anything, check this here. */ ++ if (copied == 0 && (file->f_flags & O_NONBLOCK)) { ++ up_read(&ddev->lock); ++ return -EAGAIN; ++ } ++ } while (copied == 0); ++ ++ up_read(&ddev->lock); ++ return copied; ++} ++ ++static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt) ++{ ++ struct sdtx_client *client = file->private_data; ++ __poll_t events = 0; ++ ++ if (down_read_killable(&client->ddev->lock)) ++ return -ERESTARTSYS; ++ ++ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) { ++ up_read(&client->ddev->lock); ++ return EPOLLHUP | EPOLLERR; ++ } ++ ++ poll_wait(file, &client->ddev->waitq, pt); ++ ++ if (!kfifo_is_empty(&client->buffer)) ++ events |= EPOLLIN | EPOLLRDNORM; ++ ++ up_read(&client->ddev->lock); ++ return events; ++} ++ ++static int surface_dtx_fasync(int fd, struct file *file, int on) ++{ ++ struct sdtx_client *client = file->private_data; ++ ++ return fasync_helper(fd, file, on, &client->fasync); ++} ++ ++static const struct file_operations surface_dtx_fops = { ++ .owner = THIS_MODULE, ++ .open = surface_dtx_open, ++ .release = surface_dtx_release, ++ .read = surface_dtx_read, ++ .poll = surface_dtx_poll, ++ .fasync = surface_dtx_fasync, ++ .unlocked_ioctl = surface_dtx_ioctl, ++ .compat_ioctl = surface_dtx_ioctl, ++ .llseek = no_llseek, ++}; ++ ++ ++/* -- Event handling/forwarding. -------------------------------------------- */ ++ ++/* ++ * The device operation mode is not immediately updated on the EC when the ++ * base has been connected, i.e. querying the device mode inside the ++ * connection event callback yields an outdated value. Thus, we can only ++ * determine the new tablet-mode switch and device mode values after some ++ * time. ++ * ++ * These delays have been chosen by experimenting. We first delay on connect ++ * events, then check and validate the device mode against the base state and ++ * if invalid delay again by the "recheck" delay. ++ */ ++#define SDTX_DEVICE_MODE_DELAY_CONNECT msecs_to_jiffies(100) ++#define SDTX_DEVICE_MODE_DELAY_RECHECK msecs_to_jiffies(100) ++ ++struct sdtx_status_event { ++ struct sdtx_event e; ++ __u16 v; ++} __packed; ++ ++struct sdtx_base_info_event { ++ struct sdtx_event e; ++ struct sdtx_base_info v; ++} __packed; ++ ++union sdtx_generic_event { ++ struct sdtx_event common; ++ struct sdtx_status_event status; ++ struct sdtx_base_info_event base; ++}; ++ ++static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay); ++ ++/* Must be executed with ddev->write_lock held. */ ++static void sdtx_push_event(struct sdtx_device *ddev, struct sdtx_event *evt) ++{ ++ const size_t len = sizeof(struct sdtx_event) + evt->length; ++ struct sdtx_client *client; ++ ++ lockdep_assert_held(&ddev->write_lock); ++ ++ down_read(&ddev->client_lock); ++ list_for_each_entry(client, &ddev->client_list, node) { ++ if (!test_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags)) ++ continue; ++ ++ if (likely(kfifo_avail(&client->buffer) >= len)) ++ kfifo_in(&client->buffer, (const u8 *)evt, len); ++ else ++ dev_warn(ddev->dev, "event buffer overrun\n"); ++ ++ kill_fasync(&client->fasync, SIGIO, POLL_IN); ++ } ++ up_read(&ddev->client_lock); ++ ++ wake_up_interruptible(&ddev->waitq); ++} ++ ++static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in) ++{ ++ struct sdtx_device *ddev = container_of(nf, struct sdtx_device, notif); ++ union sdtx_generic_event event; ++ size_t len; ++ ++ /* Validate event payload length. */ ++ switch (in->command_id) { ++ case SAM_EVENT_CID_DTX_CONNECTION: ++ len = 2 * sizeof(u8); ++ break; ++ ++ case SAM_EVENT_CID_DTX_REQUEST: ++ len = 0; ++ break; ++ ++ case SAM_EVENT_CID_DTX_CANCEL: ++ len = sizeof(u8); ++ break; ++ ++ case SAM_EVENT_CID_DTX_LATCH_STATUS: ++ len = sizeof(u8); ++ break; ++ ++ default: ++ return 0; ++ }; ++ ++ if (in->length != len) { ++ dev_err(ddev->dev, ++ "unexpected payload size for event %#04x: got %u, expected %zu\n", ++ in->command_id, in->length, len); ++ return 0; ++ } ++ ++ mutex_lock(&ddev->write_lock); ++ ++ /* Translate event. */ ++ switch (in->command_id) { ++ case SAM_EVENT_CID_DTX_CONNECTION: ++ clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags); ++ ++ /* If state has not changed: do not send new event. */ ++ if (ddev->state.base.state == in->data[0] && ++ ddev->state.base.base_id == in->data[1]) ++ goto out; ++ ++ ddev->state.base.state = in->data[0]; ++ ddev->state.base.base_id = in->data[1]; ++ ++ event.base.e.length = sizeof(struct sdtx_base_info); ++ event.base.e.code = SDTX_EVENT_BASE_CONNECTION; ++ event.base.v.state = sdtx_translate_base_state(ddev, in->data[0]); ++ event.base.v.base_id = SDTX_BASE_TYPE_SSH(in->data[1]); ++ break; ++ ++ case SAM_EVENT_CID_DTX_REQUEST: ++ event.common.code = SDTX_EVENT_REQUEST; ++ event.common.length = 0; ++ break; ++ ++ case SAM_EVENT_CID_DTX_CANCEL: ++ event.status.e.length = sizeof(u16); ++ event.status.e.code = SDTX_EVENT_CANCEL; ++ event.status.v = sdtx_translate_cancel_reason(ddev, in->data[0]); ++ break; ++ ++ case SAM_EVENT_CID_DTX_LATCH_STATUS: ++ clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags); ++ ++ /* If state has not changed: do not send new event. */ ++ if (ddev->state.latch_status == in->data[0]) ++ goto out; ++ ++ ddev->state.latch_status = in->data[0]; ++ ++ event.status.e.length = sizeof(u16); ++ event.status.e.code = SDTX_EVENT_LATCH_STATUS; ++ event.status.v = sdtx_translate_latch_status(ddev, in->data[0]); ++ break; ++ } ++ ++ sdtx_push_event(ddev, &event.common); ++ ++ /* Update device mode on base connection change. */ ++ if (in->command_id == SAM_EVENT_CID_DTX_CONNECTION) { ++ unsigned long delay; ++ ++ delay = in->data[0] ? SDTX_DEVICE_MODE_DELAY_CONNECT : 0; ++ sdtx_update_device_mode(ddev, delay); ++ } ++ ++out: ++ mutex_unlock(&ddev->write_lock); ++ return SSAM_NOTIF_HANDLED; ++} ++ ++ ++/* -- State update functions. ----------------------------------------------- */ ++ ++static bool sdtx_device_mode_invalid(u8 mode, u8 base_state) ++{ ++ return ((base_state == SSAM_BAS_BASE_STATE_ATTACHED) && ++ (mode == SDTX_DEVICE_MODE_TABLET)) || ++ ((base_state == SSAM_BAS_BASE_STATE_DETACH_SUCCESS) && ++ (mode != SDTX_DEVICE_MODE_TABLET)); ++} ++ ++static void sdtx_device_mode_workfn(struct work_struct *work) ++{ ++ struct sdtx_device *ddev = container_of(work, struct sdtx_device, mode_work.work); ++ struct sdtx_status_event event; ++ struct ssam_bas_base_info base; ++ int status, tablet; ++ u8 mode; ++ ++ /* Get operation mode. */ ++ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); ++ if (status) { ++ dev_err(ddev->dev, "failed to get device mode: %d\n", status); ++ return; ++ } ++ ++ /* Get base info. */ ++ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); ++ if (status) { ++ dev_err(ddev->dev, "failed to get base info: %d\n", status); ++ return; ++ } ++ ++ /* ++ * In some cases (specifically when attaching the base), the device ++ * mode isn't updated right away. Thus we check if the device mode ++ * makes sense for the given base state and try again later if it ++ * doesn't. ++ */ ++ if (sdtx_device_mode_invalid(mode, base.state)) { ++ dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); ++ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); ++ return; ++ } ++ ++ mutex_lock(&ddev->write_lock); ++ clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags); ++ ++ /* Avoid sending duplicate device-mode events. */ ++ if (ddev->state.device_mode == mode) { ++ mutex_unlock(&ddev->write_lock); ++ return; ++ } ++ ++ ddev->state.device_mode = mode; ++ ++ event.e.length = sizeof(u16); ++ event.e.code = SDTX_EVENT_DEVICE_MODE; ++ event.v = mode; ++ ++ sdtx_push_event(ddev, &event.e); ++ ++ /* Send SW_TABLET_MODE event. */ ++ tablet = mode != SDTX_DEVICE_MODE_LAPTOP; ++ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); ++ input_sync(ddev->mode_switch); ++ ++ mutex_unlock(&ddev->write_lock); ++} ++ ++static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay) ++{ ++ schedule_delayed_work(&ddev->mode_work, delay); ++} ++ ++/* Must be executed with ddev->write_lock held. */ ++static void __sdtx_device_state_update_base(struct sdtx_device *ddev, ++ struct ssam_bas_base_info info) ++{ ++ struct sdtx_base_info_event event; ++ ++ lockdep_assert_held(&ddev->write_lock); ++ ++ /* Prevent duplicate events. */ ++ if (ddev->state.base.state == info.state && ++ ddev->state.base.base_id == info.base_id) ++ return; ++ ++ ddev->state.base = info; ++ ++ event.e.length = sizeof(struct sdtx_base_info); ++ event.e.code = SDTX_EVENT_BASE_CONNECTION; ++ event.v.state = sdtx_translate_base_state(ddev, info.state); ++ event.v.base_id = SDTX_BASE_TYPE_SSH(info.base_id); ++ ++ sdtx_push_event(ddev, &event.e); ++} ++ ++/* Must be executed with ddev->write_lock held. */ ++static void __sdtx_device_state_update_mode(struct sdtx_device *ddev, u8 mode) ++{ ++ struct sdtx_status_event event; ++ int tablet; ++ ++ /* ++ * Note: This function must be called after updating the base state ++ * via __sdtx_device_state_update_base(), as we rely on the updated ++ * base state value in the validity check below. ++ */ ++ ++ lockdep_assert_held(&ddev->write_lock); ++ ++ if (sdtx_device_mode_invalid(mode, ddev->state.base.state)) { ++ dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); ++ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); ++ return; ++ } ++ ++ /* Prevent duplicate events. */ ++ if (ddev->state.device_mode == mode) ++ return; ++ ++ ddev->state.device_mode = mode; ++ ++ /* Send event. */ ++ event.e.length = sizeof(u16); ++ event.e.code = SDTX_EVENT_DEVICE_MODE; ++ event.v = mode; ++ ++ sdtx_push_event(ddev, &event.e); ++ ++ /* Send SW_TABLET_MODE event. */ ++ tablet = mode != SDTX_DEVICE_MODE_LAPTOP; ++ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); ++ input_sync(ddev->mode_switch); ++} ++ ++/* Must be executed with ddev->write_lock held. */ ++static void __sdtx_device_state_update_latch(struct sdtx_device *ddev, u8 status) ++{ ++ struct sdtx_status_event event; ++ ++ lockdep_assert_held(&ddev->write_lock); ++ ++ /* Prevent duplicate events. */ ++ if (ddev->state.latch_status == status) ++ return; ++ ++ ddev->state.latch_status = status; ++ ++ event.e.length = sizeof(struct sdtx_base_info); ++ event.e.code = SDTX_EVENT_BASE_CONNECTION; ++ event.v = sdtx_translate_latch_status(ddev, status); ++ ++ sdtx_push_event(ddev, &event.e); ++} ++ ++static void sdtx_device_state_workfn(struct work_struct *work) ++{ ++ struct sdtx_device *ddev = container_of(work, struct sdtx_device, state_work.work); ++ struct ssam_bas_base_info base; ++ u8 mode, latch; ++ int status; ++ ++ /* Mark everything as dirty. */ ++ set_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags); ++ set_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags); ++ set_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags); ++ ++ /* ++ * Ensure that the state gets marked as dirty before continuing to ++ * query it. Necessary to ensure that clear_bit() calls in ++ * sdtx_notifier() and sdtx_device_mode_workfn() actually clear these ++ * bits if an event is received while updating the state here. ++ */ ++ smp_mb__after_atomic(); ++ ++ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); ++ if (status) { ++ dev_err(ddev->dev, "failed to get base state: %d\n", status); ++ return; ++ } ++ ++ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); ++ if (status) { ++ dev_err(ddev->dev, "failed to get device mode: %d\n", status); ++ return; ++ } ++ ++ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); ++ if (status) { ++ dev_err(ddev->dev, "failed to get latch status: %d\n", status); ++ return; ++ } ++ ++ mutex_lock(&ddev->write_lock); ++ ++ /* ++ * If the respective dirty-bit has been cleared, an event has been ++ * received, updating this state. The queried state may thus be out of ++ * date. At this point, we can safely assume that the state provided ++ * by the event is either up to date, or we're about to receive ++ * another event updating it. ++ */ ++ ++ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags)) ++ __sdtx_device_state_update_base(ddev, base); ++ ++ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags)) ++ __sdtx_device_state_update_mode(ddev, mode); ++ ++ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags)) ++ __sdtx_device_state_update_latch(ddev, latch); ++ ++ mutex_unlock(&ddev->write_lock); ++} ++ ++static void sdtx_update_device_state(struct sdtx_device *ddev, unsigned long delay) ++{ ++ schedule_delayed_work(&ddev->state_work, delay); ++} ++ ++ ++/* -- Common device initialization. ----------------------------------------- */ ++ ++static int sdtx_device_init(struct sdtx_device *ddev, struct device *dev, ++ struct ssam_controller *ctrl) ++{ ++ int status, tablet_mode; ++ ++ /* Basic initialization. */ ++ kref_init(&ddev->kref); ++ init_rwsem(&ddev->lock); ++ ddev->dev = dev; ++ ddev->ctrl = ctrl; ++ ++ ddev->mdev.minor = MISC_DYNAMIC_MINOR; ++ ddev->mdev.name = "surface_dtx"; ++ ddev->mdev.nodename = "surface/dtx"; ++ ddev->mdev.fops = &surface_dtx_fops; ++ ++ ddev->notif.base.priority = 1; ++ ddev->notif.base.fn = sdtx_notifier; ++ ddev->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; ++ ddev->notif.event.id.target_category = SSAM_SSH_TC_BAS; ++ ddev->notif.event.id.instance = 0; ++ ddev->notif.event.mask = SSAM_EVENT_MASK_NONE; ++ ddev->notif.event.flags = SSAM_EVENT_SEQUENCED; ++ ++ init_waitqueue_head(&ddev->waitq); ++ mutex_init(&ddev->write_lock); ++ init_rwsem(&ddev->client_lock); ++ INIT_LIST_HEAD(&ddev->client_list); ++ ++ INIT_DELAYED_WORK(&ddev->mode_work, sdtx_device_mode_workfn); ++ INIT_DELAYED_WORK(&ddev->state_work, sdtx_device_state_workfn); ++ ++ /* ++ * Get current device state. We want to guarantee that events are only ++ * sent when state actually changes. Thus we cannot use special ++ * "uninitialized" values, as that would cause problems when manually ++ * querying the state in surface_dtx_pm_complete(). I.e. we would not ++ * be able to detect state changes there if no change event has been ++ * received between driver initialization and first device suspension. ++ * ++ * Note that we also need to do this before registering the event ++ * notifier, as that may access the state values. ++ */ ++ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &ddev->state.base); ++ if (status) ++ return status; ++ ++ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &ddev->state.device_mode); ++ if (status) ++ return status; ++ ++ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &ddev->state.latch_status); ++ if (status) ++ return status; ++ ++ /* Set up tablet mode switch. */ ++ ddev->mode_switch = input_allocate_device(); ++ if (!ddev->mode_switch) ++ return -ENOMEM; ++ ++ ddev->mode_switch->name = "Microsoft Surface DTX Device Mode Switch"; ++ ddev->mode_switch->phys = "ssam/01:11:01:00:00/input0"; ++ ddev->mode_switch->id.bustype = BUS_HOST; ++ ddev->mode_switch->dev.parent = ddev->dev; ++ ++ tablet_mode = (ddev->state.device_mode != SDTX_DEVICE_MODE_LAPTOP); ++ input_set_capability(ddev->mode_switch, EV_SW, SW_TABLET_MODE); ++ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet_mode); ++ ++ status = input_register_device(ddev->mode_switch); ++ if (status) { ++ input_free_device(ddev->mode_switch); ++ return status; ++ } ++ ++ /* Set up event notifier. */ ++ status = ssam_notifier_register(ddev->ctrl, &ddev->notif); ++ if (status) ++ goto err_notif; ++ ++ /* Register miscdevice. */ ++ status = misc_register(&ddev->mdev); ++ if (status) ++ goto err_mdev; ++ ++ /* ++ * Update device state in case it has changed between getting the ++ * initial mode and registering the event notifier. ++ */ ++ sdtx_update_device_state(ddev, 0); ++ return 0; ++ ++err_notif: ++ ssam_notifier_unregister(ddev->ctrl, &ddev->notif); ++ cancel_delayed_work_sync(&ddev->mode_work); ++err_mdev: ++ input_unregister_device(ddev->mode_switch); ++ return status; ++} ++ ++static struct sdtx_device *sdtx_device_create(struct device *dev, struct ssam_controller *ctrl) ++{ ++ struct sdtx_device *ddev; ++ int status; ++ ++ ddev = kzalloc(sizeof(*ddev), GFP_KERNEL); ++ if (!ddev) ++ return ERR_PTR(-ENOMEM); ++ ++ status = sdtx_device_init(ddev, dev, ctrl); ++ if (status) { ++ sdtx_device_put(ddev); ++ return ERR_PTR(status); ++ } ++ ++ return ddev; ++} ++ ++static void sdtx_device_destroy(struct sdtx_device *ddev) ++{ ++ struct sdtx_client *client; ++ ++ /* ++ * Mark device as shut-down. Prevent new clients from being added and ++ * new operations from being executed. ++ */ ++ set_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags); ++ ++ /* Disable notifiers, prevent new events from arriving. */ ++ ssam_notifier_unregister(ddev->ctrl, &ddev->notif); ++ ++ /* Stop mode_work, prevent access to mode_switch. */ ++ cancel_delayed_work_sync(&ddev->mode_work); ++ ++ /* Stop state_work. */ ++ cancel_delayed_work_sync(&ddev->state_work); ++ ++ /* With mode_work canceled, we can unregister the mode_switch. */ ++ input_unregister_device(ddev->mode_switch); ++ ++ /* Wake up async clients. */ ++ down_write(&ddev->client_lock); ++ list_for_each_entry(client, &ddev->client_list, node) { ++ kill_fasync(&client->fasync, SIGIO, POLL_HUP); ++ } ++ up_write(&ddev->client_lock); ++ ++ /* Wake up blocking clients. */ ++ wake_up_interruptible(&ddev->waitq); ++ ++ /* ++ * Wait for clients to finish their current operation. After this, the ++ * controller and device references are guaranteed to be no longer in ++ * use. ++ */ ++ down_write(&ddev->lock); ++ ddev->dev = NULL; ++ ddev->ctrl = NULL; ++ up_write(&ddev->lock); ++ ++ /* Finally remove the misc-device. */ ++ misc_deregister(&ddev->mdev); ++ ++ /* ++ * We're now guaranteed that sdtx_device_open() won't be called any ++ * more, so we can now drop out reference. ++ */ ++ sdtx_device_put(ddev); ++} ++ ++ ++/* -- PM ops. --------------------------------------------------------------- */ ++ ++#ifdef CONFIG_PM_SLEEP ++ ++static void surface_dtx_pm_complete(struct device *dev) ++{ ++ struct sdtx_device *ddev = dev_get_drvdata(dev); ++ ++ /* ++ * Normally, the EC will store events while suspended (i.e. in ++ * display-off state) and release them when resumed (i.e. transitioned ++ * to display-on state). During hibernation, however, the EC will be ++ * shut down and does not store events. Furthermore, events might be ++ * dropped during prolonged suspension (it is currently unknown how ++ * big this event buffer is and how it behaves on overruns). ++ * ++ * To prevent any problems, we update the device state here. We do ++ * this delayed to ensure that any events sent by the EC directly ++ * after resuming will be handled first. The delay below has been ++ * chosen (experimentally), so that there should be ample time for ++ * these events to be handled, before we check and, if necessary, ++ * update the state. ++ */ ++ sdtx_update_device_state(ddev, msecs_to_jiffies(1000)); ++} ++ ++static const struct dev_pm_ops surface_dtx_pm_ops = { ++ .complete = surface_dtx_pm_complete, ++}; ++ ++#else /* CONFIG_PM_SLEEP */ ++ ++static const struct dev_pm_ops surface_dtx_pm_ops = {}; ++ ++#endif /* CONFIG_PM_SLEEP */ ++ ++ ++/* -- Platform driver. ------------------------------------------------------ */ ++ ++static int surface_dtx_platform_probe(struct platform_device *pdev) ++{ ++ struct ssam_controller *ctrl; ++ struct sdtx_device *ddev; ++ ++ /* Link to EC. */ ++ ctrl = ssam_client_bind(&pdev->dev); ++ if (IS_ERR(ctrl)) ++ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); ++ ++ ddev = sdtx_device_create(&pdev->dev, ctrl); ++ if (IS_ERR(ddev)) ++ return PTR_ERR(ddev); ++ ++ platform_set_drvdata(pdev, ddev); ++ return 0; ++} ++ ++static int surface_dtx_platform_remove(struct platform_device *pdev) ++{ ++ sdtx_device_destroy(platform_get_drvdata(pdev)); ++ return 0; ++} ++ ++static const struct acpi_device_id surface_dtx_acpi_match[] = { ++ { "MSHW0133", 0 }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match); ++ ++static struct platform_driver surface_dtx_platform_driver = { ++ .probe = surface_dtx_platform_probe, ++ .remove = surface_dtx_platform_remove, ++ .driver = { ++ .name = "surface_dtx_pltf", ++ .acpi_match_table = surface_dtx_acpi_match, ++ .pm = &surface_dtx_pm_ops, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(surface_dtx_platform_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/include/uapi/linux/surface_aggregator/dtx.h b/include/uapi/linux/surface_aggregator/dtx.h +new file mode 100644 +index 000000000000..0833aab0d819 +--- /dev/null ++++ b/include/uapi/linux/surface_aggregator/dtx.h +@@ -0,0 +1,146 @@ ++/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ ++/* ++ * Surface DTX (clipboard detachment system driver) user-space interface. ++ * ++ * Definitions, structs, and IOCTLs for the /dev/surface/dtx misc device. This ++ * device allows user-space to control the clipboard detachment process on ++ * Surface Book series devices. ++ * ++ * Copyright (C) 2020-2021 Maximilian Luz ++ */ ++ ++#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H ++#define _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H ++ ++#include ++#include ++ ++/* Status/error categories */ ++#define SDTX_CATEGORY_STATUS 0x0000 ++#define SDTX_CATEGORY_RUNTIME_ERROR 0x1000 ++#define SDTX_CATEGORY_HARDWARE_ERROR 0x2000 ++#define SDTX_CATEGORY_UNKNOWN 0xf000 ++ ++#define SDTX_CATEGORY_MASK 0xf000 ++#define SDTX_CATEGORY(value) ((value) & SDTX_CATEGORY_MASK) ++ ++#define SDTX_STATUS(code) ((code) | SDTX_CATEGORY_STATUS) ++#define SDTX_ERR_RT(code) ((code) | SDTX_CATEGORY_RUNTIME_ERROR) ++#define SDTX_ERR_HW(code) ((code) | SDTX_CATEGORY_HARDWARE_ERROR) ++#define SDTX_UNKNOWN(code) ((code) | SDTX_CATEGORY_UNKNOWN) ++ ++#define SDTX_SUCCESS(value) (SDTX_CATEGORY(value) == SDTX_CATEGORY_STATUS) ++ ++/* Latch status values */ ++#define SDTX_LATCH_CLOSED SDTX_STATUS(0x00) ++#define SDTX_LATCH_OPENED SDTX_STATUS(0x01) ++ ++/* Base state values */ ++#define SDTX_BASE_DETACHED SDTX_STATUS(0x00) ++#define SDTX_BASE_ATTACHED SDTX_STATUS(0x01) ++ ++/* Runtime errors (non-critical) */ ++#define SDTX_DETACH_NOT_FEASIBLE SDTX_ERR_RT(0x01) ++#define SDTX_DETACH_TIMEDOUT SDTX_ERR_RT(0x02) ++ ++/* Hardware errors (critical) */ ++#define SDTX_ERR_FAILED_TO_OPEN SDTX_ERR_HW(0x01) ++#define SDTX_ERR_FAILED_TO_REMAIN_OPEN SDTX_ERR_HW(0x02) ++#define SDTX_ERR_FAILED_TO_CLOSE SDTX_ERR_HW(0x03) ++ ++/* Base types */ ++#define SDTX_DEVICE_TYPE_HID 0x0100 ++#define SDTX_DEVICE_TYPE_SSH 0x0200 ++ ++#define SDTX_DEVICE_TYPE_MASK 0x0f00 ++#define SDTX_DEVICE_TYPE(value) ((value) & SDTX_DEVICE_TYPE_MASK) ++ ++#define SDTX_BASE_TYPE_HID(id) ((id) | SDTX_DEVICE_TYPE_HID) ++#define SDTX_BASE_TYPE_SSH(id) ((id) | SDTX_DEVICE_TYPE_SSH) ++ ++/** ++ * enum sdtx_device_mode - Mode describing how (and if) the clipboard is ++ * attached to the base of the device. ++ * @SDTX_DEVICE_MODE_TABLET: The clipboard is detached from the base and the ++ * device operates as tablet. ++ * @SDTX_DEVICE_MODE_LAPTOP: The clipboard is attached normally to the base ++ * and the device operates as laptop. ++ * @SDTX_DEVICE_MODE_STUDIO: The clipboard is attached to the base in reverse. ++ * The device operates as tablet with keyboard and ++ * touchpad deactivated, however, the base battery ++ * and, if present in the specific device model, dGPU ++ * are available to the system. ++ */ ++enum sdtx_device_mode { ++ SDTX_DEVICE_MODE_TABLET = 0x00, ++ SDTX_DEVICE_MODE_LAPTOP = 0x01, ++ SDTX_DEVICE_MODE_STUDIO = 0x02, ++}; ++ ++/** ++ * struct sdtx_event - Event provided by reading from the DTX device file. ++ * @length: Length of the event payload, in bytes. ++ * @code: Event code, detailing what type of event this is. ++ * @data: Payload of the event, containing @length bytes. ++ * ++ * See &enum sdtx_event_code for currently valid event codes. ++ */ ++struct sdtx_event { ++ __u16 length; ++ __u16 code; ++ __u8 data[]; ++} __attribute__((__packed__)); ++ ++/** ++ * enum sdtx_event_code - Code describing the type of an event. ++ * @SDTX_EVENT_REQUEST: Detachment request event type. ++ * @SDTX_EVENT_CANCEL: Cancel detachment process event type. ++ * @SDTX_EVENT_BASE_CONNECTION: Base/clipboard connection change event type. ++ * @SDTX_EVENT_LATCH_STATUS: Latch status change event type. ++ * @SDTX_EVENT_DEVICE_MODE: Device mode change event type. ++ * ++ * Used in &struct sdtx_event to describe the type of the event. Further event ++ * codes are reserved for future use. Any event parser should be able to ++ * gracefully handle unknown events, i.e. by simply skipping them. ++ * ++ * Consult the DTX user-space interface documentation for details regarding ++ * the individual event types. ++ */ ++enum sdtx_event_code { ++ SDTX_EVENT_REQUEST = 1, ++ SDTX_EVENT_CANCEL = 2, ++ SDTX_EVENT_BASE_CONNECTION = 3, ++ SDTX_EVENT_LATCH_STATUS = 4, ++ SDTX_EVENT_DEVICE_MODE = 5, ++}; ++ ++/** ++ * struct sdtx_base_info - Describes if and what type of base is connected. ++ * @state: The state of the connection. Valid values are %SDTX_BASE_DETACHED, ++ * %SDTX_BASE_ATTACHED, and %SDTX_DETACH_NOT_FEASIBLE (in case a base ++ * is attached but low clipboard battery prevents detachment). Other ++ * values are currently reserved. ++ * @base_id: The type of base connected. Zero if no base is connected. ++ */ ++struct sdtx_base_info { ++ __u16 state; ++ __u16 base_id; ++} __attribute__((__packed__)); ++ ++/* IOCTLs */ ++#define SDTX_IOCTL_EVENTS_ENABLE _IO(0xa5, 0x21) ++#define SDTX_IOCTL_EVENTS_DISABLE _IO(0xa5, 0x22) ++ ++#define SDTX_IOCTL_LATCH_LOCK _IO(0xa5, 0x23) ++#define SDTX_IOCTL_LATCH_UNLOCK _IO(0xa5, 0x24) ++ ++#define SDTX_IOCTL_LATCH_REQUEST _IO(0xa5, 0x25) ++#define SDTX_IOCTL_LATCH_CONFIRM _IO(0xa5, 0x26) ++#define SDTX_IOCTL_LATCH_HEARTBEAT _IO(0xa5, 0x27) ++#define SDTX_IOCTL_LATCH_CANCEL _IO(0xa5, 0x28) ++ ++#define SDTX_IOCTL_GET_BASE_INFO _IOR(0xa5, 0x29, struct sdtx_base_info) ++#define SDTX_IOCTL_GET_DEVICE_MODE _IOR(0xa5, 0x2a, __u16) ++#define SDTX_IOCTL_GET_LATCH_STATUS _IOR(0xa5, 0x2b, __u16) ++ ++#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H */ +-- +2.31.1 + +From 5f90d1d6214b7f4a0372dadbfe614ec12ae13425 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Mon, 8 Mar 2021 19:48:18 +0100 +Subject: [PATCH] platform/surface: dtx: Add support for native SSAM devices + +Add support for native SSAM devices to the DTX driver. This allows +support for the Surface Book 3, on which the DTX device is not present +in ACPI. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210308184819.437438-3-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/Kconfig | 4 ++ + drivers/platform/surface/surface_dtx.c | 90 +++++++++++++++++++++++++- + 2 files changed, 93 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index 41d67bb250fd..53beaedefdd1 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -127,6 +127,10 @@ config SURFACE_DTX + behavior of this process, which includes the option to abort it in + case the base is still in use or speed it up in case it is not. + ++ Note that this module can be built without support for the Surface ++ Aggregator Bus (i.e. CONFIG_SURFACE_AGGREGATOR_BUS=n). In that case, ++ some devices, specifically the Surface Book 3, will not be supported. ++ + config SURFACE_GPE + tristate "Surface GPE/Lid Support Driver" + depends on DMI +diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c +index 1301fab0ea14..85451eb94d98 100644 +--- a/drivers/platform/surface/surface_dtx.c ++++ b/drivers/platform/surface/surface_dtx.c +@@ -27,6 +27,7 @@ + #include + + #include ++#include + #include + + +@@ -1194,7 +1195,94 @@ static struct platform_driver surface_dtx_platform_driver = { + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + }; +-module_platform_driver(surface_dtx_platform_driver); ++ ++ ++/* -- SSAM device driver. --------------------------------------------------- */ ++ ++#ifdef CONFIG_SURFACE_AGGREGATOR_BUS ++ ++static int surface_dtx_ssam_probe(struct ssam_device *sdev) ++{ ++ struct sdtx_device *ddev; ++ ++ ddev = sdtx_device_create(&sdev->dev, sdev->ctrl); ++ if (IS_ERR(ddev)) ++ return PTR_ERR(ddev); ++ ++ ssam_device_set_drvdata(sdev, ddev); ++ return 0; ++} ++ ++static void surface_dtx_ssam_remove(struct ssam_device *sdev) ++{ ++ sdtx_device_destroy(ssam_device_get_drvdata(sdev)); ++} ++ ++static const struct ssam_device_id surface_dtx_ssam_match[] = { ++ { SSAM_SDEV(BAS, 0x01, 0x00, 0x00) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match); ++ ++static struct ssam_device_driver surface_dtx_ssam_driver = { ++ .probe = surface_dtx_ssam_probe, ++ .remove = surface_dtx_ssam_remove, ++ .match_table = surface_dtx_ssam_match, ++ .driver = { ++ .name = "surface_dtx", ++ .pm = &surface_dtx_pm_ops, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++ ++static int ssam_dtx_driver_register(void) ++{ ++ return ssam_device_driver_register(&surface_dtx_ssam_driver); ++} ++ ++static void ssam_dtx_driver_unregister(void) ++{ ++ ssam_device_driver_unregister(&surface_dtx_ssam_driver); ++} ++ ++#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++ ++static int ssam_dtx_driver_register(void) ++{ ++ return 0; ++} ++ ++static void ssam_dtx_driver_unregister(void) ++{ ++} ++ ++#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++ ++ ++/* -- Module setup. --------------------------------------------------------- */ ++ ++static int __init surface_dtx_init(void) ++{ ++ int status; ++ ++ status = ssam_dtx_driver_register(); ++ if (status) ++ return status; ++ ++ status = platform_driver_register(&surface_dtx_platform_driver); ++ if (status) ++ ssam_dtx_driver_unregister(); ++ ++ return status; ++} ++module_init(surface_dtx_init); ++ ++static void __exit surface_dtx_exit(void) ++{ ++ platform_driver_unregister(&surface_dtx_platform_driver); ++ ssam_dtx_driver_unregister(); ++} ++module_exit(surface_dtx_exit); + + MODULE_AUTHOR("Maximilian Luz "); + MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module"); +-- +2.31.1 + +From 942c6392c91293171d960506a8d0a65896bc4805 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Mon, 8 Mar 2021 19:48:19 +0100 +Subject: [PATCH] docs: driver-api: Add Surface DTX driver documentation + +Add documentation for the user-space interface of the Surface DTX +(detachment system) driver, used on Microsoft Surface Book series +devices. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210308184819.437438-4-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../surface_aggregator/clients/dtx.rst | 718 ++++++++++++++++++ + .../surface_aggregator/clients/index.rst | 1 + + MAINTAINERS | 1 + + 3 files changed, 720 insertions(+) + create mode 100644 Documentation/driver-api/surface_aggregator/clients/dtx.rst + +diff --git a/Documentation/driver-api/surface_aggregator/clients/dtx.rst b/Documentation/driver-api/surface_aggregator/clients/dtx.rst +new file mode 100644 +index 000000000000..e7e7c20007f0 +--- /dev/null ++++ b/Documentation/driver-api/surface_aggregator/clients/dtx.rst +@@ -0,0 +1,718 @@ ++.. SPDX-License-Identifier: GPL-2.0+ ++ ++.. |__u16| replace:: :c:type:`__u16 <__u16>` ++.. |sdtx_event| replace:: :c:type:`struct sdtx_event ` ++.. |sdtx_event_code| replace:: :c:type:`enum sdtx_event_code ` ++.. |sdtx_base_info| replace:: :c:type:`struct sdtx_base_info ` ++.. |sdtx_device_mode| replace:: :c:type:`struct sdtx_device_mode ` ++ ++====================================================== ++User-Space DTX (Clipboard Detachment System) Interface ++====================================================== ++ ++The ``surface_dtx`` driver is responsible for proper clipboard detachment ++and re-attachment handling. To this end, it provides the ``/dev/surface/dtx`` ++device file, through which it can interface with a user-space daemon. This ++daemon is then ultimately responsible for determining and taking necessary ++actions, such as unmounting devices attached to the base, ++unloading/reloading the graphics-driver, user-notifications, etc. ++ ++There are two basic communication principles used in this driver: Commands ++(in other parts of the documentation also referred to as requests) and ++events. Commands are sent to the EC and may have a different implications in ++different contexts. Events are sent by the EC upon some internal state ++change. Commands are always driver-initiated, whereas events are always ++initiated by the EC. ++ ++.. contents:: ++ ++Nomenclature ++============ ++ ++* **Clipboard:** ++ The detachable upper part of the Surface Book, housing the screen and CPU. ++ ++* **Base:** ++ The lower part of the Surface Book from which the clipboard can be ++ detached, optionally (model dependent) housing the discrete GPU (dGPU). ++ ++* **Latch:** ++ The mechanism keeping the clipboard attached to the base in normal ++ operation and allowing it to be detached when requested. ++ ++* **Silently ignored commands:** ++ The command is accepted by the EC as a valid command and acknowledged ++ (following the standard communication protocol), but the EC does not act ++ upon it, i.e. ignores it.e upper part of the ++ ++ ++Detachment Process ++================== ++ ++Warning: This part of the documentation is based on reverse engineering and ++testing and thus may contain errors or be incomplete. ++ ++Latch States ++------------ ++ ++The latch mechanism has two major states: *open* and *closed*. In the ++*closed* state (default), the clipboard is secured to the base, whereas in ++the *open* state, the clipboard can be removed by a user. ++ ++The latch can additionally be locked and, correspondingly, unlocked, which ++can influence the detachment procedure. Specifically, this locking mechanism ++is intended to prevent the dGPU, positioned in the base of the device, from ++being hot-unplugged while in use. More details can be found in the ++documentation for the detachment procedure below. By default, the latch is ++unlocked. ++ ++Detachment Procedure ++-------------------- ++ ++Note that the detachment process is governed fully by the EC. The ++``surface_dtx`` driver only relays events from the EC to user-space and ++commands from user-space to the EC, i.e. it does not influence this process. ++ ++The detachment process is started with the user pressing the *detach* button ++on the base of the device or executing the ``SDTX_IOCTL_LATCH_REQUEST`` IOCTL. ++Following that: ++ ++1. The EC turns on the indicator led on the detach-button, sends a ++ *detach-request* event (``SDTX_EVENT_REQUEST``), and awaits further ++ instructions/commands. In case the latch is unlocked, the led will flash ++ green. If the latch has been locked, the led will be solid red ++ ++2. The event is, via the ``surface_dtx`` driver, relayed to user-space, where ++ an appropriate user-space daemon can handle it and send instructions back ++ to the EC via IOCTLs provided by this driver. ++ ++3. The EC waits for instructions from user-space and acts according to them. ++ If the EC does not receive any instructions in a given period, it will ++ time out and continue as follows: ++ ++ - If the latch is unlocked, the EC will open the latch and the clipboard ++ can be detached from the base. This is the exact behavior as without ++ this driver or any user-space daemon. See the ``SDTX_IOCTL_LATCH_CONFIRM`` ++ description below for more details on the follow-up behavior of the EC. ++ ++ - If the latch is locked, the EC will *not* open the latch, meaning the ++ clipboard cannot be detached from the base. Furthermore, the EC sends ++ an cancel event (``SDTX_EVENT_CANCEL``) detailing this with the cancel ++ reason ``SDTX_DETACH_TIMEDOUT`` (see :ref:`events` for details). ++ ++Valid responses by a user-space daemon to a detachment request event are: ++ ++- Execute ``SDTX_IOCTL_LATCH_REQUEST``. This will immediately abort the ++ detachment process. Furthermore, the EC will send a detach-request event, ++ similar to the user pressing the detach-button to cancel said process (see ++ below). ++ ++- Execute ``SDTX_IOCTL_LATCH_CONFIRM``. This will cause the EC to open the ++ latch, after which the user can separate clipboard and base. ++ ++ As this changes the latch state, a *latch-status* event ++ (``SDTX_EVENT_LATCH_STATUS``) will be sent once the latch has been opened ++ successfully. If the EC fails to open the latch, e.g. due to hardware ++ error or low battery, a latch-cancel event (``SDTX_EVENT_CANCEL``) will be ++ sent with the cancel reason indicating the specific failure. ++ ++ If the latch is currently locked, the latch will automatically be ++ unlocked before it is opened. ++ ++- Execute ``SDTX_IOCTL_LATCH_HEARTBEAT``. This will reset the internal timeout. ++ No other actions will be performed, i.e. the detachment process will neither ++ be completed nor canceled, and the EC will still be waiting for further ++ responses. ++ ++- Execute ``SDTX_IOCTL_LATCH_CANCEL``. This will abort the detachment process, ++ similar to ``SDTX_IOCTL_LATCH_REQUEST``, described above, or the button ++ press, described below. A *generic request* event (``SDTX_EVENT_REQUEST``) ++ is send in response to this. In contrast to those, however, this command ++ does not trigger a new detachment process if none is currently in ++ progress. ++ ++- Do nothing. The detachment process eventually times out as described in ++ point 3. ++ ++See :ref:`ioctls` for more details on these responses. ++ ++It is important to note that, if the user presses the detach button at any ++point when a detachment operation is in progress (i.e. after the EC has sent ++the initial *detach-request* event (``SDTX_EVENT_REQUEST``) and before it ++received the corresponding response concluding the process), the detachment ++process is canceled on the EC-level and an identical event is being sent. ++Thus a *detach-request* event, by itself, does not signal the start of the ++detachment process. ++ ++The detachment process may further be canceled by the EC due to hardware ++failures or a low clipboard battery. This is done via a cancel event ++(``SDTX_EVENT_CANCEL``) with the corresponding cancel reason. ++ ++ ++User-Space Interface Documentation ++================================== ++ ++Error Codes and Status Values ++----------------------------- ++ ++Error and status codes are divided into different categories, which can be ++used to determine if the status code is an error, and, if it is, the ++severity and type of that error. The current categories are: ++ ++.. flat-table:: Overview of Status/Error Categories. ++ :widths: 2 1 3 ++ :header-rows: 1 ++ ++ * - Name ++ - Value ++ - Short Description ++ ++ * - ``STATUS`` ++ - ``0x0000`` ++ - Non-error status codes. ++ ++ * - ``RUNTIME_ERROR`` ++ - ``0x1000`` ++ - Non-critical runtime errors. ++ ++ * - ``HARDWARE_ERROR`` ++ - ``0x2000`` ++ - Critical hardware failures. ++ ++ * - ``UNKNOWN`` ++ - ``0xF000`` ++ - Unknown error codes. ++ ++Other categories are reserved for future use. The ``SDTX_CATEGORY()`` macro ++can be used to determine the category of any status value. The ++``SDTX_SUCCESS()`` macro can be used to check if the status value is a ++success value (``SDTX_CATEGORY_STATUS``) or if it indicates a failure. ++ ++Unknown status or error codes sent by the EC are assigned to the ``UNKNOWN`` ++category by the driver and may be implemented via their own code in the ++future. ++ ++Currently used error codes are: ++ ++.. flat-table:: Overview of Error Codes. ++ :widths: 2 1 1 3 ++ :header-rows: 1 ++ ++ * - Name ++ - Category ++ - Value ++ - Short Description ++ ++ * - ``SDTX_DETACH_NOT_FEASIBLE`` ++ - ``RUNTIME`` ++ - ``0x1001`` ++ - Detachment not feasible due to low clipboard battery. ++ ++ * - ``SDTX_DETACH_TIMEDOUT`` ++ - ``RUNTIME`` ++ - ``0x1002`` ++ - Detachment process timed out while the latch was locked. ++ ++ * - ``SDTX_ERR_FAILED_TO_OPEN`` ++ - ``HARDWARE`` ++ - ``0x2001`` ++ - Failed to open latch. ++ ++ * - ``SDTX_ERR_FAILED_TO_REMAIN_OPEN`` ++ - ``HARDWARE`` ++ - ``0x2002`` ++ - Failed to keep latch open. ++ ++ * - ``SDTX_ERR_FAILED_TO_CLOSE`` ++ - ``HARDWARE`` ++ - ``0x2003`` ++ - Failed to close latch. ++ ++Other error codes are reserved for future use. Non-error status codes may ++overlap and are generally only unique within their use-case: ++ ++.. flat-table:: Latch Status Codes. ++ :widths: 2 1 1 3 ++ :header-rows: 1 ++ ++ * - Name ++ - Category ++ - Value ++ - Short Description ++ ++ * - ``SDTX_LATCH_CLOSED`` ++ - ``STATUS`` ++ - ``0x0000`` ++ - Latch is closed/has been closed. ++ ++ * - ``SDTX_LATCH_OPENED`` ++ - ``STATUS`` ++ - ``0x0001`` ++ - Latch is open/has been opened. ++ ++.. flat-table:: Base State Codes. ++ :widths: 2 1 1 3 ++ :header-rows: 1 ++ ++ * - Name ++ - Category ++ - Value ++ - Short Description ++ ++ * - ``SDTX_BASE_DETACHED`` ++ - ``STATUS`` ++ - ``0x0000`` ++ - Base has been detached/is not present. ++ ++ * - ``SDTX_BASE_ATTACHED`` ++ - ``STATUS`` ++ - ``0x0001`` ++ - Base has been attached/is present. ++ ++Again, other codes are reserved for future use. ++ ++.. _events: ++ ++Events ++------ ++ ++Events can be received by reading from the device file. They are disabled by ++default and have to be enabled by executing ``SDTX_IOCTL_EVENTS_ENABLE`` ++first. All events follow the layout prescribed by |sdtx_event|. Specific ++event types can be identified by their event code, described in ++|sdtx_event_code|. Note that other event codes are reserved for future use, ++thus an event parser must be able to handle any unknown/unsupported event ++types gracefully, by relying on the payload length given in the event header. ++ ++Currently provided event types are: ++ ++.. flat-table:: Overview of DTX events. ++ :widths: 2 1 1 3 ++ :header-rows: 1 ++ ++ * - Name ++ - Code ++ - Payload ++ - Short Description ++ ++ * - ``SDTX_EVENT_REQUEST`` ++ - ``1`` ++ - ``0`` bytes ++ - Detachment process initiated/aborted. ++ ++ * - ``SDTX_EVENT_CANCEL`` ++ - ``2`` ++ - ``2`` bytes ++ - EC canceled detachment process. ++ ++ * - ``SDTX_EVENT_BASE_CONNECTION`` ++ - ``3`` ++ - ``4`` bytes ++ - Base connection state changed. ++ ++ * - ``SDTX_EVENT_LATCH_STATUS`` ++ - ``4`` ++ - ``2`` bytes ++ - Latch status changed. ++ ++ * - ``SDTX_EVENT_DEVICE_MODE`` ++ - ``5`` ++ - ``2`` bytes ++ - Device mode changed. ++ ++Individual events in more detail: ++ ++``SDTX_EVENT_REQUEST`` ++^^^^^^^^^^^^^^^^^^^^^^ ++ ++Sent when a detachment process is started or, if in progress, aborted by the ++user, either via a detach button press or a detach request ++(``SDTX_IOCTL_LATCH_REQUEST``) being sent from user-space. ++ ++Does not have any payload. ++ ++``SDTX_EVENT_CANCEL`` ++^^^^^^^^^^^^^^^^^^^^^ ++ ++Sent when a detachment process is canceled by the EC due to unfulfilled ++preconditions (e.g. clipboard battery too low to detach) or hardware ++failure. The reason for cancellation is given in the event payload detailed ++below and can be one of ++ ++* ``SDTX_DETACH_TIMEDOUT``: Detachment timed out while the latch was locked. ++ The latch has neither been opened nor unlocked. ++ ++* ``SDTX_DETACH_NOT_FEASIBLE``: Detachment not feasible due to low clipboard ++ battery. ++ ++* ``SDTX_ERR_FAILED_TO_OPEN``: Could not open the latch (hardware failure). ++ ++* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``: Could not keep the latch open (hardware ++ failure). ++ ++* ``SDTX_ERR_FAILED_TO_CLOSE``: Could not close the latch (hardware failure). ++ ++Other error codes in this context are reserved for future use. ++ ++These codes can be classified via the ``SDTX_CATEGORY()`` macro to discern ++between critical hardware errors (``SDTX_CATEGORY_HARDWARE_ERROR``) or ++runtime errors (``SDTX_CATEGORY_RUNTIME_ERROR``), the latter of which may ++happen during normal operation if certain preconditions for detachment are ++not given. ++ ++.. flat-table:: Detachment Cancel Event Payload ++ :widths: 1 1 4 ++ :header-rows: 1 ++ ++ * - Field ++ - Type ++ - Description ++ ++ * - ``reason`` ++ - |__u16| ++ - Reason for cancellation. ++ ++``SDTX_EVENT_BASE_CONNECTION`` ++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Sent when the base connection state has changed, i.e. when the base has been ++attached, detached, or detachment has become infeasible due to low clipboard ++battery. The new state and, if a base is connected, ID of the base is ++provided as payload of type |sdtx_base_info| with its layout presented ++below: ++ ++.. flat-table:: Base-Connection-Change Event Payload ++ :widths: 1 1 4 ++ :header-rows: 1 ++ ++ * - Field ++ - Type ++ - Description ++ ++ * - ``state`` ++ - |__u16| ++ - Base connection state. ++ ++ * - ``base_id`` ++ - |__u16| ++ - Type of base connected (zero if none). ++ ++Possible values for ``state`` are: ++ ++* ``SDTX_BASE_DETACHED``, ++* ``SDTX_BASE_ATTACHED``, and ++* ``SDTX_DETACH_NOT_FEASIBLE``. ++ ++Other values are reserved for future use. ++ ++``SDTX_EVENT_LATCH_STATUS`` ++^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Sent when the latch status has changed, i.e. when the latch has been opened, ++closed, or an error occurred. The current status is provided as payload: ++ ++.. flat-table:: Latch-Status-Change Event Payload ++ :widths: 1 1 4 ++ :header-rows: 1 ++ ++ * - Field ++ - Type ++ - Description ++ ++ * - ``status`` ++ - |__u16| ++ - Latch status. ++ ++Possible values for ``status`` are: ++ ++* ``SDTX_LATCH_CLOSED``, ++* ``SDTX_LATCH_OPENED``, ++* ``SDTX_ERR_FAILED_TO_OPEN``, ++* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and ++* ``SDTX_ERR_FAILED_TO_CLOSE``. ++ ++Other values are reserved for future use. ++ ++``SDTX_EVENT_DEVICE_MODE`` ++^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Sent when the device mode has changed. The new device mode is provided as ++payload: ++ ++.. flat-table:: Device-Mode-Change Event Payload ++ :widths: 1 1 4 ++ :header-rows: 1 ++ ++ * - Field ++ - Type ++ - Description ++ ++ * - ``mode`` ++ - |__u16| ++ - Device operation mode. ++ ++Possible values for ``mode`` are: ++ ++* ``SDTX_DEVICE_MODE_TABLET``, ++* ``SDTX_DEVICE_MODE_LAPTOP``, and ++* ``SDTX_DEVICE_MODE_STUDIO``. ++ ++Other values are reserved for future use. ++ ++.. _ioctls: ++ ++IOCTLs ++------ ++ ++The following IOCTLs are provided: ++ ++.. flat-table:: Overview of DTX IOCTLs ++ :widths: 1 1 1 1 4 ++ :header-rows: 1 ++ ++ * - Type ++ - Number ++ - Direction ++ - Name ++ - Description ++ ++ * - ``0xA5`` ++ - ``0x21`` ++ - ``-`` ++ - ``EVENTS_ENABLE`` ++ - Enable events for the current file descriptor. ++ ++ * - ``0xA5`` ++ - ``0x22`` ++ - ``-`` ++ - ``EVENTS_DISABLE`` ++ - Disable events for the current file descriptor. ++ ++ * - ``0xA5`` ++ - ``0x23`` ++ - ``-`` ++ - ``LATCH_LOCK`` ++ - Lock the latch. ++ ++ * - ``0xA5`` ++ - ``0x24`` ++ - ``-`` ++ - ``LATCH_UNLOCK`` ++ - Unlock the latch. ++ ++ * - ``0xA5`` ++ - ``0x25`` ++ - ``-`` ++ - ``LATCH_REQUEST`` ++ - Request clipboard detachment. ++ ++ * - ``0xA5`` ++ - ``0x26`` ++ - ``-`` ++ - ``LATCH_CONFIRM`` ++ - Confirm clipboard detachment request. ++ ++ * - ``0xA5`` ++ - ``0x27`` ++ - ``-`` ++ - ``LATCH_HEARTBEAT`` ++ - Send heartbeat signal to EC. ++ ++ * - ``0xA5`` ++ - ``0x28`` ++ - ``-`` ++ - ``LATCH_CANCEL`` ++ - Cancel detachment process. ++ ++ * - ``0xA5`` ++ - ``0x29`` ++ - ``R`` ++ - ``GET_BASE_INFO`` ++ - Get current base/connection information. ++ ++ * - ``0xA5`` ++ - ``0x2A`` ++ - ``R`` ++ - ``GET_DEVICE_MODE`` ++ - Get current device operation mode. ++ ++ * - ``0xA5`` ++ - ``0x2B`` ++ - ``R`` ++ - ``GET_LATCH_STATUS`` ++ - Get current device latch status. ++ ++``SDTX_IOCTL_EVENTS_ENABLE`` ++^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Defined as ``_IO(0xA5, 0x22)``. ++ ++Enable events for the current file descriptor. Events can be obtained by ++reading from the device, if enabled. Events are disabled by default. ++ ++``SDTX_IOCTL_EVENTS_DISABLE`` ++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Defined as ``_IO(0xA5, 0x22)``. ++ ++Disable events for the current file descriptor. Events can be obtained by ++reading from the device, if enabled. Events are disabled by default. ++ ++``SDTX_IOCTL_LATCH_LOCK`` ++^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Defined as ``_IO(0xA5, 0x23)``. ++ ++Locks the latch, causing the detachment procedure to abort without opening ++the latch on timeout. The latch is unlocked by default. This command will be ++silently ignored if the latch is already locked. ++ ++``SDTX_IOCTL_LATCH_UNLOCK`` ++^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Defined as ``_IO(0xA5, 0x24)``. ++ ++Unlocks the latch, causing the detachment procedure to open the latch on ++timeout. The latch is unlocked by default. This command will not open the ++latch when sent during an ongoing detachment process. It will be silently ++ignored if the latch is already unlocked. ++ ++``SDTX_IOCTL_LATCH_REQUEST`` ++^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Defined as ``_IO(0xA5, 0x25)``. ++ ++Generic latch request. Behavior depends on the context: If no ++detachment-process is active, detachment is requested. Otherwise the ++currently active detachment-process will be aborted. ++ ++If a detachment process is canceled by this operation, a generic detachment ++request event (``SDTX_EVENT_REQUEST``) will be sent. ++ ++This essentially behaves the same as a detachment button press. ++ ++``SDTX_IOCTL_LATCH_CONFIRM`` ++^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Defined as ``_IO(0xA5, 0x26)``. ++ ++Acknowledges and confirms a latch request. If sent during an ongoing ++detachment process, this command causes the latch to be opened immediately. ++The latch will also be opened if it has been locked. In this case, the latch ++lock is reset to the unlocked state. ++ ++This command will be silently ignored if there is currently no detachment ++procedure in progress. ++ ++``SDTX_IOCTL_LATCH_HEARTBEAT`` ++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Defined as ``_IO(0xA5, 0x27)``. ++ ++Sends a heartbeat, essentially resetting the detachment timeout. This ++command can be used to keep the detachment process alive while work required ++for the detachment to succeed is still in progress. ++ ++This command will be silently ignored if there is currently no detachment ++procedure in progress. ++ ++``SDTX_IOCTL_LATCH_CANCEL`` ++^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Defined as ``_IO(0xA5, 0x28)``. ++ ++Cancels detachment in progress (if any). If a detachment process is canceled ++by this operation, a generic detachment request event ++(``SDTX_EVENT_REQUEST``) will be sent. ++ ++This command will be silently ignored if there is currently no detachment ++procedure in progress. ++ ++``SDTX_IOCTL_GET_BASE_INFO`` ++^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Defined as ``_IOR(0xA5, 0x29, struct sdtx_base_info)``. ++ ++Get the current base connection state (i.e. attached/detached) and the type ++of the base connected to the clipboard. This is command essentially provides ++a way to query the information provided by the base connection change event ++(``SDTX_EVENT_BASE_CONNECTION``). ++ ++Possible values for ``struct sdtx_base_info.state`` are: ++ ++* ``SDTX_BASE_DETACHED``, ++* ``SDTX_BASE_ATTACHED``, and ++* ``SDTX_DETACH_NOT_FEASIBLE``. ++ ++Other values are reserved for future use. ++ ++``SDTX_IOCTL_GET_DEVICE_MODE`` ++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Defined as ``_IOR(0xA5, 0x2A, __u16)``. ++ ++Returns the device operation mode, indicating if and how the base is ++attached to the clipboard. This is command essentially provides a way to ++query the information provided by the device mode change event ++(``SDTX_EVENT_DEVICE_MODE``). ++ ++Returned values are: ++ ++* ``SDTX_DEVICE_MODE_LAPTOP`` ++* ``SDTX_DEVICE_MODE_TABLET`` ++* ``SDTX_DEVICE_MODE_STUDIO`` ++ ++See |sdtx_device_mode| for details. Other values are reserved for future ++use. ++ ++ ++``SDTX_IOCTL_GET_LATCH_STATUS`` ++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++ ++Defined as ``_IOR(0xA5, 0x2B, __u16)``. ++ ++Get the current latch status or (presumably) the last error encountered when ++trying to open/close the latch. This is command essentially provides a way ++to query the information provided by the latch status change event ++(``SDTX_EVENT_LATCH_STATUS``). ++ ++Returned values are: ++ ++* ``SDTX_LATCH_CLOSED``, ++* ``SDTX_LATCH_OPENED``, ++* ``SDTX_ERR_FAILED_TO_OPEN``, ++* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and ++* ``SDTX_ERR_FAILED_TO_CLOSE``. ++ ++Other values are reserved for future use. ++ ++A Note on Base IDs ++------------------ ++ ++Base types/IDs provided via ``SDTX_EVENT_BASE_CONNECTION`` or ++``SDTX_IOCTL_GET_BASE_INFO`` are directly forwarded from the EC in the lower ++byte of the combined |__u16| value, with the driver storing the EC type from ++which this ID comes in the high byte (without this, base IDs over different ++types of ECs may be overlapping). ++ ++The ``SDTX_DEVICE_TYPE()`` macro can be used to determine the EC device ++type. This can be one of ++ ++* ``SDTX_DEVICE_TYPE_HID``, for Surface Aggregator Module over HID, and ++ ++* ``SDTX_DEVICE_TYPE_SSH``, for Surface Aggregator Module over Surface Serial ++ Hub. ++ ++Note that currently only the ``SSH`` type EC is supported, however ``HID`` ++type is reserved for future use. ++ ++Structures and Enums ++-------------------- ++ ++.. kernel-doc:: include/uapi/linux/surface_aggregator/dtx.h ++ ++API Users ++========= ++ ++A user-space daemon utilizing this API can be found at ++https://github.com/linux-surface/surface-dtx-daemon. +diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst +index 3ccabce23271..98ea9946b8a2 100644 +--- a/Documentation/driver-api/surface_aggregator/clients/index.rst ++++ b/Documentation/driver-api/surface_aggregator/clients/index.rst +@@ -11,6 +11,7 @@ This is the documentation for client drivers themselves. Refer to + :maxdepth: 1 + + cdev ++ dtx + san + + .. only:: subproject and html +diff --git a/MAINTAINERS b/MAINTAINERS +index 3917e7363520..da1487d672a8 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -11872,6 +11872,7 @@ MICROSOFT SURFACE DTX DRIVER + M: Maximilian Luz + L: platform-driver-x86@vger.kernel.org + S: Maintained ++F: Documentation/driver-api/surface_aggregator/clients/dtx.rst + F: drivers/platform/surface/surface_dtx.c + F: include/uapi/linux/surface_aggregator/dtx.h + +-- +2.31.1 + +From d3739c04cbefe7605af24308c13d0a690203af6a Mon Sep 17 00:00:00 2001 +From: Wei Yongjun +Date: Tue, 9 Mar 2021 13:15:00 +0000 +Subject: [PATCH] platform/surface: aggregator_registry: Make symbol + 'ssam_base_hub_group' static + +The sparse tool complains as follows: + +drivers/platform/surface/surface_aggregator_registry.c:355:30: warning: + symbol 'ssam_base_hub_group' was not declared. Should it be static? + +This symbol is not used outside of surface_aggregator_registry.c, so this +commit marks it static. + +Fixes: 797e78564634 ("platform/surface: aggregator_registry: Add base device hub") +Reported-by: Hulk Robot +Signed-off-by: Wei Yongjun +Reviewed-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210309131500.1885772-1-weiyongjun1@huawei.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index cdb4a95af3e8..86cff5fce3cd 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -352,7 +352,7 @@ static struct attribute *ssam_base_hub_attrs[] = { + NULL, + }; + +-const struct attribute_group ssam_base_hub_group = { ++static const struct attribute_group ssam_base_hub_group = { + .attrs = ssam_base_hub_attrs, + }; + +-- +2.31.1 + +From 3b80ee5034052a82df2fb0b1370a2f8511f3bc1f Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Tue, 9 Mar 2021 17:25:50 +0100 +Subject: [PATCH] platform/surface: aggregator_registry: Add support for + Surface Pro 7+ + +The Surface Pro 7+ is essentially a refresh of the Surface Pro 7 with +updated hardware and a new WSID identifier. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210309162550.302161-1-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 86cff5fce3cd..eccb9d1007cd 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -191,7 +191,7 @@ static const struct software_node *ssam_node_group_sp6[] = { + NULL, + }; + +-/* Devices for Surface Pro 7. */ ++/* Devices for Surface Pro 7 and Surface Pro 7+. */ + static const struct software_node *ssam_node_group_sp7[] = { + &ssam_node_root, + &ssam_node_bat_ac, +@@ -521,6 +521,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { + /* Surface Pro 7 */ + { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, + ++ /* Surface Pro 7+ */ ++ { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, ++ + /* Surface Book 2 */ + { "MSHW0107", (unsigned long)ssam_node_group_sb2 }, + +-- +2.31.1 + +From e7d4cc3e3f1abba7d83303c452e9c718ab0e2e4c Mon Sep 17 00:00:00 2001 +From: kernel test robot +Date: Fri, 19 Mar 2021 13:19:19 +0800 +Subject: [PATCH] platform/surface: fix semicolon.cocci warnings + +drivers/platform/surface/surface_dtx.c:651:2-3: Unneeded semicolon + + Remove unneeded semicolon. + +Generated by: scripts/coccinelle/misc/semicolon.cocci + +Fixes: 1d609992832e ("platform/surface: Add DTX driver") +CC: Maximilian Luz +Reported-by: kernel test robot +Signed-off-by: kernel test robot +Reviewed-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210319051919.GA39801@ae4f36e4f012 +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/surface_dtx.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c +index 85451eb94d98..1fedacf74050 100644 +--- a/drivers/platform/surface/surface_dtx.c ++++ b/drivers/platform/surface/surface_dtx.c +@@ -649,7 +649,7 @@ static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event + + default: + return 0; +- }; ++ } + + if (in->length != len) { + dev_err(ddev->dev, +-- +2.31.1 + +From 2eeddec425f7ea847bd1dc9ff9baf1a112814cfe Mon Sep 17 00:00:00 2001 +From: Dan Carpenter +Date: Fri, 26 Mar 2021 15:28:48 +0300 +Subject: [PATCH] platform/surface: clean up a variable in surface_dtx_read() + +The "&client->ddev->lock" and "&ddev->lock" are the same thing. Let's +use "&ddev->lock" consistently. + +Signed-off-by: Dan Carpenter +Reviewed-by: Maximilian Luz +Link: https://lore.kernel.org/r/YF3TgCcpcCYl3a//@mwanda +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/surface_dtx.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c +index 1fedacf74050..63ce587e79e3 100644 +--- a/drivers/platform/surface/surface_dtx.c ++++ b/drivers/platform/surface/surface_dtx.c +@@ -487,7 +487,7 @@ static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t coun + if (status < 0) + return status; + +- if (down_read_killable(&client->ddev->lock)) ++ if (down_read_killable(&ddev->lock)) + return -ERESTARTSYS; + + /* Need to check that we're not shut down again. */ +-- +2.31.1 + +From b37d3dc077939e99cf0950a6a3009764531bcc15 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Tue, 6 Apr 2021 01:12:22 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Give devices time to + set up when connecting + +Sometimes, the "base connected" event that we rely on to (re-)attach the +device connected to the base is sent a bit too early. When this happens, +some devices may not be completely ready yet. + +Specifically, the battery has been observed to report zero-values for +things like full charge capacity, which, however, is only loaded once +when the driver for that device probes. This can thus result in battery +readings being unavailable. + +As we cannot easily and reliably discern between devices that are not +ready yet and devices that are not connected (i.e. will never be ready), +delay adding these devices. This should give them enough time to set up. + +The delay is set to 2.5 seconds, which should give us a good safety +margin based on testing and still be fairly responsive for users. + +To achieve that delay switch to updating via a delayed work struct, +which means that we can also get rid of some locking. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210405231222.358113-1-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 98 ++++++++----------- + 1 file changed, 40 insertions(+), 58 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index eccb9d1007cd..685d37a7add1 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -13,10 +13,10 @@ + #include + #include + #include +-#include + #include + #include + #include ++#include + + #include + #include +@@ -287,6 +287,13 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c + + /* -- SSAM base-hub driver. ------------------------------------------------- */ + ++/* ++ * Some devices (especially battery) may need a bit of time to be fully usable ++ * after being (re-)connected. This delay has been determined via ++ * experimentation. ++ */ ++#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) ++ + enum ssam_base_hub_state { + SSAM_BASE_HUB_UNINITIALIZED, + SSAM_BASE_HUB_CONNECTED, +@@ -296,8 +303,8 @@ enum ssam_base_hub_state { + struct ssam_base_hub { + struct ssam_device *sdev; + +- struct mutex lock; /* Guards state update checks and transitions. */ + enum ssam_base_hub_state state; ++ struct delayed_work update_work; + + struct ssam_event_notifier notif; + }; +@@ -335,11 +342,7 @@ static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attrib + char *buf) + { + struct ssam_base_hub *hub = dev_get_drvdata(dev); +- bool connected; +- +- mutex_lock(&hub->lock); +- connected = hub->state == SSAM_BASE_HUB_CONNECTED; +- mutex_unlock(&hub->lock); ++ bool connected = hub->state == SSAM_BASE_HUB_CONNECTED; + + return sysfs_emit(buf, "%d\n", connected); + } +@@ -356,16 +359,20 @@ static const struct attribute_group ssam_base_hub_group = { + .attrs = ssam_base_hub_attrs, + }; + +-static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new) ++static void ssam_base_hub_update_workfn(struct work_struct *work) + { ++ struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work); + struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); ++ enum ssam_base_hub_state state; + int status = 0; + +- lockdep_assert_held(&hub->lock); ++ status = ssam_base_hub_query_state(hub, &state); ++ if (status) ++ return; + +- if (hub->state == new) +- return 0; +- hub->state = new; ++ if (hub->state == state) ++ return; ++ hub->state = state; + + if (hub->state == SSAM_BASE_HUB_CONNECTED) + status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); +@@ -374,51 +381,28 @@ static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_ + + if (status) + dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); +- +- return status; +-} +- +-static int ssam_base_hub_update(struct ssam_base_hub *hub) +-{ +- enum ssam_base_hub_state state; +- int status; +- +- mutex_lock(&hub->lock); +- +- status = ssam_base_hub_query_state(hub, &state); +- if (!status) +- status = __ssam_base_hub_update(hub, state); +- +- mutex_unlock(&hub->lock); +- return status; + } + + static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) + { +- struct ssam_base_hub *hub; +- struct ssam_device *sdev; +- enum ssam_base_hub_state new; +- +- hub = container_of(nf, struct ssam_base_hub, notif); +- sdev = hub->sdev; ++ struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif); ++ unsigned long delay; + + if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) + return 0; + + if (event->length < 1) { +- dev_err(&sdev->dev, "unexpected payload size: %u\n", +- event->length); ++ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); + return 0; + } + +- if (event->data[0]) +- new = SSAM_BASE_HUB_CONNECTED; +- else +- new = SSAM_BASE_HUB_DISCONNECTED; ++ /* ++ * Delay update when the base is being connected to give devices/EC ++ * some time to set up. ++ */ ++ delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0; + +- mutex_lock(&hub->lock); +- __ssam_base_hub_update(hub, new); +- mutex_unlock(&hub->lock); ++ schedule_delayed_work(&hub->update_work, delay); + + /* + * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and +@@ -430,7 +414,10 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam + + static int __maybe_unused ssam_base_hub_resume(struct device *dev) + { +- return ssam_base_hub_update(dev_get_drvdata(dev)); ++ struct ssam_base_hub *hub = dev_get_drvdata(dev); ++ ++ schedule_delayed_work(&hub->update_work, 0); ++ return 0; + } + static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); + +@@ -443,8 +430,6 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) + if (!hub) + return -ENOMEM; + +- mutex_init(&hub->lock); +- + hub->sdev = sdev; + hub->state = SSAM_BASE_HUB_UNINITIALIZED; + +@@ -456,27 +441,25 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) + hub->notif.event.mask = SSAM_EVENT_MASK_NONE; + hub->notif.event.flags = SSAM_EVENT_SEQUENCED; + ++ INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn); ++ + ssam_device_set_drvdata(sdev, hub); + + status = ssam_notifier_register(sdev->ctrl, &hub->notif); + if (status) +- goto err_register; +- +- status = ssam_base_hub_update(hub); +- if (status) +- goto err_update; ++ return status; + + status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); + if (status) +- goto err_update; ++ goto err; + ++ schedule_delayed_work(&hub->update_work, 0); + return 0; + +-err_update: ++err: + ssam_notifier_unregister(sdev->ctrl, &hub->notif); ++ cancel_delayed_work_sync(&hub->update_work); + ssam_hub_remove_devices(&sdev->dev); +-err_register: +- mutex_destroy(&hub->lock); + return status; + } + +@@ -487,9 +470,8 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) + sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); + + ssam_notifier_unregister(sdev->ctrl, &hub->notif); ++ cancel_delayed_work_sync(&hub->update_work); + ssam_hub_remove_devices(&sdev->dev); +- +- mutex_destroy(&hub->lock); + } + + static const struct ssam_device_id ssam_base_hub_match[] = { +-- +2.31.1 + +From 5c89063cd80d956a0060bba8f01cdb32e4b8647a Mon Sep 17 00:00:00 2001 +From: Barry Song +Date: Wed, 3 Mar 2021 11:49:15 +1300 +Subject: [PATCH] genirq: Add IRQF_NO_AUTOEN for request_irq/nmi() + +Many drivers don't want interrupts enabled automatically via request_irq(). +So they are handling this issue by either way of the below two: + +(1) + irq_set_status_flags(irq, IRQ_NOAUTOEN); + request_irq(dev, irq...); + +(2) + request_irq(dev, irq...); + disable_irq(irq); + +The code in the second way is silly and unsafe. In the small time gap +between request_irq() and disable_irq(), interrupts can still come. + +The code in the first way is safe though it's subobtimal. + +Add a new IRQF_NO_AUTOEN flag which can be handed in by drivers to +request_irq() and request_nmi(). It prevents the automatic enabling of the +requested interrupt/nmi in the same safe way as #1 above. With that the +various usage sites of #1 and #2 above can be simplified and corrected. + +Signed-off-by: Barry Song +Signed-off-by: Thomas Gleixner +Signed-off-by: Ingo Molnar +Cc: dmitry.torokhov@gmail.com +Link: https://lore.kernel.org/r/20210302224916.13980-2-song.bao.hua@hisilicon.com +Patchset: surface-sam +--- + include/linux/interrupt.h | 4 ++++ + kernel/irq/manage.c | 11 +++++++++-- + 2 files changed, 13 insertions(+), 2 deletions(-) + +diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h +index 967e25767153..76f1161a441a 100644 +--- a/include/linux/interrupt.h ++++ b/include/linux/interrupt.h +@@ -61,6 +61,9 @@ + * interrupt handler after suspending interrupts. For system + * wakeup devices users need to implement wakeup detection in + * their interrupt handlers. ++ * IRQF_NO_AUTOEN - Don't enable IRQ or NMI automatically when users request it. ++ * Users will enable it explicitly by enable_irq() or enable_nmi() ++ * later. + */ + #define IRQF_SHARED 0x00000080 + #define IRQF_PROBE_SHARED 0x00000100 +@@ -74,6 +77,7 @@ + #define IRQF_NO_THREAD 0x00010000 + #define IRQF_EARLY_RESUME 0x00020000 + #define IRQF_COND_SUSPEND 0x00040000 ++#define IRQF_NO_AUTOEN 0x00080000 + + #define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD) + +diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c +index 21ea370fccda..49288e941365 100644 +--- a/kernel/irq/manage.c ++++ b/kernel/irq/manage.c +@@ -1697,7 +1697,8 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new) + irqd_set(&desc->irq_data, IRQD_NO_BALANCING); + } + +- if (irq_settings_can_autoenable(desc)) { ++ if (!(new->flags & IRQF_NO_AUTOEN) && ++ irq_settings_can_autoenable(desc)) { + irq_startup(desc, IRQ_RESEND, IRQ_START_COND); + } else { + /* +@@ -2090,10 +2091,15 @@ int request_threaded_irq(unsigned int irq, irq_handler_t handler, + * which interrupt is which (messes up the interrupt freeing + * logic etc). + * ++ * Also shared interrupts do not go well with disabling auto enable. ++ * The sharing interrupt might request it while it's still disabled ++ * and then wait for interrupts forever. ++ * + * Also IRQF_COND_SUSPEND only makes sense for shared interrupts and + * it cannot be set along with IRQF_NO_SUSPEND. + */ + if (((irqflags & IRQF_SHARED) && !dev_id) || ++ ((irqflags & IRQF_SHARED) && (irqflags & IRQF_NO_AUTOEN)) || + (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) || + ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND))) + return -EINVAL; +@@ -2249,7 +2255,8 @@ int request_nmi(unsigned int irq, irq_handler_t handler, + + desc = irq_to_desc(irq); + +- if (!desc || irq_settings_can_autoenable(desc) || ++ if (!desc || (irq_settings_can_autoenable(desc) && ++ !(irqflags & IRQF_NO_AUTOEN)) || + !irq_settings_can_request(desc) || + WARN_ON(irq_settings_is_per_cpu_devid(desc)) || + !irq_supports_nmi(desc)) +-- +2.31.1 + +From 4c27478492346cbc69ad1779f0ac0f2dd93c4ec2 Mon Sep 17 00:00:00 2001 +From: Tian Tao +Date: Wed, 7 Apr 2021 15:00:52 +0800 +Subject: [PATCH] platform/surface: aggregator: move to use request_irq by + IRQF_NO_AUTOEN flag + +disable_irq() after request_irq() still has a time gap in which +interrupts can come. request_irq() with IRQF_NO_AUTOEN flag will +disable IRQ auto-enable because of requesting. + +this patch is made base on "add IRQF_NO_AUTOEN for request_irq" which +is being merged: https://lore.kernel.org/patchwork/patch/1388765/ + +Signed-off-by: Tian Tao +Reviewed-by: Maximilian Luz +Link: https://lore.kernel.org/r/1617778852-26492-1-git-send-email-tiantao6@hisilicon.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/controller.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c +index aa6f37b4f46e..00e38284885a 100644 +--- a/drivers/platform/surface/aggregator/controller.c ++++ b/drivers/platform/surface/aggregator/controller.c +@@ -2483,7 +2483,8 @@ int ssam_irq_setup(struct ssam_controller *ctrl) + * interrupt, and let the SAM resume callback during the controller + * resume process clear it. + */ +- const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING; ++ const int irqf = IRQF_SHARED | IRQF_ONESHOT | ++ IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN; + + gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS); + if (IS_ERR(gpiod)) +@@ -2501,7 +2502,6 @@ int ssam_irq_setup(struct ssam_controller *ctrl) + return status; + + ctrl->irq.num = irq; +- disable_irq(ctrl->irq.num); + return 0; + } + +-- +2.31.1 + +From 8dcb1d5d4c05dbea8d40e4e9f23e631f1e5cdd3c Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Wed, 5 May 2021 14:53:45 +0200 +Subject: [PATCH] platform/surface: aggregator: Do not mark interrupt as shared + +Having both IRQF_NO_AUTOEN and IRQF_SHARED set causes +request_threaded_irq() to return with -EINVAL (see comment in flag +validation in that function). As the interrupt is currently not shared +between multiple devices, drop the IRQF_SHARED flag. + +Fixes: 507cf5a2f1e2 ("platform/surface: aggregator: move to use request_irq by IRQF_NO_AUTOEN flag") +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/controller.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c +index 00e38284885a..1f42fcd5d8c3 100644 +--- a/drivers/platform/surface/aggregator/controller.c ++++ b/drivers/platform/surface/aggregator/controller.c +@@ -2483,8 +2483,7 @@ int ssam_irq_setup(struct ssam_controller *ctrl) + * interrupt, and let the SAM resume callback during the controller + * resume process clear it. + */ +- const int irqf = IRQF_SHARED | IRQF_ONESHOT | +- IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN; ++ const int irqf = IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN; + + gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS); + if (IS_ERR(gpiod)) +-- +2.31.1 + +From afcab874b65e164fd01bee6905b281642e4785c5 Mon Sep 17 00:00:00 2001 +From: Dan Carpenter +Date: Tue, 20 Apr 2021 11:44:02 +0300 +Subject: [PATCH] platform/surface: aggregator: fix a bit test + +The "funcs" variable is a u64. If "func" is more than 31 then the +BIT() shift will wrap instead of testing the high bits. + +Fixes: c167b9c7e3d6 ("platform/surface: Add Surface Aggregator subsystem") +Reported-by: kernel test robot +Signed-off-by: Dan Carpenter +Reviewed-by: Maximilian Luz +Link: https://lore.kernel.org/r/YH6UUhJhGk3mk13b@mwanda +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/controller.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c +index 1f42fcd5d8c3..8a70df60142c 100644 +--- a/drivers/platform/surface/aggregator/controller.c ++++ b/drivers/platform/surface/aggregator/controller.c +@@ -1040,7 +1040,7 @@ static int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret) + union acpi_object *obj; + u64 val; + +- if (!(funcs & BIT(func))) ++ if (!(funcs & BIT_ULL(func))) + return 0; /* Not supported, leave *ret at its default value */ + + obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, +-- +2.31.1 + +From 8636b17320f580819226a2108b9cb211d36f0fcb Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Wed, 10 Mar 2021 23:53:28 +0100 +Subject: [PATCH] HID: Add support for Surface Aggregator Module HID transport +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add a HID transport driver to support integrated HID devices on newer +Microsoft Surface models (specifically 7th-generation, i.e. Surface +Laptop 3, Surface Book 3, and later). + +On those models, the internal keyboard and touchpad (as well as some +other HID devices with currently unknown function) are connected via the +generic HID subsystem (TC=0x15) of the Surface System Aggregator Module +(SSAM). This subsystem provides a generic HID transport layer, support +for which is implemented by this driver. + +Co-developed-by: Blaž Hrastnik +Signed-off-by: Blaž Hrastnik +Signed-off-by: Maximilian Luz +Signed-off-by: Jiri Kosina +Patchset: surface-sam +--- + MAINTAINERS | 7 + + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 2 + + drivers/hid/surface-hid/Kconfig | 28 +++ + drivers/hid/surface-hid/Makefile | 6 + + drivers/hid/surface-hid/surface_hid.c | 253 +++++++++++++++++++ + drivers/hid/surface-hid/surface_hid_core.c | 272 +++++++++++++++++++++ + drivers/hid/surface-hid/surface_hid_core.h | 77 ++++++ + 8 files changed, 647 insertions(+) + create mode 100644 drivers/hid/surface-hid/Kconfig + create mode 100644 drivers/hid/surface-hid/Makefile + create mode 100644 drivers/hid/surface-hid/surface_hid.c + create mode 100644 drivers/hid/surface-hid/surface_hid_core.c + create mode 100644 drivers/hid/surface-hid/surface_hid_core.h + +diff --git a/MAINTAINERS b/MAINTAINERS +index da1487d672a8..f54b22333ec6 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -11891,6 +11891,13 @@ S: Maintained + T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git + F: drivers/platform/surface/ + ++MICROSOFT SURFACE HID TRANSPORT DRIVER ++M: Maximilian Luz ++L: linux-input@vger.kernel.org ++L: platform-driver-x86@vger.kernel.org ++S: Maintained ++F: drivers/hid/surface-hid/ ++ + MICROSOFT SURFACE HOT-PLUG DRIVER + M: Maximilian Luz + L: platform-driver-x86@vger.kernel.org +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 786b71ef7738..26e06097ba08 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1206,4 +1206,6 @@ source "drivers/hid/intel-ish-hid/Kconfig" + + source "drivers/hid/amd-sfh-hid/Kconfig" + ++source "drivers/hid/surface-hid/Kconfig" ++ + endmenu +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index c4f6d5c613dc..1044ed238856 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -145,3 +145,5 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ + obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ + + obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ ++ ++obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ +diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig +new file mode 100644 +index 000000000000..642c7f0e64fe +--- /dev/null ++++ b/drivers/hid/surface-hid/Kconfig +@@ -0,0 +1,28 @@ ++# SPDX-License-Identifier: GPL-2.0+ ++menu "Surface System Aggregator Module HID support" ++ depends on SURFACE_AGGREGATOR ++ depends on INPUT ++ ++config SURFACE_HID ++ tristate "HID transport driver for Surface System Aggregator Module" ++ depends on SURFACE_AGGREGATOR_REGISTRY ++ select SURFACE_HID_CORE ++ help ++ Driver to support integrated HID devices on newer Microsoft Surface ++ models. ++ ++ This driver provides support for the HID transport protocol provided ++ by the Surface Aggregator Module (i.e. the embedded controller) on ++ 7th-generation Microsoft Surface devices, i.e. Surface Book 3 and ++ Surface Laptop 3. On those models, it is mainly used to connect the ++ integrated touchpad and keyboard. ++ ++ Say M or Y here, if you want support for integrated HID devices, i.e. ++ integrated touchpad and keyboard, on 7th generation Microsoft Surface ++ models. ++ ++endmenu ++ ++config SURFACE_HID_CORE ++ tristate ++ select HID +diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile +new file mode 100644 +index 000000000000..62fc04632d3d +--- /dev/null ++++ b/drivers/hid/surface-hid/Makefile +@@ -0,0 +1,6 @@ ++# SPDX-License-Identifier: GPL-2.0+ ++# ++# Makefile - Surface System Aggregator Module (SSAM) HID transport driver. ++# ++obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o ++obj-$(CONFIG_SURFACE_HID) += surface_hid.o +diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c +new file mode 100644 +index 000000000000..3477b31611ae +--- /dev/null ++++ b/drivers/hid/surface-hid/surface_hid.c +@@ -0,0 +1,253 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Surface System Aggregator Module (SSAM) HID transport driver for the ++ * generic HID interface (HID/TC=0x15 subsystem). Provides support for ++ * integrated HID devices on Surface Laptop 3, Book 3, and later. ++ * ++ * Copyright (C) 2019-2021 Blaž Hrastnik , ++ * Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include "surface_hid_core.h" ++ ++ ++/* -- SAM interface. -------------------------------------------------------- */ ++ ++struct surface_hid_buffer_slice { ++ __u8 entry; ++ __le32 offset; ++ __le32 length; ++ __u8 end; ++ __u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct surface_hid_buffer_slice) == 10); ++ ++enum surface_hid_cid { ++ SURFACE_HID_CID_OUTPUT_REPORT = 0x01, ++ SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02, ++ SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03, ++ SURFACE_HID_CID_GET_DESCRIPTOR = 0x04, ++}; ++ ++static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len) ++{ ++ u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76]; ++ struct surface_hid_buffer_slice *slice; ++ struct ssam_request rqst; ++ struct ssam_response rsp; ++ u32 buffer_len, offset, length; ++ int status; ++ ++ /* ++ * Note: The 0x76 above has been chosen because that's what's used by ++ * the Windows driver. Together with the header, this leads to a 128 ++ * byte payload in total. ++ */ ++ ++ buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice); ++ ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR; ++ rqst.instance_id = shid->uid.instance; ++ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; ++ rqst.length = sizeof(struct surface_hid_buffer_slice); ++ rqst.payload = buffer; ++ ++ rsp.capacity = ARRAY_SIZE(buffer); ++ rsp.pointer = buffer; ++ ++ slice = (struct surface_hid_buffer_slice *)buffer; ++ slice->entry = entry; ++ slice->end = 0; ++ ++ offset = 0; ++ length = buffer_len; ++ ++ while (!slice->end && offset < len) { ++ put_unaligned_le32(offset, &slice->offset); ++ put_unaligned_le32(length, &slice->length); ++ ++ rsp.length = 0; ++ ++ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, ++ sizeof(*slice)); ++ if (status) ++ return status; ++ ++ offset = get_unaligned_le32(&slice->offset); ++ length = get_unaligned_le32(&slice->length); ++ ++ /* Don't mess stuff up in case we receive garbage. */ ++ if (length > buffer_len || offset > len) ++ return -EPROTO; ++ ++ if (offset + length > len) ++ length = len - offset; ++ ++ memcpy(buf + offset, &slice->data[0], length); ++ ++ offset += length; ++ length = buffer_len; ++ } ++ ++ if (offset != len) { ++ dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n", ++ offset, len); ++ return -EPROTO; ++ } ++ ++ return 0; ++} ++ ++static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature, ++ u8 *buf, size_t len) ++{ ++ struct ssam_request rqst; ++ u8 cid; ++ ++ if (feature) ++ cid = SURFACE_HID_CID_SET_FEATURE_REPORT; ++ else ++ cid = SURFACE_HID_CID_OUTPUT_REPORT; ++ ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.instance_id = shid->uid.instance; ++ rqst.command_id = cid; ++ rqst.flags = 0; ++ rqst.length = len; ++ rqst.payload = buf; ++ ++ buf[0] = rprt_id; ++ ++ return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL); ++} ++ ++static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) ++{ ++ struct ssam_request rqst; ++ struct ssam_response rsp; ++ ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.instance_id = shid->uid.instance; ++ rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT; ++ rqst.flags = 0; ++ rqst.length = sizeof(rprt_id); ++ rqst.payload = &rprt_id; ++ ++ rsp.capacity = len; ++ rsp.length = 0; ++ rsp.pointer = buf; ++ ++ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id)); ++} ++ ++static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event) ++{ ++ struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif); ++ ++ if (event->command_id != 0x00) ++ return 0; ++ ++ hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0); ++ return SSAM_NOTIF_HANDLED; ++} ++ ++ ++/* -- Transport driver. ----------------------------------------------------- */ ++ ++static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) ++{ ++ int status; ++ ++ status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len); ++ return status >= 0 ? len : status; ++} ++ ++static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) ++{ ++ int status; ++ ++ status = ssam_hid_get_raw_report(shid, rprt_id, buf, len); ++ return status >= 0 ? len : status; ++} ++ ++static int shid_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) ++{ ++ int status; ++ ++ status = ssam_hid_set_raw_report(shid, rprt_id, true, buf, len); ++ return status >= 0 ? len : status; ++} ++ ++ ++/* -- Driver setup. --------------------------------------------------------- */ ++ ++static int surface_hid_probe(struct ssam_device *sdev) ++{ ++ struct surface_hid_device *shid; ++ ++ shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL); ++ if (!shid) ++ return -ENOMEM; ++ ++ shid->dev = &sdev->dev; ++ shid->ctrl = sdev->ctrl; ++ shid->uid = sdev->uid; ++ ++ shid->notif.base.priority = 1; ++ shid->notif.base.fn = ssam_hid_event_fn; ++ shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG; ++ shid->notif.event.id.target_category = sdev->uid.category; ++ shid->notif.event.id.instance = sdev->uid.instance; ++ shid->notif.event.mask = SSAM_EVENT_MASK_STRICT; ++ shid->notif.event.flags = 0; ++ ++ shid->ops.get_descriptor = ssam_hid_get_descriptor; ++ shid->ops.output_report = shid_output_report; ++ shid->ops.get_feature_report = shid_get_feature_report; ++ shid->ops.set_feature_report = shid_set_feature_report; ++ ++ ssam_device_set_drvdata(sdev, shid); ++ return surface_hid_device_add(shid); ++} ++ ++static void surface_hid_remove(struct ssam_device *sdev) ++{ ++ surface_hid_device_destroy(ssam_device_get_drvdata(sdev)); ++} ++ ++static const struct ssam_device_id surface_hid_match[] = { ++ { SSAM_SDEV(HID, 0x02, SSAM_ANY_IID, 0x00) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_hid_match); ++ ++static struct ssam_device_driver surface_hid_driver = { ++ .probe = surface_hid_probe, ++ .remove = surface_hid_remove, ++ .match_table = surface_hid_match, ++ .driver = { ++ .name = "surface_hid", ++ .pm = &surface_hid_pm_ops, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(surface_hid_driver); ++ ++MODULE_AUTHOR("Blaž Hrastnik "); ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c +new file mode 100644 +index 000000000000..7b27ec392232 +--- /dev/null ++++ b/drivers/hid/surface-hid/surface_hid_core.c +@@ -0,0 +1,272 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Common/core components for the Surface System Aggregator Module (SSAM) HID ++ * transport driver. Provides support for integrated HID devices on Microsoft ++ * Surface models. ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "surface_hid_core.h" ++ ++ ++/* -- Device descriptor access. --------------------------------------------- */ ++ ++static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) ++{ ++ int status; ++ ++ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, ++ (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); ++ if (status) ++ return status; ++ ++ if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) { ++ dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n", ++ shid->hid_desc.desc_len, sizeof(shid->hid_desc)); ++ return -EPROTO; ++ } ++ ++ if (shid->hid_desc.desc_type != HID_DT_HID) { ++ dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n", ++ shid->hid_desc.desc_type, HID_DT_HID); ++ return -EPROTO; ++ } ++ ++ if (shid->hid_desc.num_descriptors != 1) { ++ dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n", ++ shid->hid_desc.num_descriptors); ++ return -EPROTO; ++ } ++ ++ if (shid->hid_desc.report_desc_type != HID_DT_REPORT) { ++ dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n", ++ shid->hid_desc.report_desc_type, HID_DT_REPORT); ++ return -EPROTO; ++ } ++ ++ return 0; ++} ++ ++static int surface_hid_load_device_attributes(struct surface_hid_device *shid) ++{ ++ int status; ++ ++ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, ++ (u8 *)&shid->attrs, sizeof(shid->attrs)); ++ if (status) ++ return status; ++ ++ if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) { ++ dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n", ++ get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs)); ++ return -EPROTO; ++ } ++ ++ return 0; ++} ++ ++ ++/* -- Transport driver (common). -------------------------------------------- */ ++ ++static int surface_hid_start(struct hid_device *hid) ++{ ++ struct surface_hid_device *shid = hid->driver_data; ++ ++ return ssam_notifier_register(shid->ctrl, &shid->notif); ++} ++ ++static void surface_hid_stop(struct hid_device *hid) ++{ ++ struct surface_hid_device *shid = hid->driver_data; ++ ++ /* Note: This call will log errors for us, so ignore them here. */ ++ ssam_notifier_unregister(shid->ctrl, &shid->notif); ++} ++ ++static int surface_hid_open(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void surface_hid_close(struct hid_device *hid) ++{ ++} ++ ++static int surface_hid_parse(struct hid_device *hid) ++{ ++ struct surface_hid_device *shid = hid->driver_data; ++ size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len); ++ u8 *buf; ++ int status; ++ ++ buf = kzalloc(len, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len); ++ if (!status) ++ status = hid_parse_report(hid, buf, len); ++ ++ kfree(buf); ++ return status; ++} ++ ++static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf, ++ size_t len, unsigned char rtype, int reqtype) ++{ ++ struct surface_hid_device *shid = hid->driver_data; ++ ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) ++ return shid->ops.output_report(shid, reportnum, buf, len); ++ ++ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) ++ return shid->ops.get_feature_report(shid, reportnum, buf, len); ++ ++ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) ++ return shid->ops.set_feature_report(shid, reportnum, buf, len); ++ ++ return -EIO; ++} ++ ++static struct hid_ll_driver surface_hid_ll_driver = { ++ .start = surface_hid_start, ++ .stop = surface_hid_stop, ++ .open = surface_hid_open, ++ .close = surface_hid_close, ++ .parse = surface_hid_parse, ++ .raw_request = surface_hid_raw_request, ++}; ++ ++ ++/* -- Common device setup. -------------------------------------------------- */ ++ ++int surface_hid_device_add(struct surface_hid_device *shid) ++{ ++ int status; ++ ++ status = surface_hid_load_hid_descriptor(shid); ++ if (status) ++ return status; ++ ++ status = surface_hid_load_device_attributes(shid); ++ if (status) ++ return status; ++ ++ shid->hid = hid_allocate_device(); ++ if (IS_ERR(shid->hid)) ++ return PTR_ERR(shid->hid); ++ ++ shid->hid->dev.parent = shid->dev; ++ shid->hid->bus = BUS_HOST; ++ shid->hid->vendor = cpu_to_le16(shid->attrs.vendor); ++ shid->hid->product = cpu_to_le16(shid->attrs.product); ++ shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version); ++ shid->hid->country = shid->hid_desc.country_code; ++ ++ snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X", ++ shid->hid->vendor, shid->hid->product); ++ ++ strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys)); ++ ++ shid->hid->driver_data = shid; ++ shid->hid->ll_driver = &surface_hid_ll_driver; ++ ++ status = hid_add_device(shid->hid); ++ if (status) ++ hid_destroy_device(shid->hid); ++ ++ return status; ++} ++EXPORT_SYMBOL_GPL(surface_hid_device_add); ++ ++void surface_hid_device_destroy(struct surface_hid_device *shid) ++{ ++ hid_destroy_device(shid->hid); ++} ++EXPORT_SYMBOL_GPL(surface_hid_device_destroy); ++ ++ ++/* -- PM ops. --------------------------------------------------------------- */ ++ ++#ifdef CONFIG_PM_SLEEP ++ ++static int surface_hid_suspend(struct device *dev) ++{ ++ struct surface_hid_device *d = dev_get_drvdata(dev); ++ ++ if (d->hid->driver && d->hid->driver->suspend) ++ return d->hid->driver->suspend(d->hid, PMSG_SUSPEND); ++ ++ return 0; ++} ++ ++static int surface_hid_resume(struct device *dev) ++{ ++ struct surface_hid_device *d = dev_get_drvdata(dev); ++ ++ if (d->hid->driver && d->hid->driver->resume) ++ return d->hid->driver->resume(d->hid); ++ ++ return 0; ++} ++ ++static int surface_hid_freeze(struct device *dev) ++{ ++ struct surface_hid_device *d = dev_get_drvdata(dev); ++ ++ if (d->hid->driver && d->hid->driver->suspend) ++ return d->hid->driver->suspend(d->hid, PMSG_FREEZE); ++ ++ return 0; ++} ++ ++static int surface_hid_poweroff(struct device *dev) ++{ ++ struct surface_hid_device *d = dev_get_drvdata(dev); ++ ++ if (d->hid->driver && d->hid->driver->suspend) ++ return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE); ++ ++ return 0; ++} ++ ++static int surface_hid_restore(struct device *dev) ++{ ++ struct surface_hid_device *d = dev_get_drvdata(dev); ++ ++ if (d->hid->driver && d->hid->driver->reset_resume) ++ return d->hid->driver->reset_resume(d->hid); ++ ++ return 0; ++} ++ ++const struct dev_pm_ops surface_hid_pm_ops = { ++ .freeze = surface_hid_freeze, ++ .thaw = surface_hid_resume, ++ .suspend = surface_hid_suspend, ++ .resume = surface_hid_resume, ++ .poweroff = surface_hid_poweroff, ++ .restore = surface_hid_restore, ++}; ++EXPORT_SYMBOL_GPL(surface_hid_pm_ops); ++ ++#else /* CONFIG_PM_SLEEP */ ++ ++const struct dev_pm_ops surface_hid_pm_ops = { }; ++EXPORT_SYMBOL_GPL(surface_hid_pm_ops); ++ ++#endif /* CONFIG_PM_SLEEP */ ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hid/surface-hid/surface_hid_core.h b/drivers/hid/surface-hid/surface_hid_core.h +new file mode 100644 +index 000000000000..4b1a7b57e035 +--- /dev/null ++++ b/drivers/hid/surface-hid/surface_hid_core.h +@@ -0,0 +1,77 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Common/core components for the Surface System Aggregator Module (SSAM) HID ++ * transport driver. Provides support for integrated HID devices on Microsoft ++ * Surface models. ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#ifndef SURFACE_HID_CORE_H ++#define SURFACE_HID_CORE_H ++ ++#include ++#include ++#include ++ ++#include ++#include ++ ++enum surface_hid_descriptor_entry { ++ SURFACE_HID_DESC_HID = 0, ++ SURFACE_HID_DESC_REPORT = 1, ++ SURFACE_HID_DESC_ATTRS = 2, ++}; ++ ++struct surface_hid_descriptor { ++ __u8 desc_len; /* = 9 */ ++ __u8 desc_type; /* = HID_DT_HID */ ++ __le16 hid_version; ++ __u8 country_code; ++ __u8 num_descriptors; /* = 1 */ ++ ++ __u8 report_desc_type; /* = HID_DT_REPORT */ ++ __le16 report_desc_len; ++} __packed; ++ ++static_assert(sizeof(struct surface_hid_descriptor) == 9); ++ ++struct surface_hid_attributes { ++ __le32 length; ++ __le16 vendor; ++ __le16 product; ++ __le16 version; ++ __u8 _unknown[22]; ++} __packed; ++ ++static_assert(sizeof(struct surface_hid_attributes) == 32); ++ ++struct surface_hid_device; ++ ++struct surface_hid_device_ops { ++ int (*get_descriptor)(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len); ++ int (*output_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); ++ int (*get_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); ++ int (*set_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); ++}; ++ ++struct surface_hid_device { ++ struct device *dev; ++ struct ssam_controller *ctrl; ++ struct ssam_device_uid uid; ++ ++ struct surface_hid_descriptor hid_desc; ++ struct surface_hid_attributes attrs; ++ ++ struct ssam_event_notifier notif; ++ struct hid_device *hid; ++ ++ struct surface_hid_device_ops ops; ++}; ++ ++int surface_hid_device_add(struct surface_hid_device *shid); ++void surface_hid_device_destroy(struct surface_hid_device *shid); ++ ++extern const struct dev_pm_ops surface_hid_pm_ops; ++ ++#endif /* SURFACE_HID_CORE_H */ +-- +2.31.1 + +From 0b9f3fca97a121daae91fd91364f41c844f229e8 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Wed, 10 Mar 2021 23:53:29 +0100 +Subject: [PATCH] HID: surface-hid: Add support for legacy keyboard interface + +Add support for the legacy keyboard (KBD/TC=0x08) HID transport layer of +the Surface System Aggregator Module (SSAM) to the Surface HID driver. +On Surface Laptops 1 and 2, this interface is used to connect the +integrated keyboard. + +Note that this subsystem interface essentially provides a limited HID +transport layer. In contrast to the generic HID interface (TC=0x15) used +on newer Surface models, this interface only allows (as far as we know) +for a single device to be connected and is otherwise severely limited in +terms of support for feature- and output-reports. Specifically, only +caps-lock-LED output-reports and a single read-only feature-report are +supported. + +Signed-off-by: Maximilian Luz +Signed-off-by: Jiri Kosina +Patchset: surface-sam +--- + drivers/hid/surface-hid/Kconfig | 14 ++ + drivers/hid/surface-hid/Makefile | 1 + + drivers/hid/surface-hid/surface_kbd.c | 300 ++++++++++++++++++++++++++ + 3 files changed, 315 insertions(+) + create mode 100644 drivers/hid/surface-hid/surface_kbd.c + +diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig +index 642c7f0e64fe..7ce9b5d641eb 100644 +--- a/drivers/hid/surface-hid/Kconfig ++++ b/drivers/hid/surface-hid/Kconfig +@@ -21,6 +21,20 @@ config SURFACE_HID + integrated touchpad and keyboard, on 7th generation Microsoft Surface + models. + ++config SURFACE_KBD ++ tristate "HID keyboard transport driver for Surface System Aggregator Module" ++ select SURFACE_HID_CORE ++ help ++ Driver to support HID keyboards on Surface Laptop 1 and 2 devices. ++ ++ This driver provides support for the HID transport protocol provided ++ by the Surface Aggregator Module (i.e. the embedded controller) on ++ Microsoft Surface Laptops 1 and 2. It is used to connect the ++ integrated keyboard on those devices. ++ ++ Say M or Y here, if you want support for the integrated keyboard on ++ Microsoft Surface Laptops 1 and 2. ++ + endmenu + + config SURFACE_HID_CORE +diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile +index 62fc04632d3d..4ae11cf09b25 100644 +--- a/drivers/hid/surface-hid/Makefile ++++ b/drivers/hid/surface-hid/Makefile +@@ -4,3 +4,4 @@ + # + obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o + obj-$(CONFIG_SURFACE_HID) += surface_hid.o ++obj-$(CONFIG_SURFACE_KBD) += surface_kbd.o +diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c +new file mode 100644 +index 000000000000..0635341bc517 +--- /dev/null ++++ b/drivers/hid/surface-hid/surface_kbd.c +@@ -0,0 +1,300 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Surface System Aggregator Module (SSAM) HID transport driver for the legacy ++ * keyboard interface (KBD/TC=0x08 subsystem). Provides support for the ++ * integrated HID keyboard on Surface Laptops 1 and 2. ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "surface_hid_core.h" ++ ++ ++/* -- SAM interface (KBD). -------------------------------------------------- */ ++ ++#define KBD_FEATURE_REPORT_SIZE 7 /* 6 + report ID */ ++ ++enum surface_kbd_cid { ++ SURFACE_KBD_CID_GET_DESCRIPTOR = 0x00, ++ SURFACE_KBD_CID_SET_CAPSLOCK_LED = 0x01, ++ SURFACE_KBD_CID_EVT_INPUT_GENERIC = 0x03, ++ SURFACE_KBD_CID_EVT_INPUT_HOTKEYS = 0x04, ++ SURFACE_KBD_CID_GET_FEATURE_REPORT = 0x0b, ++}; ++ ++static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len) ++{ ++ struct ssam_request rqst; ++ struct ssam_response rsp; ++ int status; ++ ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR; ++ rqst.instance_id = shid->uid.instance; ++ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; ++ rqst.length = sizeof(entry); ++ rqst.payload = &entry; ++ ++ rsp.capacity = len; ++ rsp.length = 0; ++ rsp.pointer = buf; ++ ++ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry)); ++ if (status) ++ return status; ++ ++ if (rsp.length != len) { ++ dev_err(shid->dev, "invalid descriptor length: got %zu, expected, %zu\n", ++ rsp.length, len); ++ return -EPROTO; ++ } ++ ++ return 0; ++} ++ ++static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value) ++{ ++ struct ssam_request rqst; ++ u8 value_u8 = value; ++ ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED; ++ rqst.instance_id = shid->uid.instance; ++ rqst.flags = 0; ++ rqst.length = sizeof(value_u8); ++ rqst.payload = &value_u8; ++ ++ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8)); ++} ++ ++static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len) ++{ ++ struct ssam_request rqst; ++ struct ssam_response rsp; ++ u8 payload = 0; ++ int status; ++ ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT; ++ rqst.instance_id = shid->uid.instance; ++ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; ++ rqst.length = sizeof(payload); ++ rqst.payload = &payload; ++ ++ rsp.capacity = len; ++ rsp.length = 0; ++ rsp.pointer = buf; ++ ++ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload)); ++ if (status) ++ return status; ++ ++ if (rsp.length != len) { ++ dev_err(shid->dev, "invalid feature report length: got %zu, expected, %zu\n", ++ rsp.length, len); ++ return -EPROTO; ++ } ++ ++ return 0; ++} ++ ++static bool ssam_kbd_is_input_event(const struct ssam_event *event) ++{ ++ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC) ++ return true; ++ ++ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS) ++ return true; ++ ++ return false; ++} ++ ++static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event) ++{ ++ struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif); ++ ++ /* ++ * Check against device UID manually, as registry and device target ++ * category doesn't line up. ++ */ ++ ++ if (shid->uid.category != event->target_category) ++ return 0; ++ ++ if (shid->uid.target != event->target_id) ++ return 0; ++ ++ if (shid->uid.instance != event->instance_id) ++ return 0; ++ ++ if (!ssam_kbd_is_input_event(event)) ++ return 0; ++ ++ hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0); ++ return SSAM_NOTIF_HANDLED; ++} ++ ++ ++/* -- Transport driver (KBD). ----------------------------------------------- */ ++ ++static int skbd_get_caps_led_value(struct hid_device *hid, u8 rprt_id, u8 *buf, size_t len) ++{ ++ struct hid_field *field; ++ unsigned int offset, size; ++ int i; ++ ++ /* Get LED field. */ ++ field = hidinput_get_led_field(hid); ++ if (!field) ++ return -ENOENT; ++ ++ /* Check if we got the correct report. */ ++ if (len != hid_report_len(field->report)) ++ return -ENOENT; ++ ++ if (rprt_id != field->report->id) ++ return -ENOENT; ++ ++ /* Get caps lock LED index. */ ++ for (i = 0; i < field->report_count; i++) ++ if ((field->usage[i].hid & 0xffff) == 0x02) ++ break; ++ ++ if (i == field->report_count) ++ return -ENOENT; ++ ++ /* Extract value. */ ++ size = field->report_size; ++ offset = field->report_offset + i * size; ++ return !!hid_field_extract(hid, buf + 1, size, offset); ++} ++ ++static int skbd_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) ++{ ++ int caps_led; ++ int status; ++ ++ caps_led = skbd_get_caps_led_value(shid->hid, rprt_id, buf, len); ++ if (caps_led < 0) ++ return -EIO; /* Only caps LED output reports are supported. */ ++ ++ status = ssam_kbd_set_caps_led(shid, caps_led); ++ if (status < 0) ++ return status; ++ ++ return len; ++} ++ ++static int skbd_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) ++{ ++ u8 report[KBD_FEATURE_REPORT_SIZE]; ++ int status; ++ ++ /* ++ * The keyboard only has a single hard-coded read-only feature report ++ * of size KBD_FEATURE_REPORT_SIZE. Try to load it and compare its ++ * report ID against the requested one. ++ */ ++ ++ if (len < ARRAY_SIZE(report)) ++ return -ENOSPC; ++ ++ status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report)); ++ if (status < 0) ++ return status; ++ ++ if (rprt_id != report[0]) ++ return -ENOENT; ++ ++ memcpy(buf, report, ARRAY_SIZE(report)); ++ return len; ++} ++ ++static int skbd_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) ++{ ++ /* Not supported. See skbd_get_feature_report() for details. */ ++ return -EIO; ++} ++ ++ ++/* -- Driver setup. --------------------------------------------------------- */ ++ ++static int surface_kbd_probe(struct platform_device *pdev) ++{ ++ struct ssam_controller *ctrl; ++ struct surface_hid_device *shid; ++ ++ /* Add device link to EC. */ ++ ctrl = ssam_client_bind(&pdev->dev); ++ if (IS_ERR(ctrl)) ++ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); ++ ++ shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL); ++ if (!shid) ++ return -ENOMEM; ++ ++ shid->dev = &pdev->dev; ++ shid->ctrl = ctrl; ++ ++ shid->uid.domain = SSAM_DOMAIN_SERIALHUB; ++ shid->uid.category = SSAM_SSH_TC_KBD; ++ shid->uid.target = 2; ++ shid->uid.instance = 0; ++ shid->uid.function = 0; ++ ++ shid->notif.base.priority = 1; ++ shid->notif.base.fn = ssam_kbd_event_fn; ++ shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; ++ shid->notif.event.id.target_category = shid->uid.category; ++ shid->notif.event.id.instance = shid->uid.instance; ++ shid->notif.event.mask = SSAM_EVENT_MASK_NONE; ++ shid->notif.event.flags = 0; ++ ++ shid->ops.get_descriptor = ssam_kbd_get_descriptor; ++ shid->ops.output_report = skbd_output_report; ++ shid->ops.get_feature_report = skbd_get_feature_report; ++ shid->ops.set_feature_report = skbd_set_feature_report; ++ ++ platform_set_drvdata(pdev, shid); ++ return surface_hid_device_add(shid); ++} ++ ++static int surface_kbd_remove(struct platform_device *pdev) ++{ ++ surface_hid_device_destroy(platform_get_drvdata(pdev)); ++ return 0; ++} ++ ++static const struct acpi_device_id surface_kbd_match[] = { ++ { "MSHW0096" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, surface_kbd_match); ++ ++static struct platform_driver surface_kbd_driver = { ++ .probe = surface_kbd_probe, ++ .remove = surface_kbd_remove, ++ .driver = { ++ .name = "surface_keyboard", ++ .acpi_match_table = surface_kbd_match, ++ .pm = &surface_hid_pm_ops, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(surface_kbd_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.31.1 + +From b533b1fbc3f818c8bd2bb2967093deb3ce665ea5 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 23 Apr 2021 00:51:22 +0200 +Subject: [PATCH] HID: surface-hid: Fix integer endian conversion + +We want to convert from 16 bit (unsigned) little endian values contained +in a packed struct to CPU native endian values here, not the other way +around. So replace cpu_to_le16() with get_unaligned_le16(), using the +latter instead of le16_to_cpu() to acknowledge that we are reading from +a packed struct. + +Reported-by: kernel test robot +Fixes: b05ff1002a5c ("HID: Add support for Surface Aggregator Module HID transport") +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/hid/surface-hid/surface_hid_core.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c +index 7b27ec392232..5571e74abe91 100644 +--- a/drivers/hid/surface-hid/surface_hid_core.c ++++ b/drivers/hid/surface-hid/surface_hid_core.c +@@ -168,9 +168,9 @@ int surface_hid_device_add(struct surface_hid_device *shid) + + shid->hid->dev.parent = shid->dev; + shid->hid->bus = BUS_HOST; +- shid->hid->vendor = cpu_to_le16(shid->attrs.vendor); +- shid->hid->product = cpu_to_le16(shid->attrs.product); +- shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version); ++ shid->hid->vendor = get_unaligned_le16(&shid->attrs.vendor); ++ shid->hid->product = get_unaligned_le16(&shid->attrs.product); ++ shid->hid->version = get_unaligned_le16(&shid->hid_desc.hid_version); + shid->hid->country = shid->hid_desc.country_code; + + snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X", +-- +2.31.1 + +From da3b800dc4b1ca234c239cf30a0d5bfbf3540e53 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Tue, 6 Apr 2021 01:41:25 +0200 +Subject: [PATCH] power: supply: Add battery driver for Surface Aggregator + Module + +On newer Microsoft Surface models (specifically 7th-generation, i.e. +Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go), +battery and AC status/information is no longer handled via standard ACPI +devices, but instead directly via the Surface System Aggregator Module +(SSAM), i.e. the embedded controller on those devices. + +While on previous generation models, battery status is also handled via +SSAM, an ACPI shim was present to translate the standard ACPI battery +interface to SSAM requests. The SSAM interface itself, which is modeled +closely after the ACPI interface, has not changed. + +This commit introduces a new SSAM client device driver to support +battery status/information via the aforementioned interface on said +Surface models. It is in parts based on the standard ACPI battery +driver. + +Signed-off-by: Maximilian Luz +Signed-off-by: Sebastian Reichel +Patchset: surface-sam +--- + .../ABI/testing/sysfs-class-power-surface | 15 + + MAINTAINERS | 7 + + drivers/power/supply/Kconfig | 16 + + drivers/power/supply/Makefile | 1 + + drivers/power/supply/surface_battery.c | 865 ++++++++++++++++++ + 5 files changed, 904 insertions(+) + create mode 100644 Documentation/ABI/testing/sysfs-class-power-surface + create mode 100644 drivers/power/supply/surface_battery.c + +diff --git a/Documentation/ABI/testing/sysfs-class-power-surface b/Documentation/ABI/testing/sysfs-class-power-surface +new file mode 100644 +index 000000000000..79cde4dcf2f5 +--- /dev/null ++++ b/Documentation/ABI/testing/sysfs-class-power-surface +@@ -0,0 +1,15 @@ ++What: /sys/class/power_supply//alarm ++Date: April 2021 ++KernelVersion: 5.13 ++Contact: Maximilian Luz ++Description: ++ Battery trip point. When the remaining battery capacity crosses this ++ value in either direction, the system will be notified and if ++ necessary woken. ++ ++ Set to zero to clear/disable. ++ ++ Access: Read, Write ++ ++ Valid values: In micro-Wh or micro-Ah, depending on the power unit ++ of the battery +diff --git a/MAINTAINERS b/MAINTAINERS +index f54b22333ec6..7ee93b732270 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -11868,6 +11868,13 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch] + F: include/linux/cciss*.h + F: include/uapi/linux/cciss*.h + ++MICROSOFT SURFACE BATTERY AND AC DRIVERS ++M: Maximilian Luz ++L: linux-pm@vger.kernel.org ++L: platform-driver-x86@vger.kernel.org ++S: Maintained ++F: drivers/power/supply/surface_battery.c ++ + MICROSOFT SURFACE DTX DRIVER + M: Maximilian Luz + L: platform-driver-x86@vger.kernel.org +diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig +index 006b95eca673..cebeff10d543 100644 +--- a/drivers/power/supply/Kconfig ++++ b/drivers/power/supply/Kconfig +@@ -801,4 +801,20 @@ config BATTERY_ACER_A500 + help + Say Y to include support for Acer Iconia Tab A500 battery fuel gauge. + ++config BATTERY_SURFACE ++ tristate "Battery driver for 7th-generation Microsoft Surface devices" ++ depends on SURFACE_AGGREGATOR_REGISTRY ++ help ++ Driver for battery devices connected via/managed by the Surface System ++ Aggregator Module (SSAM). ++ ++ This driver provides battery-information and -status support for ++ Surface devices where said data is not exposed via the standard ACPI ++ devices. On those models (7th-generation), battery-information is ++ instead handled directly via SSAM client devices and this driver. ++ ++ Say M or Y here to include battery status support for 7th-generation ++ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, ++ Surface Book 3, and Surface Laptop Go. ++ + endif # POWER_SUPPLY +diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile +index 5e5fdbbef531..134041538d2c 100644 +--- a/drivers/power/supply/Makefile ++++ b/drivers/power/supply/Makefile +@@ -101,3 +101,4 @@ obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o + obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o + obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o + obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o ++obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o +diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c +new file mode 100644 +index 000000000000..4116dd839ecd +--- /dev/null ++++ b/drivers/power/supply/surface_battery.c +@@ -0,0 +1,865 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Battery driver for 7th-generation Microsoft Surface devices via Surface ++ * System Aggregator Module (SSAM). ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++ ++/* -- SAM interface. -------------------------------------------------------- */ ++ ++enum sam_event_cid_bat { ++ SAM_EVENT_CID_BAT_BIX = 0x15, ++ SAM_EVENT_CID_BAT_BST = 0x16, ++ SAM_EVENT_CID_BAT_ADP = 0x17, ++ SAM_EVENT_CID_BAT_PROT = 0x18, ++ SAM_EVENT_CID_BAT_DPTF = 0x53, ++}; ++ ++enum sam_battery_sta { ++ SAM_BATTERY_STA_OK = 0x0f, ++ SAM_BATTERY_STA_PRESENT = 0x10, ++}; ++ ++enum sam_battery_state { ++ SAM_BATTERY_STATE_DISCHARGING = BIT(0), ++ SAM_BATTERY_STATE_CHARGING = BIT(1), ++ SAM_BATTERY_STATE_CRITICAL = BIT(2), ++}; ++ ++enum sam_battery_power_unit { ++ SAM_BATTERY_POWER_UNIT_mW = 0, ++ SAM_BATTERY_POWER_UNIT_mA = 1, ++}; ++ ++/* Equivalent to data returned in ACPI _BIX method, revision 0. */ ++struct spwr_bix { ++ u8 revision; ++ __le32 power_unit; ++ __le32 design_cap; ++ __le32 last_full_charge_cap; ++ __le32 technology; ++ __le32 design_voltage; ++ __le32 design_cap_warn; ++ __le32 design_cap_low; ++ __le32 cycle_count; ++ __le32 measurement_accuracy; ++ __le32 max_sampling_time; ++ __le32 min_sampling_time; ++ __le32 max_avg_interval; ++ __le32 min_avg_interval; ++ __le32 bat_cap_granularity_1; ++ __le32 bat_cap_granularity_2; ++ __u8 model[21]; ++ __u8 serial[11]; ++ __u8 type[5]; ++ __u8 oem_info[21]; ++} __packed; ++ ++static_assert(sizeof(struct spwr_bix) == 119); ++ ++/* Equivalent to data returned in ACPI _BST method. */ ++struct spwr_bst { ++ __le32 state; ++ __le32 present_rate; ++ __le32 remaining_cap; ++ __le32 present_voltage; ++} __packed; ++ ++static_assert(sizeof(struct spwr_bst) == 16); ++ ++#define SPWR_BIX_REVISION 0 ++#define SPWR_BATTERY_VALUE_UNKNOWN 0xffffffff ++ ++/* Get battery status (_STA) */ ++SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { ++ .target_category = SSAM_SSH_TC_BAT, ++ .command_id = 0x01, ++}); ++ ++/* Get battery static information (_BIX). */ ++SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bix, struct spwr_bix, { ++ .target_category = SSAM_SSH_TC_BAT, ++ .command_id = 0x02, ++}); ++ ++/* Get battery dynamic information (_BST). */ ++SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bst, struct spwr_bst, { ++ .target_category = SSAM_SSH_TC_BAT, ++ .command_id = 0x03, ++}); ++ ++/* Set battery trip point (_BTP). */ ++SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, { ++ .target_category = SSAM_SSH_TC_BAT, ++ .command_id = 0x04, ++}); ++ ++ ++/* -- Device structures. ---------------------------------------------------- */ ++ ++struct spwr_psy_properties { ++ const char *name; ++ struct ssam_event_registry registry; ++}; ++ ++struct spwr_battery_device { ++ struct ssam_device *sdev; ++ ++ char name[32]; ++ struct power_supply *psy; ++ struct power_supply_desc psy_desc; ++ ++ struct delayed_work update_work; ++ ++ struct ssam_event_notifier notif; ++ ++ struct mutex lock; /* Guards access to state data below. */ ++ unsigned long timestamp; ++ ++ __le32 sta; ++ struct spwr_bix bix; ++ struct spwr_bst bst; ++ u32 alarm; ++}; ++ ++ ++/* -- Module parameters. ---------------------------------------------------- */ ++ ++static unsigned int cache_time = 1000; ++module_param(cache_time, uint, 0644); ++MODULE_PARM_DESC(cache_time, "battery state caching time in milliseconds [default: 1000]"); ++ ++ ++/* -- State management. ----------------------------------------------------- */ ++ ++/* ++ * Delay for battery update quirk. See spwr_external_power_changed() below ++ * for more details. ++ */ ++#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000) ++ ++static bool spwr_battery_present(struct spwr_battery_device *bat) ++{ ++ lockdep_assert_held(&bat->lock); ++ ++ return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT; ++} ++ ++static int spwr_battery_load_sta(struct spwr_battery_device *bat) ++{ ++ lockdep_assert_held(&bat->lock); ++ ++ return ssam_retry(ssam_bat_get_sta, bat->sdev, &bat->sta); ++} ++ ++static int spwr_battery_load_bix(struct spwr_battery_device *bat) ++{ ++ int status; ++ ++ lockdep_assert_held(&bat->lock); ++ ++ if (!spwr_battery_present(bat)) ++ return 0; ++ ++ status = ssam_retry(ssam_bat_get_bix, bat->sdev, &bat->bix); ++ ++ /* Enforce NULL terminated strings in case anything goes wrong... */ ++ bat->bix.model[ARRAY_SIZE(bat->bix.model) - 1] = 0; ++ bat->bix.serial[ARRAY_SIZE(bat->bix.serial) - 1] = 0; ++ bat->bix.type[ARRAY_SIZE(bat->bix.type) - 1] = 0; ++ bat->bix.oem_info[ARRAY_SIZE(bat->bix.oem_info) - 1] = 0; ++ ++ return status; ++} ++ ++static int spwr_battery_load_bst(struct spwr_battery_device *bat) ++{ ++ lockdep_assert_held(&bat->lock); ++ ++ if (!spwr_battery_present(bat)) ++ return 0; ++ ++ return ssam_retry(ssam_bat_get_bst, bat->sdev, &bat->bst); ++} ++ ++static int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value) ++{ ++ __le32 value_le = cpu_to_le32(value); ++ ++ lockdep_assert_held(&bat->lock); ++ ++ bat->alarm = value; ++ return ssam_retry(ssam_bat_set_btp, bat->sdev, &value_le); ++} ++ ++static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached) ++{ ++ unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time); ++ int status; ++ ++ lockdep_assert_held(&bat->lock); ++ ++ if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline)) ++ return 0; ++ ++ status = spwr_battery_load_sta(bat); ++ if (status) ++ return status; ++ ++ status = spwr_battery_load_bst(bat); ++ if (status) ++ return status; ++ ++ bat->timestamp = jiffies; ++ return 0; ++} ++ ++static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached) ++{ ++ int status; ++ ++ mutex_lock(&bat->lock); ++ status = spwr_battery_update_bst_unlocked(bat, cached); ++ mutex_unlock(&bat->lock); ++ ++ return status; ++} ++ ++static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat) ++{ ++ int status; ++ ++ lockdep_assert_held(&bat->lock); ++ ++ status = spwr_battery_load_sta(bat); ++ if (status) ++ return status; ++ ++ status = spwr_battery_load_bix(bat); ++ if (status) ++ return status; ++ ++ status = spwr_battery_load_bst(bat); ++ if (status) ++ return status; ++ ++ if (bat->bix.revision != SPWR_BIX_REVISION) ++ dev_warn(&bat->sdev->dev, "unsupported battery revision: %u\n", bat->bix.revision); ++ ++ bat->timestamp = jiffies; ++ return 0; ++} ++ ++static u32 sprw_battery_get_full_cap_safe(struct spwr_battery_device *bat) ++{ ++ u32 full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); ++ ++ lockdep_assert_held(&bat->lock); ++ ++ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN) ++ full_cap = get_unaligned_le32(&bat->bix.design_cap); ++ ++ return full_cap; ++} ++ ++static bool spwr_battery_is_full(struct spwr_battery_device *bat) ++{ ++ u32 state = get_unaligned_le32(&bat->bst.state); ++ u32 full_cap = sprw_battery_get_full_cap_safe(bat); ++ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); ++ ++ lockdep_assert_held(&bat->lock); ++ ++ return full_cap != SPWR_BATTERY_VALUE_UNKNOWN && full_cap != 0 && ++ remaining_cap != SPWR_BATTERY_VALUE_UNKNOWN && ++ remaining_cap >= full_cap && ++ state == 0; ++} ++ ++static int spwr_battery_recheck_full(struct spwr_battery_device *bat) ++{ ++ bool present; ++ u32 unit; ++ int status; ++ ++ mutex_lock(&bat->lock); ++ unit = get_unaligned_le32(&bat->bix.power_unit); ++ present = spwr_battery_present(bat); ++ ++ status = spwr_battery_update_bix_unlocked(bat); ++ if (status) ++ goto out; ++ ++ /* If battery has been attached, (re-)initialize alarm. */ ++ if (!present && spwr_battery_present(bat)) { ++ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); ++ ++ status = spwr_battery_set_alarm_unlocked(bat, cap_warn); ++ if (status) ++ goto out; ++ } ++ ++ /* ++ * Warn if the unit has changed. This is something we genuinely don't ++ * expect to happen, so make this a big warning. If it does, we'll ++ * need to add support for it. ++ */ ++ WARN_ON(unit != get_unaligned_le32(&bat->bix.power_unit)); ++ ++out: ++ mutex_unlock(&bat->lock); ++ ++ if (!status) ++ power_supply_changed(bat->psy); ++ ++ return status; ++} ++ ++static int spwr_battery_recheck_status(struct spwr_battery_device *bat) ++{ ++ int status; ++ ++ status = spwr_battery_update_bst(bat, false); ++ if (!status) ++ power_supply_changed(bat->psy); ++ ++ return status; ++} ++ ++static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_event *event) ++{ ++ struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif); ++ int status; ++ ++ dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", ++ event->command_id, event->instance_id, event->target_id); ++ ++ switch (event->command_id) { ++ case SAM_EVENT_CID_BAT_BIX: ++ status = spwr_battery_recheck_full(bat); ++ break; ++ ++ case SAM_EVENT_CID_BAT_BST: ++ status = spwr_battery_recheck_status(bat); ++ break; ++ ++ case SAM_EVENT_CID_BAT_PROT: ++ /* ++ * TODO: Implement support for battery protection status change ++ * event. ++ */ ++ status = 0; ++ break; ++ ++ case SAM_EVENT_CID_BAT_DPTF: ++ /* ++ * TODO: Implement support for DPTF event. ++ */ ++ status = 0; ++ break; ++ ++ default: ++ return 0; ++ } ++ ++ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; ++} ++ ++static void spwr_battery_update_bst_workfn(struct work_struct *work) ++{ ++ struct delayed_work *dwork = to_delayed_work(work); ++ struct spwr_battery_device *bat; ++ int status; ++ ++ bat = container_of(dwork, struct spwr_battery_device, update_work); ++ ++ status = spwr_battery_update_bst(bat, false); ++ if (status) { ++ dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", status); ++ return; ++ } ++ ++ power_supply_changed(bat->psy); ++} ++ ++static void spwr_external_power_changed(struct power_supply *psy) ++{ ++ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); ++ ++ /* ++ * Handle battery update quirk: When the battery is fully charged (or ++ * charged up to the limit imposed by the UEFI battery limit) and the ++ * adapter is plugged in or removed, the EC does not send a separate ++ * event for the state (charging/discharging) change. Furthermore it ++ * may take some time until the state is updated on the battery. ++ * Schedule an update to solve this. ++ */ ++ ++ schedule_delayed_work(&bat->update_work, SPWR_AC_BAT_UPDATE_DELAY); ++} ++ ++ ++/* -- Properties. ----------------------------------------------------------- */ ++ ++static const enum power_supply_property spwr_battery_props_chg[] = { ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_PRESENT, ++ POWER_SUPPLY_PROP_TECHNOLOGY, ++ POWER_SUPPLY_PROP_CYCLE_COUNT, ++ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, ++ POWER_SUPPLY_PROP_VOLTAGE_NOW, ++ POWER_SUPPLY_PROP_CURRENT_NOW, ++ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, ++ POWER_SUPPLY_PROP_CHARGE_FULL, ++ POWER_SUPPLY_PROP_CHARGE_NOW, ++ POWER_SUPPLY_PROP_CAPACITY, ++ POWER_SUPPLY_PROP_CAPACITY_LEVEL, ++ POWER_SUPPLY_PROP_MODEL_NAME, ++ POWER_SUPPLY_PROP_MANUFACTURER, ++ POWER_SUPPLY_PROP_SERIAL_NUMBER, ++}; ++ ++static const enum power_supply_property spwr_battery_props_eng[] = { ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_PRESENT, ++ POWER_SUPPLY_PROP_TECHNOLOGY, ++ POWER_SUPPLY_PROP_CYCLE_COUNT, ++ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, ++ POWER_SUPPLY_PROP_VOLTAGE_NOW, ++ POWER_SUPPLY_PROP_POWER_NOW, ++ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, ++ POWER_SUPPLY_PROP_ENERGY_FULL, ++ POWER_SUPPLY_PROP_ENERGY_NOW, ++ POWER_SUPPLY_PROP_CAPACITY, ++ POWER_SUPPLY_PROP_CAPACITY_LEVEL, ++ POWER_SUPPLY_PROP_MODEL_NAME, ++ POWER_SUPPLY_PROP_MANUFACTURER, ++ POWER_SUPPLY_PROP_SERIAL_NUMBER, ++}; ++ ++static int spwr_battery_prop_status(struct spwr_battery_device *bat) ++{ ++ u32 state = get_unaligned_le32(&bat->bst.state); ++ u32 present_rate = get_unaligned_le32(&bat->bst.present_rate); ++ ++ lockdep_assert_held(&bat->lock); ++ ++ if (state & SAM_BATTERY_STATE_DISCHARGING) ++ return POWER_SUPPLY_STATUS_DISCHARGING; ++ ++ if (state & SAM_BATTERY_STATE_CHARGING) ++ return POWER_SUPPLY_STATUS_CHARGING; ++ ++ if (spwr_battery_is_full(bat)) ++ return POWER_SUPPLY_STATUS_FULL; ++ ++ if (present_rate == 0) ++ return POWER_SUPPLY_STATUS_NOT_CHARGING; ++ ++ return POWER_SUPPLY_STATUS_UNKNOWN; ++} ++ ++static int spwr_battery_prop_technology(struct spwr_battery_device *bat) ++{ ++ lockdep_assert_held(&bat->lock); ++ ++ if (!strcasecmp("NiCd", bat->bix.type)) ++ return POWER_SUPPLY_TECHNOLOGY_NiCd; ++ ++ if (!strcasecmp("NiMH", bat->bix.type)) ++ return POWER_SUPPLY_TECHNOLOGY_NiMH; ++ ++ if (!strcasecmp("LION", bat->bix.type)) ++ return POWER_SUPPLY_TECHNOLOGY_LION; ++ ++ if (!strncasecmp("LI-ION", bat->bix.type, 6)) ++ return POWER_SUPPLY_TECHNOLOGY_LION; ++ ++ if (!strcasecmp("LiP", bat->bix.type)) ++ return POWER_SUPPLY_TECHNOLOGY_LIPO; ++ ++ return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; ++} ++ ++static int spwr_battery_prop_capacity(struct spwr_battery_device *bat) ++{ ++ u32 full_cap = sprw_battery_get_full_cap_safe(bat); ++ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); ++ ++ lockdep_assert_held(&bat->lock); ++ ++ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN) ++ return -ENODATA; ++ ++ if (remaining_cap == SPWR_BATTERY_VALUE_UNKNOWN) ++ return -ENODATA; ++ ++ return remaining_cap * 100 / full_cap; ++} ++ ++static int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat) ++{ ++ u32 state = get_unaligned_le32(&bat->bst.state); ++ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); ++ ++ lockdep_assert_held(&bat->lock); ++ ++ if (state & SAM_BATTERY_STATE_CRITICAL) ++ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; ++ ++ if (spwr_battery_is_full(bat)) ++ return POWER_SUPPLY_CAPACITY_LEVEL_FULL; ++ ++ if (remaining_cap <= bat->alarm) ++ return POWER_SUPPLY_CAPACITY_LEVEL_LOW; ++ ++ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; ++} ++ ++static int spwr_battery_get_property(struct power_supply *psy, enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); ++ u32 value; ++ int status; ++ ++ mutex_lock(&bat->lock); ++ ++ status = spwr_battery_update_bst_unlocked(bat, true); ++ if (status) ++ goto out; ++ ++ /* Abort if battery is not present. */ ++ if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) { ++ status = -ENODEV; ++ goto out; ++ } ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_STATUS: ++ val->intval = spwr_battery_prop_status(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_PRESENT: ++ val->intval = spwr_battery_present(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_TECHNOLOGY: ++ val->intval = spwr_battery_prop_technology(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_CYCLE_COUNT: ++ value = get_unaligned_le32(&bat->bix.cycle_count); ++ if (value != SPWR_BATTERY_VALUE_UNKNOWN) ++ val->intval = value; ++ else ++ status = -ENODATA; ++ break; ++ ++ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: ++ value = get_unaligned_le32(&bat->bix.design_voltage); ++ if (value != SPWR_BATTERY_VALUE_UNKNOWN) ++ val->intval = value * 1000; ++ else ++ status = -ENODATA; ++ break; ++ ++ case POWER_SUPPLY_PROP_VOLTAGE_NOW: ++ value = get_unaligned_le32(&bat->bst.present_voltage); ++ if (value != SPWR_BATTERY_VALUE_UNKNOWN) ++ val->intval = value * 1000; ++ else ++ status = -ENODATA; ++ break; ++ ++ case POWER_SUPPLY_PROP_CURRENT_NOW: ++ case POWER_SUPPLY_PROP_POWER_NOW: ++ value = get_unaligned_le32(&bat->bst.present_rate); ++ if (value != SPWR_BATTERY_VALUE_UNKNOWN) ++ val->intval = value * 1000; ++ else ++ status = -ENODATA; ++ break; ++ ++ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: ++ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: ++ value = get_unaligned_le32(&bat->bix.design_cap); ++ if (value != SPWR_BATTERY_VALUE_UNKNOWN) ++ val->intval = value * 1000; ++ else ++ status = -ENODATA; ++ break; ++ ++ case POWER_SUPPLY_PROP_CHARGE_FULL: ++ case POWER_SUPPLY_PROP_ENERGY_FULL: ++ value = get_unaligned_le32(&bat->bix.last_full_charge_cap); ++ if (value != SPWR_BATTERY_VALUE_UNKNOWN) ++ val->intval = value * 1000; ++ else ++ status = -ENODATA; ++ break; ++ ++ case POWER_SUPPLY_PROP_CHARGE_NOW: ++ case POWER_SUPPLY_PROP_ENERGY_NOW: ++ value = get_unaligned_le32(&bat->bst.remaining_cap); ++ if (value != SPWR_BATTERY_VALUE_UNKNOWN) ++ val->intval = value * 1000; ++ else ++ status = -ENODATA; ++ break; ++ ++ case POWER_SUPPLY_PROP_CAPACITY: ++ val->intval = spwr_battery_prop_capacity(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_CAPACITY_LEVEL: ++ val->intval = spwr_battery_prop_capacity_level(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_MODEL_NAME: ++ val->strval = bat->bix.model; ++ break; ++ ++ case POWER_SUPPLY_PROP_MANUFACTURER: ++ val->strval = bat->bix.oem_info; ++ break; ++ ++ case POWER_SUPPLY_PROP_SERIAL_NUMBER: ++ val->strval = bat->bix.serial; ++ break; ++ ++ default: ++ status = -EINVAL; ++ break; ++ } ++ ++out: ++ mutex_unlock(&bat->lock); ++ return status; ++} ++ ++ ++/* -- Alarm attribute. ------------------------------------------------------ */ ++ ++static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct power_supply *psy = dev_get_drvdata(dev); ++ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); ++ int status; ++ ++ mutex_lock(&bat->lock); ++ status = sysfs_emit(buf, "%d\n", bat->alarm * 1000); ++ mutex_unlock(&bat->lock); ++ ++ return status; ++} ++ ++static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ struct power_supply *psy = dev_get_drvdata(dev); ++ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); ++ unsigned long value; ++ int status; ++ ++ status = kstrtoul(buf, 0, &value); ++ if (status) ++ return status; ++ ++ mutex_lock(&bat->lock); ++ ++ if (!spwr_battery_present(bat)) { ++ mutex_unlock(&bat->lock); ++ return -ENODEV; ++ } ++ ++ status = spwr_battery_set_alarm_unlocked(bat, value / 1000); ++ if (status) { ++ mutex_unlock(&bat->lock); ++ return status; ++ } ++ ++ mutex_unlock(&bat->lock); ++ return count; ++} ++ ++DEVICE_ATTR_RW(alarm); ++ ++static struct attribute *spwr_battery_attrs[] = { ++ &dev_attr_alarm.attr, ++ NULL, ++}; ++ATTRIBUTE_GROUPS(spwr_battery); ++ ++ ++/* -- Device setup. --------------------------------------------------------- */ ++ ++static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_device *sdev, ++ struct ssam_event_registry registry, const char *name) ++{ ++ mutex_init(&bat->lock); ++ strncpy(bat->name, name, ARRAY_SIZE(bat->name) - 1); ++ ++ bat->sdev = sdev; ++ ++ bat->notif.base.priority = 1; ++ bat->notif.base.fn = spwr_notify_bat; ++ bat->notif.event.reg = registry; ++ bat->notif.event.id.target_category = sdev->uid.category; ++ bat->notif.event.id.instance = 0; ++ bat->notif.event.mask = SSAM_EVENT_MASK_STRICT; ++ bat->notif.event.flags = SSAM_EVENT_SEQUENCED; ++ ++ bat->psy_desc.name = bat->name; ++ bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; ++ bat->psy_desc.get_property = spwr_battery_get_property; ++ ++ INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn); ++} ++ ++static int spwr_battery_register(struct spwr_battery_device *bat) ++{ ++ struct power_supply_config psy_cfg = {}; ++ __le32 sta; ++ int status; ++ ++ /* Make sure the device is there and functioning properly. */ ++ status = ssam_retry(ssam_bat_get_sta, bat->sdev, &sta); ++ if (status) ++ return status; ++ ++ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) ++ return -ENODEV; ++ ++ /* Satisfy lockdep although we are in an exclusive context here. */ ++ mutex_lock(&bat->lock); ++ ++ status = spwr_battery_update_bix_unlocked(bat); ++ if (status) { ++ mutex_unlock(&bat->lock); ++ return status; ++ } ++ ++ if (spwr_battery_present(bat)) { ++ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); ++ ++ status = spwr_battery_set_alarm_unlocked(bat, cap_warn); ++ if (status) { ++ mutex_unlock(&bat->lock); ++ return status; ++ } ++ } ++ ++ mutex_unlock(&bat->lock); ++ ++ bat->psy_desc.external_power_changed = spwr_external_power_changed; ++ ++ switch (get_unaligned_le32(&bat->bix.power_unit)) { ++ case SAM_BATTERY_POWER_UNIT_mW: ++ bat->psy_desc.properties = spwr_battery_props_eng; ++ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng); ++ break; ++ ++ case SAM_BATTERY_POWER_UNIT_mA: ++ bat->psy_desc.properties = spwr_battery_props_chg; ++ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg); ++ break; ++ ++ default: ++ dev_err(&bat->sdev->dev, "unsupported battery power unit: %u\n", ++ get_unaligned_le32(&bat->bix.power_unit)); ++ return -EINVAL; ++ } ++ ++ psy_cfg.drv_data = bat; ++ psy_cfg.attr_grp = spwr_battery_groups; ++ ++ bat->psy = devm_power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg); ++ if (IS_ERR(bat->psy)) ++ return PTR_ERR(bat->psy); ++ ++ return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); ++} ++ ++ ++/* -- Driver setup. --------------------------------------------------------- */ ++ ++static int __maybe_unused surface_battery_resume(struct device *dev) ++{ ++ return spwr_battery_recheck_full(dev_get_drvdata(dev)); ++} ++SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume); ++ ++static int surface_battery_probe(struct ssam_device *sdev) ++{ ++ const struct spwr_psy_properties *p; ++ struct spwr_battery_device *bat; ++ ++ p = ssam_device_get_match_data(sdev); ++ if (!p) ++ return -ENODEV; ++ ++ bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL); ++ if (!bat) ++ return -ENOMEM; ++ ++ spwr_battery_init(bat, sdev, p->registry, p->name); ++ ssam_device_set_drvdata(sdev, bat); ++ ++ return spwr_battery_register(bat); ++} ++ ++static void surface_battery_remove(struct ssam_device *sdev) ++{ ++ struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); ++ ++ ssam_notifier_unregister(sdev->ctrl, &bat->notif); ++ cancel_delayed_work_sync(&bat->update_work); ++} ++ ++static const struct spwr_psy_properties spwr_psy_props_bat1 = { ++ .name = "BAT1", ++ .registry = SSAM_EVENT_REGISTRY_SAM, ++}; ++ ++static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { ++ .name = "BAT2", ++ .registry = SSAM_EVENT_REGISTRY_KIP, ++}; ++ ++static const struct ssam_device_id surface_battery_match[] = { ++ { SSAM_SDEV(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, ++ { SSAM_SDEV(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_battery_match); ++ ++static struct ssam_device_driver surface_battery_driver = { ++ .probe = surface_battery_probe, ++ .remove = surface_battery_remove, ++ .match_table = surface_battery_match, ++ .driver = { ++ .name = "surface_battery", ++ .pm = &surface_battery_pm_ops, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(surface_battery_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Battery driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.31.1 + +From 51bdf381a1d059ecefb2e13f663752c4016e6156 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Tue, 6 Apr 2021 01:41:26 +0200 +Subject: [PATCH] power: supply: Add AC driver for Surface Aggregator Module + +On newer Microsoft Surface models (specifically 7th-generation, i.e. +Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go), +battery and AC status/information is no longer handled via standard ACPI +devices, but instead directly via the Surface System Aggregator Module +(SSAM), i.e. the embedded controller on those devices. + +While on previous generation models, AC status is also handled via SSAM, +an ACPI shim was present to translate the standard ACPI AC interface to +SSAM requests. The SSAM interface itself, which is modeled closely after +the ACPI interface, has not changed. + +This commit introduces a new SSAM client device driver to support AC +status/information via the aforementioned interface on said Surface +models. + +Signed-off-by: Maximilian Luz +Signed-off-by: Sebastian Reichel +Patchset: surface-sam +--- + MAINTAINERS | 1 + + drivers/power/supply/Kconfig | 16 ++ + drivers/power/supply/Makefile | 1 + + drivers/power/supply/surface_charger.c | 282 +++++++++++++++++++++++++ + 4 files changed, 300 insertions(+) + create mode 100644 drivers/power/supply/surface_charger.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index 7ee93b732270..710617e26f3e 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -11874,6 +11874,7 @@ L: linux-pm@vger.kernel.org + L: platform-driver-x86@vger.kernel.org + S: Maintained + F: drivers/power/supply/surface_battery.c ++F: drivers/power/supply/surface_charger.c + + MICROSOFT SURFACE DTX DRIVER + M: Maximilian Luz +diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig +index cebeff10d543..91f7cf425ac9 100644 +--- a/drivers/power/supply/Kconfig ++++ b/drivers/power/supply/Kconfig +@@ -817,4 +817,20 @@ config BATTERY_SURFACE + Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, + Surface Book 3, and Surface Laptop Go. + ++config CHARGER_SURFACE ++ tristate "AC driver for 7th-generation Microsoft Surface devices" ++ depends on SURFACE_AGGREGATOR_REGISTRY ++ help ++ Driver for AC devices connected via/managed by the Surface System ++ Aggregator Module (SSAM). ++ ++ This driver provides AC-information and -status support for Surface ++ devices where said data is not exposed via the standard ACPI devices. ++ On those models (7th-generation), AC-information is instead handled ++ directly via a SSAM client device and this driver. ++ ++ Say M or Y here to include AC status support for 7th-generation ++ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, ++ Surface Book 3, and Surface Laptop Go. ++ + endif # POWER_SUPPLY +diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile +index 134041538d2c..a7309a3d1a47 100644 +--- a/drivers/power/supply/Makefile ++++ b/drivers/power/supply/Makefile +@@ -102,3 +102,4 @@ obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o + obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o + obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o + obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o ++obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o +diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c +new file mode 100644 +index 000000000000..c2dd7e604d14 +--- /dev/null ++++ b/drivers/power/supply/surface_charger.c +@@ -0,0 +1,282 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * AC driver for 7th-generation Microsoft Surface devices via Surface System ++ * Aggregator Module (SSAM). ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++ ++/* -- SAM interface. -------------------------------------------------------- */ ++ ++enum sam_event_cid_bat { ++ SAM_EVENT_CID_BAT_ADP = 0x17, ++}; ++ ++enum sam_battery_sta { ++ SAM_BATTERY_STA_OK = 0x0f, ++ SAM_BATTERY_STA_PRESENT = 0x10, ++}; ++ ++/* Get battery status (_STA). */ ++SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { ++ .target_category = SSAM_SSH_TC_BAT, ++ .command_id = 0x01, ++}); ++ ++/* Get platform power source for battery (_PSR / DPTF PSRC). */ ++SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, { ++ .target_category = SSAM_SSH_TC_BAT, ++ .command_id = 0x0d, ++}); ++ ++ ++/* -- Device structures. ---------------------------------------------------- */ ++ ++struct spwr_psy_properties { ++ const char *name; ++ struct ssam_event_registry registry; ++}; ++ ++struct spwr_ac_device { ++ struct ssam_device *sdev; ++ ++ char name[32]; ++ struct power_supply *psy; ++ struct power_supply_desc psy_desc; ++ ++ struct ssam_event_notifier notif; ++ ++ struct mutex lock; /* Guards access to state below. */ ++ ++ __le32 state; ++}; ++ ++ ++/* -- State management. ----------------------------------------------------- */ ++ ++static int spwr_ac_update_unlocked(struct spwr_ac_device *ac) ++{ ++ u32 old = ac->state; ++ int status; ++ ++ lockdep_assert_held(&ac->lock); ++ ++ status = ssam_retry(ssam_bat_get_psrc, ac->sdev, &ac->state); ++ if (status < 0) ++ return status; ++ ++ return old != ac->state; ++} ++ ++static int spwr_ac_update(struct spwr_ac_device *ac) ++{ ++ int status; ++ ++ mutex_lock(&ac->lock); ++ status = spwr_ac_update_unlocked(ac); ++ mutex_unlock(&ac->lock); ++ ++ return status; ++} ++ ++static int spwr_ac_recheck(struct spwr_ac_device *ac) ++{ ++ int status; ++ ++ status = spwr_ac_update(ac); ++ if (status > 0) ++ power_supply_changed(ac->psy); ++ ++ return status >= 0 ? 0 : status; ++} ++ ++static u32 spwr_notify_ac(struct ssam_event_notifier *nf, const struct ssam_event *event) ++{ ++ struct spwr_ac_device *ac; ++ int status; ++ ++ ac = container_of(nf, struct spwr_ac_device, notif); ++ ++ dev_dbg(&ac->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", ++ event->command_id, event->instance_id, event->target_id); ++ ++ /* ++ * Allow events of all targets/instances here. Global adapter status ++ * seems to be handled via target=1 and instance=1, but events are ++ * reported on all targets/instances in use. ++ * ++ * While it should be enough to just listen on 1/1, listen everywhere to ++ * make sure we don't miss anything. ++ */ ++ ++ switch (event->command_id) { ++ case SAM_EVENT_CID_BAT_ADP: ++ status = spwr_ac_recheck(ac); ++ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; ++ ++ default: ++ return 0; ++ } ++} ++ ++ ++/* -- Properties. ----------------------------------------------------------- */ ++ ++static const enum power_supply_property spwr_ac_props[] = { ++ POWER_SUPPLY_PROP_ONLINE, ++}; ++ ++static int spwr_ac_get_property(struct power_supply *psy, enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct spwr_ac_device *ac = power_supply_get_drvdata(psy); ++ int status; ++ ++ mutex_lock(&ac->lock); ++ ++ status = spwr_ac_update_unlocked(ac); ++ if (status) ++ goto out; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_ONLINE: ++ val->intval = !!le32_to_cpu(ac->state); ++ break; ++ ++ default: ++ status = -EINVAL; ++ goto out; ++ } ++ ++out: ++ mutex_unlock(&ac->lock); ++ return status; ++} ++ ++ ++/* -- Device setup. --------------------------------------------------------- */ ++ ++static char *battery_supplied_to[] = { ++ "BAT1", ++ "BAT2", ++}; ++ ++static void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev, ++ struct ssam_event_registry registry, const char *name) ++{ ++ mutex_init(&ac->lock); ++ strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1); ++ ++ ac->sdev = sdev; ++ ++ ac->notif.base.priority = 1; ++ ac->notif.base.fn = spwr_notify_ac; ++ ac->notif.event.reg = registry; ++ ac->notif.event.id.target_category = sdev->uid.category; ++ ac->notif.event.id.instance = 0; ++ ac->notif.event.mask = SSAM_EVENT_MASK_NONE; ++ ac->notif.event.flags = SSAM_EVENT_SEQUENCED; ++ ++ ac->psy_desc.name = ac->name; ++ ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; ++ ac->psy_desc.properties = spwr_ac_props; ++ ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props); ++ ac->psy_desc.get_property = spwr_ac_get_property; ++} ++ ++static int spwr_ac_register(struct spwr_ac_device *ac) ++{ ++ struct power_supply_config psy_cfg = {}; ++ __le32 sta; ++ int status; ++ ++ /* Make sure the device is there and functioning properly. */ ++ status = ssam_retry(ssam_bat_get_sta, ac->sdev, &sta); ++ if (status) ++ return status; ++ ++ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) ++ return -ENODEV; ++ ++ psy_cfg.drv_data = ac; ++ psy_cfg.supplied_to = battery_supplied_to; ++ psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); ++ ++ ac->psy = devm_power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg); ++ if (IS_ERR(ac->psy)) ++ return PTR_ERR(ac->psy); ++ ++ return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); ++} ++ ++ ++/* -- Driver setup. --------------------------------------------------------- */ ++ ++static int __maybe_unused surface_ac_resume(struct device *dev) ++{ ++ return spwr_ac_recheck(dev_get_drvdata(dev)); ++} ++SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); ++ ++static int surface_ac_probe(struct ssam_device *sdev) ++{ ++ const struct spwr_psy_properties *p; ++ struct spwr_ac_device *ac; ++ ++ p = ssam_device_get_match_data(sdev); ++ if (!p) ++ return -ENODEV; ++ ++ ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL); ++ if (!ac) ++ return -ENOMEM; ++ ++ spwr_ac_init(ac, sdev, p->registry, p->name); ++ ssam_device_set_drvdata(sdev, ac); ++ ++ return spwr_ac_register(ac); ++} ++ ++static void surface_ac_remove(struct ssam_device *sdev) ++{ ++ struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); ++ ++ ssam_notifier_unregister(sdev->ctrl, &ac->notif); ++} ++ ++static const struct spwr_psy_properties spwr_psy_props_adp1 = { ++ .name = "ADP1", ++ .registry = SSAM_EVENT_REGISTRY_SAM, ++}; ++ ++static const struct ssam_device_id surface_ac_match[] = { ++ { SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_ac_match); ++ ++static struct ssam_device_driver surface_ac_driver = { ++ .probe = surface_ac_probe, ++ .remove = surface_ac_remove, ++ .match_table = surface_ac_match, ++ .driver = { ++ .name = "surface_ac", ++ .pm = &surface_ac_pm_ops, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(surface_ac_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.31.1 + +From c10193b61e2b7cd895ad7447c61dad50609f9173 Mon Sep 17 00:00:00 2001 +From: Qiheng Lin +Date: Sat, 10 Apr 2021 12:12:46 +0800 +Subject: [PATCH] power: supply: surface-battery: Make some symbols static + +The sparse tool complains as follows: + +drivers/power/supply/surface_battery.c:700:1: warning: + symbol 'dev_attr_alarm' was not declared. Should it be static? +drivers/power/supply/surface_battery.c:805:1: warning: + symbol 'surface_battery_pm_ops' was not declared. Should it be static? + +This symbol is not used outside of surface_battery.c, so this +commit marks it static. + +Reported-by: Hulk Robot +Signed-off-by: Qiheng Lin +Acked-by: Maximilian Luz +Signed-off-by: Sebastian Reichel +Patchset: surface-sam +--- + drivers/power/supply/surface_battery.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c +index 4116dd839ecd..7efa431a62b2 100644 +--- a/drivers/power/supply/surface_battery.c ++++ b/drivers/power/supply/surface_battery.c +@@ -697,7 +697,7 @@ static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, co + return count; + } + +-DEVICE_ATTR_RW(alarm); ++static DEVICE_ATTR_RW(alarm); + + static struct attribute *spwr_battery_attrs[] = { + &dev_attr_alarm.attr, +@@ -802,7 +802,7 @@ static int __maybe_unused surface_battery_resume(struct device *dev) + { + return spwr_battery_recheck_full(dev_get_drvdata(dev)); + } +-SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume); ++static SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume); + + static int surface_battery_probe(struct ssam_device *sdev) + { +-- +2.31.1 + +From 37fd14bed9096ff15a7ea94e0dfd295f13a08ef0 Mon Sep 17 00:00:00 2001 +From: Qiheng Lin +Date: Sat, 10 Apr 2021 12:12:49 +0800 +Subject: [PATCH] power: supply: surface-charger: Make symbol + 'surface_ac_pm_ops' static + +The sparse tool complains as follows: + +drivers/power/supply/surface_charger.c:229:1: warning: + symbol 'surface_ac_pm_ops' was not declared. Should it be static? + +This symbol is not used outside of surface_charger.c, so this +commit marks it static. + +Reported-by: Hulk Robot +Signed-off-by: Qiheng Lin +Acked-by: Maximilian Luz +Signed-off-by: Sebastian Reichel +Patchset: surface-sam +--- + drivers/power/supply/surface_charger.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c +index c2dd7e604d14..81a5b79822c9 100644 +--- a/drivers/power/supply/surface_charger.c ++++ b/drivers/power/supply/surface_charger.c +@@ -226,7 +226,7 @@ static int __maybe_unused surface_ac_resume(struct device *dev) + { + return spwr_ac_recheck(dev_get_drvdata(dev)); + } +-SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); ++static SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); + + static int surface_ac_probe(struct ssam_device *sdev) + { +-- +2.31.1 + +From ee96c5d16365a59aa098ed7a328c6e3f9366514c Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Tue, 4 May 2021 20:00:46 +0200 +Subject: [PATCH] power: supply: surface_battery: Fix battery event handling + +The battery subsystem of the Surface Aggregator Module EC requires us to +register the battery notifier with instance ID 0. However, battery +events are actually sent with the instance ID corresponding to the +device, which is nonzero. Thus, the strict-matching approach doesn't +work here and will discard events that the driver is expected to handle. + +To fix this we have to fall back on notifier matching by target-category +only and have to manually check the instance ID in the notifier +callback. + +Fixes: 167f77f7d0b3 ("power: supply: Add battery driver for Surface Aggregator Module") +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/power/supply/surface_battery.c | 14 ++++++++++++-- + 1 file changed, 12 insertions(+), 2 deletions(-) + +diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c +index 7efa431a62b2..5ec2e6bb2465 100644 +--- a/drivers/power/supply/surface_battery.c ++++ b/drivers/power/supply/surface_battery.c +@@ -345,6 +345,16 @@ static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_eve + struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif); + int status; + ++ /* ++ * We cannot use strict matching when registering the notifier as the ++ * EC expects us to register it against instance ID 0. Strict matching ++ * would thus drop events, as those may have non-zero instance IDs in ++ * this subsystem. So we need to check the instance ID of the event ++ * here manually. ++ */ ++ if (event->instance_id != bat->sdev->uid.instance) ++ return 0; ++ + dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", + event->command_id, event->instance_id, event->target_id); + +@@ -720,8 +730,8 @@ static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_devic + bat->notif.base.fn = spwr_notify_bat; + bat->notif.event.reg = registry; + bat->notif.event.id.target_category = sdev->uid.category; +- bat->notif.event.id.instance = 0; +- bat->notif.event.mask = SSAM_EVENT_MASK_STRICT; ++ bat->notif.event.id.instance = 0; /* need to register with instance 0 */ ++ bat->notif.event.mask = SSAM_EVENT_MASK_TARGET; + bat->notif.event.flags = SSAM_EVENT_SEQUENCED; + + bat->psy_desc.name = bat->name; +-- +2.31.1 + diff --git a/patches/5.12/0007-surface-hotplug.patch b/patches/5.12/0007-surface-hotplug.patch new file mode 100644 index 000000000..f31c8354c --- /dev/null +++ b/patches/5.12/0007-surface-hotplug.patch @@ -0,0 +1,68 @@ +From eabd9f96cf8a3bda4c73397fbcc6b7a0a04e8041 Mon Sep 17 00:00:00 2001 +From: "Rafael J. Wysocki" +Date: Tue, 16 Mar 2021 16:51:40 +0100 +Subject: [PATCH] PCI: PM: Do not read power state in pci_enable_device_flags() + +It should not be necessary to update the current_state field of +struct pci_dev in pci_enable_device_flags() before calling +do_pci_enable_device() for the device, because none of the +code between that point and the pci_set_power_state() call in +do_pci_enable_device() invoked later depends on it. + +Moreover, doing that is actively harmful in some cases. For example, +if the given PCI device depends on an ACPI power resource whose _STA +method initially returns 0 ("off"), but the config space of the PCI +device is accessible and the power state retrieved from the +PCI_PM_CTRL register is D0, the current_state field in the struct +pci_dev representing that device will get out of sync with the +power.state of its ACPI companion object and that will lead to +power management issues going forward. + +To avoid such issues it is better to leave the current_state value +as is until it is changed to PCI_D0 by do_pci_enable_device() as +appropriate. However, the power state of the device is not changed +to PCI_D0 if it is already enabled when pci_enable_device_flags() +gets called for it, so update its current_state in that case, but +use pci_update_current_state() covering platform PM too for that. + +Link: https://lore.kernel.org/lkml/20210314000439.3138941-1-luzmaximilian@gmail.com/ +Reported-by: Maximilian Luz +Tested-by: Maximilian Luz +Signed-off-by: Rafael J. Wysocki +Reviewed-by: Mika Westerberg +Patchset: surface-hotplug +--- + drivers/pci/pci.c | 16 +++------------- + 1 file changed, 3 insertions(+), 13 deletions(-) + +diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c +index 16a17215f633..e4d4e399004b 100644 +--- a/drivers/pci/pci.c ++++ b/drivers/pci/pci.c +@@ -1870,20 +1870,10 @@ static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags) + int err; + int i, bars = 0; + +- /* +- * Power state could be unknown at this point, either due to a fresh +- * boot or a device removal call. So get the current power state +- * so that things like MSI message writing will behave as expected +- * (e.g. if the device really is in D0 at enable time). +- */ +- if (dev->pm_cap) { +- u16 pmcsr; +- pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); +- dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK); +- } +- +- if (atomic_inc_return(&dev->enable_cnt) > 1) ++ if (atomic_inc_return(&dev->enable_cnt) > 1) { ++ pci_update_current_state(dev, dev->current_state); + return 0; /* already enabled */ ++ } + + bridge = pci_upstream_bridge(dev); + if (bridge) +-- +2.31.1 + diff --git a/patches/5.12/0008-surface-typecover.patch b/patches/5.12/0008-surface-typecover.patch new file mode 100644 index 000000000..2ff29e1a5 --- /dev/null +++ b/patches/5.12/0008-surface-typecover.patch @@ -0,0 +1,233 @@ +From 449a4cc68d442a4551fa3aa435a0abcbe5f1fa52 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 5 Nov 2020 13:09:45 +0100 +Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when + suspending + +The Type Cover for Microsoft Surface devices supports a special usb +control request to disable or enable the built-in keyboard backlight. +On Windows, this request happens when putting the device into suspend or +resuming it, without it the backlight of the Type Cover will remain +enabled for some time even though the computer is suspended, which looks +weird to the user. + +So add support for this special usb control request to hid-multitouch, +which is the driver that's handling the Type Cover. + +The reason we have to use a pm_notifier for this instead of the usual +suspend/resume methods is that those won't get called in case the usb +device is already autosuspended. + +Also, if the device is autosuspended, we have to briefly autoresume it +in order to send the request. Doing that should be fine, the usb-core +driver does something similar during suspend inside choose_wakeup(). + +To make sure we don't send that request to every device but only to +devices which support it, add a new quirk +MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk +is only enabled for the usb id of the Surface Pro 2017 Type Cover, which +is where I confirmed that it's working. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- + 1 file changed, 98 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 9d9f3e1bd5f4..800476dbc327 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -34,7 +34,10 @@ + #include + #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) +@@ -70,12 +74,15 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_WIN8_PTP_BUTTONS BIT(18) + #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) + #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) ++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(21) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 + + #define MT_BUTTONTYPE_CLICKPAD 0 + ++#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++ + enum latency_mode { + HID_LATENCY_NORMAL = 0, + HID_LATENCY_HIGH = 1, +@@ -167,6 +174,8 @@ struct mt_device { + + struct list_head applications; + struct list_head reports; ++ ++ struct notifier_block pm_notifier; + }; + + static void mt_post_parse_default_settings(struct mt_device *td, +@@ -208,6 +217,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_GOOGLE 0x0111 + #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 + #define MT_CLS_SMART_TECH 0x0113 ++#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 + + #define MT_DEFAULT_MAXCONTACT 10 + #define MT_MAX_MAXCONTACT 250 +@@ -367,6 +377,16 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_SEPARATE_APP_REPORT, + }, ++ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS, ++ .export_all_inputs = true ++ }, + { } + }; + +@@ -1674,6 +1694,69 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + ++static void get_type_cover_backlight_field(struct hid_device *hdev, ++ struct hid_field **field) ++{ ++ struct hid_report_enum *rep_enum; ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid ++ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { ++ *field = cur_field; ++ return; ++ } ++ } ++ } ++ } ++} ++ ++static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) ++{ ++ struct usb_device *udev = hid_to_usb_dev(hdev); ++ struct hid_field *field = NULL; ++ ++ /* Wake up the device in case it's already suspended */ ++ pm_runtime_get_sync(&udev->dev); ++ ++ get_type_cover_backlight_field(hdev, &field); ++ if (!field) { ++ hid_err(hdev, "couldn't find backlight field\n"); ++ goto out; ++ } ++ ++ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; ++ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); ++ ++out: ++ pm_runtime_put_sync(&udev->dev); ++} ++ ++static int mt_pm_notifier(struct notifier_block *notifier, ++ unsigned long pm_event, ++ void *unused) ++{ ++ struct mt_device *td = ++ container_of(notifier, struct mt_device, pm_notifier); ++ struct hid_device *hdev = td->hdev; ++ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { ++ if (pm_event == PM_SUSPEND_PREPARE) ++ update_keyboard_backlight(hdev, 0); ++ else if (pm_event == PM_POST_SUSPEND) ++ update_keyboard_backlight(hdev, 1); ++ } ++ ++ return NOTIFY_DONE; ++} ++ + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + { + int ret, i; +@@ -1697,6 +1780,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; + hid_set_drvdata(hdev, td); + ++ td->pm_notifier.notifier_call = mt_pm_notifier; ++ register_pm_notifier(&td->pm_notifier); ++ + INIT_LIST_HEAD(&td->applications); + INIT_LIST_HEAD(&td->reports); + +@@ -1726,15 +1812,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + timer_setup(&td->release_timer, mt_expired_timeout, 0); + + ret = hid_parse(hdev); +- if (ret != 0) ++ if (ret != 0) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) + mt_fix_const_fields(hdev, HID_DG_CONTACTID); + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); +- if (ret) ++ if (ret) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); + if (ret) +@@ -1779,6 +1869,7 @@ static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); + ++ unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); + + sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); +@@ -2130,6 +2221,11 @@ static const struct hid_device_id mt_devices[] = { + MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_CSR2) }, + ++ /* Microsoft Surface type cover */ ++ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.31.1 + diff --git a/patches/5.12/0009-cameras.patch b/patches/5.12/0009-cameras.patch new file mode 100644 index 000000000..8bdc805e4 --- /dev/null +++ b/patches/5.12/0009-cameras.patch @@ -0,0 +1,3764 @@ +From c10c028151ea853e1529ad7d828a9600b4060825 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Mon, 22 Feb 2021 13:07:30 +0000 +Subject: [PATCH] ACPI: scan: Extend acpi_walk_dep_device_list() + +The acpi_walk_dep_device_list() is not as generalisable as its name +implies, serving only to decrement the dependency count for each +dependent device of the input. Extend the function to instead accept +a callback which can be applied to all the dependencies in acpi_dep_list. +Replace all existing calls to the function with calls to a wrapper, passing +a callback that applies the same dependency reduction. + +Suggested-by: Rafael J. Wysocki +Signed-off-by: Daniel Scally +Reviewed-by: Andy Shevchenko +Acked-by: Wolfram Sang # for changing I2C core +Patchset: cameras +--- + drivers/acpi/ec.c | 2 +- + drivers/acpi/pmic/intel_pmic_chtdc_ti.c | 2 +- + drivers/acpi/scan.c | 58 +++++++++++++------ + drivers/gpio/gpiolib-acpi.c | 2 +- + drivers/i2c/i2c-core-acpi.c | 2 +- + drivers/platform/surface/aggregator/core.c | 2 +- + drivers/platform/surface/surface3_power.c | 2 +- + .../platform/surface/surface_acpi_notify.c | 2 +- + include/acpi/acpi_bus.h | 7 +++ + include/linux/acpi.h | 4 +- + 10 files changed, 57 insertions(+), 26 deletions(-) + +diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c +index 13565629ce0a..a258db713bd2 100644 +--- a/drivers/acpi/ec.c ++++ b/drivers/acpi/ec.c +@@ -1627,7 +1627,7 @@ static int acpi_ec_add(struct acpi_device *device) + WARN(!ret, "Could not request EC cmd io port 0x%lx", ec->command_addr); + + /* Reprobe devices depending on the EC */ +- acpi_walk_dep_device_list(ec->handle); ++ acpi_dev_flag_dependency_met(ec->handle); + + acpi_handle_debug(ec->handle, "enumerated.\n"); + return 0; +diff --git a/drivers/acpi/pmic/intel_pmic_chtdc_ti.c b/drivers/acpi/pmic/intel_pmic_chtdc_ti.c +index a5101b07611a..59cca504325e 100644 +--- a/drivers/acpi/pmic/intel_pmic_chtdc_ti.c ++++ b/drivers/acpi/pmic/intel_pmic_chtdc_ti.c +@@ -117,7 +117,7 @@ static int chtdc_ti_pmic_opregion_probe(struct platform_device *pdev) + return err; + + /* Re-enumerate devices depending on PMIC */ +- acpi_walk_dep_device_list(ACPI_HANDLE(pdev->dev.parent)); ++ acpi_dev_flag_dependency_met(ACPI_HANDLE(pdev->dev.parent)); + return 0; + } + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index 6efe7edd7b1e..44e4783d8e95 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -47,12 +47,6 @@ static DEFINE_MUTEX(acpi_hp_context_lock); + */ + static u64 spcr_uart_addr; + +-struct acpi_dep_data { +- struct list_head node; +- acpi_handle supplier; +- acpi_handle consumer; +-}; +- + void acpi_scan_lock_acquire(void) + { + mutex_lock(&acpi_scan_lock); +@@ -2141,30 +2135,58 @@ static void acpi_bus_attach(struct acpi_device *device, bool first_pass) + device->handler->hotplug.notify_online(device); + } + +-void acpi_walk_dep_device_list(acpi_handle handle) ++static int __acpi_dev_flag_dependency_met(struct acpi_dep_data *dep, ++ void *data) + { +- struct acpi_dep_data *dep, *tmp; + struct acpi_device *adev; + ++ acpi_bus_get_device(dep->consumer, &adev); ++ ++ if (adev) { ++ adev->dep_unmet--; ++ if (!adev->dep_unmet) ++ acpi_bus_attach(adev, true); ++ } ++ ++ list_del(&dep->node); ++ kfree(dep); ++ return 0; ++} ++ ++void acpi_walk_dep_device_list(acpi_handle handle, ++ int (*callback)(struct acpi_dep_data *, void *), ++ void *data) ++{ ++ struct acpi_dep_data *dep, *tmp; ++ int ret; ++ + mutex_lock(&acpi_dep_list_lock); + list_for_each_entry_safe(dep, tmp, &acpi_dep_list, node) { + if (dep->supplier == handle) { +- acpi_bus_get_device(dep->consumer, &adev); +- +- if (adev) { +- adev->dep_unmet--; +- if (!adev->dep_unmet) +- acpi_bus_attach(adev, true); +- } +- +- list_del(&dep->node); +- kfree(dep); ++ ret = callback(dep, data); ++ if (ret) ++ break; + } + } + mutex_unlock(&acpi_dep_list_lock); + } + EXPORT_SYMBOL_GPL(acpi_walk_dep_device_list); + ++/** ++ * acpi_dev_flag_dependency_met() - Inform consumers of @handle that the device ++ * is now active ++ * @handle: acpi_handle for the supplier device ++ * ++ * This function walks through the dependencies list and informs each consumer ++ * of @handle that their dependency upon it is now met. Devices with no more ++ * unmet dependencies will be attached to the acpi bus. ++ */ ++void acpi_dev_flag_dependency_met(acpi_handle handle) ++{ ++ acpi_walk_dep_device_list(handle, __acpi_dev_flag_dependency_met, NULL); ++} ++EXPORT_SYMBOL_GPL(acpi_dev_flag_dependency_met); ++ + /** + * acpi_bus_scan - Add ACPI device node objects in a given namespace scope. + * @handle: Root of the namespace scope to scan. +diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c +index 1aacd2a5a1fd..4a16e0d9b257 100644 +--- a/drivers/gpio/gpiolib-acpi.c ++++ b/drivers/gpio/gpiolib-acpi.c +@@ -1263,7 +1263,7 @@ void acpi_gpiochip_add(struct gpio_chip *chip) + + acpi_gpiochip_request_regions(acpi_gpio); + acpi_gpiochip_scan_gpios(acpi_gpio); +- acpi_walk_dep_device_list(handle); ++ acpi_dev_flag_dependency_met(handle); + } + + void acpi_gpiochip_remove(struct gpio_chip *chip) +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index deceed0d76c6..1e7ae3421de1 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -279,7 +279,7 @@ void i2c_acpi_register_devices(struct i2c_adapter *adap) + if (!handle) + return; + +- acpi_walk_dep_device_list(handle); ++ acpi_dev_flag_dependency_met(handle); + } + + static const struct acpi_device_id i2c_acpi_force_400khz_device_ids[] = { +diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c +index 8dc2c267bcd6..f8c691720caf 100644 +--- a/drivers/platform/surface/aggregator/core.c ++++ b/drivers/platform/surface/aggregator/core.c +@@ -706,7 +706,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) + * For now let's thus default power/wakeup to false. + */ + device_set_wakeup_capable(&serdev->dev, true); +- acpi_walk_dep_device_list(ssh); ++ acpi_dev_flag_dependency_met(ssh); + + return 0; + +diff --git a/drivers/platform/surface/surface3_power.c b/drivers/platform/surface/surface3_power.c +index cc4f9cba6856..ad895285d3e9 100644 +--- a/drivers/platform/surface/surface3_power.c ++++ b/drivers/platform/surface/surface3_power.c +@@ -478,7 +478,7 @@ static int mshw0011_install_space_handler(struct i2c_client *client) + return -ENOMEM; + } + +- acpi_walk_dep_device_list(handle); ++ acpi_dev_flag_dependency_met(handle); + return 0; + } + +diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c +index ef9c1f8e8336..e18dc4e30af2 100644 +--- a/drivers/platform/surface/surface_acpi_notify.c ++++ b/drivers/platform/surface/surface_acpi_notify.c +@@ -835,7 +835,7 @@ static int san_probe(struct platform_device *pdev) + if (status) + goto err_install_dev; + +- acpi_walk_dep_device_list(san); ++ acpi_dev_flag_dependency_met(san); + return 0; + + err_install_dev: +diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h +index f28b097c658f..128eec2a26f6 100644 +--- a/include/acpi/acpi_bus.h ++++ b/include/acpi/acpi_bus.h +@@ -279,6 +279,12 @@ struct acpi_device_power { + struct acpi_device_power_state states[ACPI_D_STATE_COUNT]; /* Power states (D0-D3Cold) */ + }; + ++struct acpi_dep_data { ++ struct list_head node; ++ acpi_handle supplier; ++ acpi_handle consumer; ++}; ++ + /* Performance Management */ + + struct acpi_device_perf_flags { +@@ -684,6 +690,7 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) + + bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); + ++void acpi_dev_flag_dependency_met(acpi_handle handle); + struct acpi_device * + acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); + struct acpi_device * +diff --git a/include/linux/acpi.h b/include/linux/acpi.h +index 3bdcfc4401b7..b560e47b700c 100644 +--- a/include/linux/acpi.h ++++ b/include/linux/acpi.h +@@ -666,7 +666,9 @@ extern bool acpi_driver_match_device(struct device *dev, + const struct device_driver *drv); + int acpi_device_uevent_modalias(struct device *, struct kobj_uevent_env *); + int acpi_device_modalias(struct device *, char *, int); +-void acpi_walk_dep_device_list(acpi_handle handle); ++void acpi_walk_dep_device_list(acpi_handle handle, ++ int (*callback)(struct acpi_dep_data *, void *), ++ void *data); + + struct platform_device *acpi_create_platform_device(struct acpi_device *, + struct property_entry *); +-- +2.31.1 + +From 48ca31919b84fcc1456e0f86292b95bda1a74b81 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Mon, 22 Feb 2021 13:07:31 +0000 +Subject: [PATCH] ACPI: scan: Add function to fetch dependent of acpi device + +In some ACPI tables we encounter, devices use the _DEP method to assert +a dependence on other ACPI devices as opposed to the OpRegions that the +specification intends. We need to be able to find those devices "from" +the dependee, so add a callback and a wrapper to walk over the +acpi_dep_list and return the dependent ACPI device. + +Signed-off-by: Daniel Scally +Reviewed-by: Andy Shevchenko +Patchset: cameras +--- + drivers/acpi/scan.c | 34 ++++++++++++++++++++++++++++++++++ + include/acpi/acpi_bus.h | 1 + + 2 files changed, 35 insertions(+) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index 44e4783d8e95..a4735a479249 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2135,6 +2135,21 @@ static void acpi_bus_attach(struct acpi_device *device, bool first_pass) + device->handler->hotplug.notify_online(device); + } + ++static int __acpi_dev_get_dependent_dev(struct acpi_dep_data *dep, void *data) ++{ ++ struct acpi_device *adev; ++ int ret; ++ ++ ret = acpi_bus_get_device(dep->consumer, &adev); ++ if (ret) ++ /* If we don't find an adev then we want to continue parsing */ ++ return 0; ++ ++ *(struct acpi_device **)data = adev; ++ ++ return 1; ++} ++ + static int __acpi_dev_flag_dependency_met(struct acpi_dep_data *dep, + void *data) + { +@@ -2187,6 +2202,25 @@ void acpi_dev_flag_dependency_met(acpi_handle handle) + } + EXPORT_SYMBOL_GPL(acpi_dev_flag_dependency_met); + ++/** ++ * acpi_dev_get_dependent_dev - Return ACPI device dependent on @adev ++ * @adev: Pointer to the dependee device ++ * ++ * Returns the first &struct acpi_device which declares itself dependent on ++ * @adev via the _DEP buffer, parsed from the acpi_dep_list. ++ */ ++struct acpi_device * ++acpi_dev_get_dependent_dev(struct acpi_device *supplier) ++{ ++ struct acpi_device *adev = NULL; ++ ++ acpi_walk_dep_device_list(supplier->handle, ++ __acpi_dev_get_dependent_dev, &adev); ++ ++ return adev; ++} ++EXPORT_SYMBOL_GPL(acpi_dev_get_dependent_dev); ++ + /** + * acpi_bus_scan - Add ACPI device node objects in a given namespace scope. + * @handle: Root of the namespace scope to scan. +diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h +index 128eec2a26f6..d00dec1ad9fb 100644 +--- a/include/acpi/acpi_bus.h ++++ b/include/acpi/acpi_bus.h +@@ -691,6 +691,7 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) + bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); + + void acpi_dev_flag_dependency_met(acpi_handle handle); ++struct acpi_device *acpi_dev_get_dependent_dev(struct acpi_device *supplier); + struct acpi_device * + acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); + struct acpi_device * +-- +2.31.1 + +From c861065d78420ea39fec121e4cd9081fde790aa8 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Mon, 22 Feb 2021 13:07:32 +0000 +Subject: [PATCH] i2c: core: Add a format macro for I2C device names + +Some places in the kernel allow users to map resources to a device +using device name (for example, in the struct gpiod_lookup_table). +Currently this involves waiting for the I2C client to have been registered +so we can use dev_name(&client->dev). We want to add a function to allow +users to refer to an I2C device by name before it has been instantiated, +so create a macro for the format that's accessible outside the I2C layer +and use it in i2c_dev_set_name(). + +Suggested-by: Andy Shevchenko +Reviewed-by: Laurent Pinchart +Reviewed-by: Sakari Ailus +Reviewed-by: Andy Shevchenko +Signed-off-by: Daniel Scally +Acked-by: Wolfram Sang # for changing I2C core +Patchset: cameras +--- + drivers/i2c/i2c-core-base.c | 4 ++-- + include/linux/i2c.h | 3 +++ + 2 files changed, 5 insertions(+), 2 deletions(-) + +diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c +index f21362355973..e2cf16f27d65 100644 +--- a/drivers/i2c/i2c-core-base.c ++++ b/drivers/i2c/i2c-core-base.c +@@ -812,12 +812,12 @@ static void i2c_dev_set_name(struct i2c_adapter *adap, + struct acpi_device *adev = ACPI_COMPANION(&client->dev); + + if (info && info->dev_name) { +- dev_set_name(&client->dev, "i2c-%s", info->dev_name); ++ dev_set_name(&client->dev, I2C_DEV_NAME_FORMAT, info->dev_name); + return; + } + + if (adev) { +- dev_set_name(&client->dev, "i2c-%s", acpi_dev_name(adev)); ++ dev_set_name(&client->dev, I2C_DEV_NAME_FORMAT, acpi_dev_name(adev)); + return; + } + +diff --git a/include/linux/i2c.h b/include/linux/i2c.h +index 56622658b215..4d40a4b46810 100644 +--- a/include/linux/i2c.h ++++ b/include/linux/i2c.h +@@ -39,6 +39,9 @@ enum i2c_slave_event; + typedef int (*i2c_slave_cb_t)(struct i2c_client *client, + enum i2c_slave_event event, u8 *val); + ++/* I2C Device Name Format - to maintain consistency outside the i2c layer */ ++#define I2C_DEV_NAME_FORMAT "i2c-%s" ++ + /* I2C Frequency Modes */ + #define I2C_MAX_STANDARD_MODE_FREQ 100000 + #define I2C_MAX_FAST_MODE_FREQ 400000 +-- +2.31.1 + +From e39f06db39a488efd95244f07c02672e510edc25 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Mon, 22 Feb 2021 13:07:33 +0000 +Subject: [PATCH] gpiolib: acpi: Export acpi_get_gpiod() + +I need to be able to translate GPIO resources in an ACPI device's _CRS +into GPIO descriptor array. Those are represented in _CRS as a pathname +to a GPIO device plus the pin's index number: this function is perfect +for that purpose. + +As it's currently only used internally within the GPIO layer, provide and +export a wrapper function that additionally holds a reference to the GPIO +device. + +Signed-off-by: Daniel Scally +Reviewed-by: Andy Shevchenko +Patchset: cameras +--- + drivers/gpio/gpiolib-acpi.c | 36 +++++++++++++++++++++++++++++++---- + include/linux/gpio/consumer.h | 7 +++++++ + 2 files changed, 39 insertions(+), 4 deletions(-) + +diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c +index 4a16e0d9b257..5f99d698f60d 100644 +--- a/drivers/gpio/gpiolib-acpi.c ++++ b/drivers/gpio/gpiolib-acpi.c +@@ -102,7 +102,8 @@ static int acpi_gpiochip_find(struct gpio_chip *gc, void *data) + } + + /** +- * acpi_get_gpiod() - Translate ACPI GPIO pin to GPIO descriptor usable with GPIO API ++ * __acpi_get_gpiod() - Translate ACPI GPIO pin to GPIO descriptor usable with ++ * GPIO API + * @path: ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1") + * @pin: ACPI GPIO pin number (0-based, controller-relative) + * +@@ -111,7 +112,7 @@ static int acpi_gpiochip_find(struct gpio_chip *gc, void *data) + * controller does not have GPIO chip registered at the moment. This is to + * support probe deferral. + */ +-static struct gpio_desc *acpi_get_gpiod(char *path, int pin) ++static struct gpio_desc *__acpi_get_gpiod(char *path, int pin) + { + struct gpio_chip *chip; + acpi_handle handle; +@@ -128,6 +129,33 @@ static struct gpio_desc *acpi_get_gpiod(char *path, int pin) + return gpiochip_get_desc(chip, pin); + } + ++/** ++ * acpi_get_gpiod() - Translate ACPI GPIO pin to GPIO descriptor usable with ++ * GPIO API, and hold a refcount to the GPIO device. ++ * @path: ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1") ++ * @pin: ACPI GPIO pin number (0-based, controller-relative) ++ * @label: Label to pass to gpiod_request() ++ * ++ * This function is a simple pass-through to __acpi_get_gpiod(), except that as ++ * it is intended for use outside of the GPIO layer (in a similar fashion to ++ * gpiod_get_index() for example) it also holds a reference to the GPIO device. ++ */ ++struct gpio_desc *acpi_get_gpiod(char *path, int pin, char *label) ++{ ++ struct gpio_desc *gpio = __acpi_get_gpiod(path, pin); ++ int ret; ++ ++ if (IS_ERR(gpio)) ++ return gpio; ++ ++ ret = gpiod_request(gpio, label); ++ if (ret) ++ return ERR_PTR(ret); ++ ++ return gpio; ++} ++EXPORT_SYMBOL_GPL(acpi_get_gpiod); ++ + static irqreturn_t acpi_gpio_irq_handler(int irq, void *data) + { + struct acpi_gpio_event *event = data; +@@ -693,8 +721,8 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data) + if (lookup->info.quirks & ACPI_GPIO_QUIRK_ABSOLUTE_NUMBER) + desc = gpio_to_desc(agpio->pin_table[pin_index]); + else +- desc = acpi_get_gpiod(agpio->resource_source.string_ptr, +- agpio->pin_table[pin_index]); ++ desc = __acpi_get_gpiod(agpio->resource_source.string_ptr, ++ agpio->pin_table[pin_index]); + lookup->desc = desc; + lookup->info.pin_config = agpio->pin_config; + lookup->info.debounce = agpio->debounce_timeout; +diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h +index c73b25bc9213..e26fb586b6c8 100644 +--- a/include/linux/gpio/consumer.h ++++ b/include/linux/gpio/consumer.h +@@ -692,6 +692,8 @@ int devm_acpi_dev_add_driver_gpios(struct device *dev, + const struct acpi_gpio_mapping *gpios); + void devm_acpi_dev_remove_driver_gpios(struct device *dev); + ++struct gpio_desc *acpi_get_gpiod(char *path, int pin, char *label); ++ + #else /* CONFIG_GPIOLIB && CONFIG_ACPI */ + + struct acpi_device; +@@ -710,6 +712,11 @@ static inline int devm_acpi_dev_add_driver_gpios(struct device *dev, + } + static inline void devm_acpi_dev_remove_driver_gpios(struct device *dev) {} + ++struct gpio_desc *acpi_get_gpiod(char *path, int pin, char *label) ++{ ++ return NULL; ++} ++ + #endif /* CONFIG_GPIOLIB && CONFIG_ACPI */ + + +-- +2.31.1 + +From 478ecf8703bdcda8aab6cffc6a25b853fe00556f Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Mon, 22 Feb 2021 13:07:34 +0000 +Subject: [PATCH] platform/x86: Add intel_skl_int3472 driver + +ACPI devices with _HID INT3472 are currently matched to the tps68470 +driver, however this does not cover all situations in which that _HID +occurs. We've encountered three possibilities: + +1. On Chrome OS devices, an ACPI device with _HID INT3472 (representing +a physical TPS68470 device) that requires a GPIO and OpRegion driver +2. On devices designed for Windows, an ACPI device with _HID INT3472 +(again representing a physical TPS68470 device) which requires GPIO, +Clock and Regulator drivers. +3. On other devices designed for Windows, an ACPI device with _HID +INT3472 which does **not** represent a physical TPS68470, and is instead +used as a dummy device to group some system GPIO lines which are meant +to be consumed by the sensor that is dependent on this entry. + +This commit adds a new module, registering a platform driver to deal +with the 3rd scenario plus an i2c driver to deal with #1 and #2, by +querying the CLDB buffer found against INT3472 entries to determine +which is most appropriate. + +Suggested-by: Laurent Pinchart +Signed-off-by: Daniel Scally +Patchset: cameras +--- + MAINTAINERS | 5 + + drivers/platform/x86/Kconfig | 2 + + drivers/platform/x86/Makefile | 1 + + drivers/platform/x86/intel-int3472/Kconfig | 31 + + drivers/platform/x86/intel-int3472/Makefile | 4 + + .../intel-int3472/intel_skl_int3472_common.c | 106 ++++ + .../intel-int3472/intel_skl_int3472_common.h | 110 ++++ + .../intel_skl_int3472_discrete.c | 592 ++++++++++++++++++ + .../intel_skl_int3472_tps68470.c | 113 ++++ + 9 files changed, 964 insertions(+) + create mode 100644 drivers/platform/x86/intel-int3472/Kconfig + create mode 100644 drivers/platform/x86/intel-int3472/Makefile + create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c + create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h + create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c + create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index 710617e26f3e..2a421d2e4b07 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -9191,6 +9191,11 @@ S: Maintained + F: arch/x86/include/asm/intel_scu_ipc.h + F: drivers/platform/x86/intel_scu_* + ++INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER ++M: Daniel Scally ++S: Maintained ++F: drivers/platform/x86/intel-int3472/intel_skl_int3472_* ++ + INTEL SPEED SELECT TECHNOLOGY + M: Srinivas Pandruvada + L: platform-driver-x86@vger.kernel.org +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +index 461ec61530eb..51b258120c50 100644 +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -674,6 +674,8 @@ config INTEL_CHT_INT33FE + device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m + for Type-C device. + ++source "drivers/platform/x86/intel-int3472/Kconfig" ++ + config INTEL_HID_EVENT + tristate "INTEL HID Event" + depends on ACPI +diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile +index 60d554073749..4cbcff47a571 100644 +--- a/drivers/platform/x86/Makefile ++++ b/drivers/platform/x86/Makefile +@@ -72,6 +72,7 @@ obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o + obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o + obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o + obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o ++obj-$(CONFIG_INTEL_SKL_INT3472) += intel-int3472/ + obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o + + # MSI +diff --git a/drivers/platform/x86/intel-int3472/Kconfig b/drivers/platform/x86/intel-int3472/Kconfig +new file mode 100644 +index 000000000000..b94622245c21 +--- /dev/null ++++ b/drivers/platform/x86/intel-int3472/Kconfig +@@ -0,0 +1,31 @@ ++config INTEL_SKL_INT3472 ++ tristate "Intel SkyLake ACPI INT3472 Driver" ++ depends on ACPI ++ depends on REGULATOR ++ depends on GPIOLIB ++ depends on COMMON_CLK && CLKDEV_LOOKUP ++ depends on I2C ++ select MFD_CORE ++ select REGMAP_I2C ++ help ++ This driver adds support for the INT3472 ACPI devices found on some ++ Intel SkyLake devices. ++ ++ The INT3472 is an Intel camera power controller, a logical device ++ found on some Skylake-based systems that can map to different ++ hardware devices depending on the platform. On machines ++ designed for Chrome OS, it maps to a TPS68470 camera PMIC. On ++ machines designed for Windows, it maps to either a TP68470 ++ camera PMIC, a uP6641Q sensor PMIC, or a set of discrete GPIOs ++ and power gates. ++ ++ If your device was designed for Chrome OS, this driver will provide ++ an ACPI OpRegion, which must be available before any of the devices ++ using it are probed. For this reason, you should select Y if your ++ device was designed for ChromeOS. For the same reason the ++ I2C_DESIGNWARE_PLATFORM option must be set to Y too. ++ ++ Say Y or M here if you have a SkyLake device designed for use ++ with Windows or ChromeOS. Say N here if you are not sure. ++ ++ The module will be named "intel-skl-int3472" +diff --git a/drivers/platform/x86/intel-int3472/Makefile b/drivers/platform/x86/intel-int3472/Makefile +new file mode 100644 +index 000000000000..c887ee7d52ca +--- /dev/null ++++ b/drivers/platform/x86/intel-int3472/Makefile +@@ -0,0 +1,4 @@ ++obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o ++intel_skl_int3472-objs := intel_skl_int3472_common.o \ ++ intel_skl_int3472_discrete.o \ ++ intel_skl_int3472_tps68470.o +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c +new file mode 100644 +index 000000000000..f61166b6c497 +--- /dev/null ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c +@@ -0,0 +1,106 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Author: Dan Scally */ ++ ++#include ++#include ++#include ++#include ++ ++#include "intel_skl_int3472_common.h" ++ ++union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, ++ char *id) ++{ ++ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ acpi_handle handle = adev->handle; ++ union acpi_object *obj; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(handle, id, NULL, &buffer); ++ if (ACPI_FAILURE(status)) ++ return ERR_PTR(-ENODEV); ++ ++ obj = buffer.pointer; ++ if (!obj) ++ return ERR_PTR(-ENODEV); ++ ++ if (obj->type != ACPI_TYPE_BUFFER) { ++ dev_err(&adev->dev, "%s object is not an ACPI buffer\n", id); ++ kfree(obj); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ return obj; ++} ++ ++int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) ++{ ++ union acpi_object *obj; ++ int ret = 0; ++ ++ obj = skl_int3472_get_acpi_buffer(adev, "CLDB"); ++ if (IS_ERR(obj)) ++ return PTR_ERR(obj); ++ ++ if (obj->buffer.length > sizeof(*cldb)) { ++ dev_err(&adev->dev, "The CLDB buffer is too large\n"); ++ ret = -EINVAL; ++ goto out_free_obj; ++ } ++ ++ memcpy(cldb, obj->buffer.pointer, obj->buffer.length); ++ ++out_free_obj: ++ kfree(obj); ++ return ret; ++} ++ ++static const struct acpi_device_id int3472_device_id[] = { ++ { "INT3472", 0 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, int3472_device_id); ++ ++static struct platform_driver int3472_discrete = { ++ .driver = { ++ .name = "int3472-discrete", ++ .acpi_match_table = int3472_device_id, ++ }, ++ .probe = skl_int3472_discrete_probe, ++ .remove = skl_int3472_discrete_remove, ++}; ++ ++static struct i2c_driver int3472_tps68470 = { ++ .driver = { ++ .name = "int3472-tps68470", ++ .acpi_match_table = int3472_device_id, ++ }, ++ .probe_new = skl_int3472_tps68470_probe, ++}; ++ ++static int skl_int3472_init(void) ++{ ++ int ret = 0; ++ ++ ret = platform_driver_register(&int3472_discrete); ++ if (ret) ++ return ret; ++ ++ ret = i2c_register_driver(THIS_MODULE, &int3472_tps68470); ++ if (ret) ++ platform_driver_unregister(&int3472_discrete); ++ ++ return ret; ++} ++module_init(skl_int3472_init); ++ ++static void skl_int3472_exit(void) ++{ ++ platform_driver_unregister(&int3472_discrete); ++ i2c_del_driver(&int3472_tps68470); ++} ++module_exit(skl_int3472_exit); ++ ++MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver"); ++MODULE_AUTHOR("Daniel Scally "); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +new file mode 100644 +index 000000000000..9169356cd522 +--- /dev/null ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +@@ -0,0 +1,110 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Author: Dan Scally */ ++ ++#ifndef _INTEL_SKL_INT3472_H ++#define _INTEL_SKL_INT3472_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++/* PMIC GPIO Types */ ++#define INT3472_GPIO_TYPE_RESET 0x00 ++#define INT3472_GPIO_TYPE_POWERDOWN 0x01 ++#define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b ++#define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c ++#define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d ++ ++#define INT3472_PDEV_MAX_NAME_LEN 23 ++#define INT3472_MAX_SENSOR_GPIOS 3 ++ ++#define GPIO_REGULATOR_NAME_LENGTH 21 ++#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 ++ ++#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86 ++ ++#define INT3472_REGULATOR(_name, _supply, _ops) \ ++ (const struct regulator_desc) { \ ++ .name = _name, \ ++ .supply_name = _supply, \ ++ .type = REGULATOR_VOLTAGE, \ ++ .ops = _ops, \ ++ .owner = THIS_MODULE, \ ++ } ++ ++#define to_int3472_clk(hw) \ ++ container_of(hw, struct int3472_gpio_clock, clk_hw) ++ ++#define to_int3472_device(clk) \ ++ container_of(clk, struct int3472_discrete_device, clock) ++ ++struct platform_device; ++struct i2c_client; ++struct acpi_device; ++ ++struct int3472_cldb { ++ u8 version; ++ /* ++ * control logic type ++ * 0: UNKNOWN ++ * 1: DISCRETE(CRD-D) ++ * 2: PMIC TPS68470 ++ * 3: PMIC uP6641 ++ */ ++ u8 control_logic_type; ++ u8 control_logic_id; ++ u8 sensor_card_sku; ++ u8 reserved[28]; ++}; ++ ++struct int3472_gpio_function_remap { ++ char *documented; ++ char *actual; ++}; ++ ++struct int3472_sensor_config { ++ const char *sensor_module_name; ++ struct regulator_consumer_supply supply_map; ++ const struct int3472_gpio_function_remap *function_maps; ++}; ++ ++struct int3472_discrete_device { ++ struct acpi_device *adev; ++ struct device *dev; ++ struct acpi_device *sensor; ++ const char *sensor_name; ++ ++ struct int3472_sensor_config *sensor_config; ++ ++ struct int3472_gpio_regulator { ++ char regulator_name[GPIO_REGULATOR_NAME_LENGTH]; ++ char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH]; ++ struct gpio_desc *gpio; ++ struct regulator_dev *rdev; ++ struct regulator_desc rdesc; ++ } regulator; ++ ++ struct int3472_gpio_clock { ++ struct clk *clk; ++ struct clk_hw clk_hw; ++ struct clk_lookup *cl; ++ struct gpio_desc *ena_gpio; ++ struct gpio_desc *led_gpio; ++ u32 frequency; ++ } clock; ++ ++ unsigned int n_gpios; /* how many GPIOs have we seen */ ++ unsigned int n_sensor_gpios; /* how many have we mapped to sensor */ ++ struct gpiod_lookup_table gpios; ++}; ++ ++int skl_int3472_discrete_probe(struct platform_device *pdev); ++int skl_int3472_discrete_remove(struct platform_device *pdev); ++int skl_int3472_tps68470_probe(struct i2c_client *client); ++union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, ++ char *id); ++int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); ++ ++#endif +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +new file mode 100644 +index 000000000000..40652161bbbf +--- /dev/null ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +@@ -0,0 +1,592 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Author: Dan Scally */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "intel_skl_int3472_common.h" ++ ++/* ++ * 79234640-9e10-4fea-a5c1-b5aa8b19756f ++ * This _DSM GUID returns information about the GPIO lines mapped to a ++ * discrete INT3472 device. Function number 1 returns a count of the GPIO ++ * lines that are mapped. Subsequent functions return 32 bit ints encoding ++ * information about the GPIO line, including its purpose. ++ */ ++static const guid_t int3472_gpio_guid = ++ GUID_INIT(0x79234640, 0x9e10, 0x4fea, ++ 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); ++ ++/* ++ * 822ace8f-2814-4174-a56b-5f029fe079ee ++ * This _DSM GUID returns a string from the sensor device, which acts as a ++ * module identifier. ++ */ ++static const guid_t cio2_sensor_module_guid = ++ GUID_INIT(0x822ace8f, 0x2814, 0x4174, ++ 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); ++ ++/* ++ * Here follows platform specific mapping information that we can pass to ++ * the functions mapping resources to the sensors. Where the sensors have ++ * a power enable pin defined in DSDT we need to provide a supply name so ++ * the sensor drivers can find the regulator. The device name will be derived ++ * from the sensor's ACPI device within the code. Optionally, we can provide a ++ * NULL terminated array of function name mappings to deal with any platform ++ * specific deviations from the documented behaviour of GPIOs. ++ * ++ * Map a GPIO function name to NULL to prevent the driver from mapping that ++ * GPIO at all. ++ */ ++ ++static const struct int3472_gpio_function_remap ov2680_gpio_function_remaps[] = { ++ { "reset", NULL }, ++ { "powerdown", "reset" }, ++ { } ++}; ++ ++static struct int3472_sensor_config int3472_sensor_configs[] = { ++ /* Lenovo Miix 510-12ISK - OV2680, Front */ ++ { "GNDF140809R", { 0 }, ov2680_gpio_function_remaps}, ++ /* Lenovo Miix 510-12ISK - OV5648, Rear */ ++ { "GEFF150023R", REGULATOR_SUPPLY("avdd", NULL), NULL}, ++ /* Surface Go 1&2 - OV5693, Front */ ++ { "YHCU", REGULATOR_SUPPLY("avdd", NULL), NULL}, ++}; ++ ++/* ++ * The regulators have to have .ops to be valid, but the only ops we actually ++ * support are .enable and .disable which are handled via .ena_gpiod. Pass an ++ * empty struct to clear the check without lying about capabilities. ++ */ ++static const struct regulator_ops int3472_gpio_regulator_ops = { 0 }; ++ ++static int skl_int3472_clk_prepare(struct clk_hw *hw) ++{ ++ struct int3472_gpio_clock *clk = to_int3472_clk(hw); ++ ++ gpiod_set_value(clk->ena_gpio, 1); ++ if (clk->led_gpio) ++ gpiod_set_value(clk->led_gpio, 1); ++ ++ return 0; ++} ++ ++static void skl_int3472_clk_unprepare(struct clk_hw *hw) ++{ ++ struct int3472_gpio_clock *clk = to_int3472_clk(hw); ++ ++ gpiod_set_value(clk->ena_gpio, 0); ++ if (clk->led_gpio) ++ gpiod_set_value(clk->led_gpio, 0); ++} ++ ++static int skl_int3472_clk_enable(struct clk_hw *hw) ++{ ++ /* ++ * We're just turning a GPIO on to enable, which operation has the ++ * potential to sleep. Given enable cannot sleep, but prepare can, ++ * we toggle the GPIO in prepare instead. Thus, nothing to do here. ++ */ ++ return 0; ++} ++ ++static void skl_int3472_clk_disable(struct clk_hw *hw) ++{ ++ /* Likewise, nothing to do here... */ ++} ++ ++static unsigned int skl_int3472_get_clk_frequency(struct int3472_discrete_device *int3472) ++{ ++ union acpi_object *obj; ++ unsigned int ret = 0; ++ ++ obj = skl_int3472_get_acpi_buffer(int3472->sensor, "SSDB"); ++ if (IS_ERR(obj)) ++ return 0; /* report rate as 0 on error */ ++ ++ if (obj->buffer.length < CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET + sizeof(u32)) { ++ dev_err(int3472->dev, "The buffer is too small\n"); ++ goto out_free_buff; ++ } ++ ++ ret = *(u32 *)(obj->buffer.pointer + CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET); ++ ++out_free_buff: ++ kfree(obj); ++ return ret; ++} ++ ++static unsigned long skl_int3472_clk_recalc_rate(struct clk_hw *hw, ++ unsigned long parent_rate) ++{ ++ struct int3472_gpio_clock *clk = to_int3472_clk(hw); ++ struct int3472_discrete_device *int3472 = to_int3472_device(clk); ++ ++ return int3472->clock.frequency; ++} ++ ++static const struct clk_ops skl_int3472_clock_ops = { ++ .prepare = skl_int3472_clk_prepare, ++ .unprepare = skl_int3472_clk_unprepare, ++ .enable = skl_int3472_clk_enable, ++ .disable = skl_int3472_clk_disable, ++ .recalc_rate = skl_int3472_clk_recalc_rate, ++}; ++ ++static struct int3472_sensor_config * ++skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472) ++{ ++ struct int3472_sensor_config *ret; ++ union acpi_object *obj; ++ unsigned int i; ++ ++ obj = acpi_evaluate_dsm_typed(int3472->sensor->handle, ++ &cio2_sensor_module_guid, 0x00, ++ 0x01, NULL, ACPI_TYPE_STRING); ++ ++ if (!obj) { ++ dev_err(int3472->dev, ++ "Failed to get sensor module string from _DSM\n"); ++ return ERR_PTR(-ENODEV); ++ } ++ ++ if (obj->string.type != ACPI_TYPE_STRING) { ++ dev_err(int3472->dev, ++ "Sensor _DSM returned a non-string value\n"); ++ ret = ERR_PTR(-EINVAL); ++ goto out_free_obj; ++ } ++ ++ ret = NULL; ++ for (i = 0; i < ARRAY_SIZE(int3472_sensor_configs); i++) { ++ if (!strcmp(int3472_sensor_configs[i].sensor_module_name, ++ obj->string.pointer)) { ++ ret = &int3472_sensor_configs[i]; ++ break; ++ } ++ } ++ ++out_free_obj: ++ ACPI_FREE(obj); ++ return ret; ++} ++ ++static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472, ++ struct acpi_resource *ares, ++ char *func, u32 polarity) ++{ ++ char *path = ares->data.gpio.resource_source.string_ptr; ++ const struct int3472_sensor_config *sensor_config; ++ struct gpiod_lookup *table_entry; ++ struct acpi_device *adev; ++ acpi_handle handle; ++ acpi_status status; ++ int ret; ++ ++ if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { ++ dev_warn(int3472->dev, "Too many GPIOs mapped\n"); ++ return -EINVAL; ++ } ++ ++ sensor_config = int3472->sensor_config; ++ if (!IS_ERR_OR_NULL(sensor_config) && sensor_config->function_maps) { ++ const struct int3472_gpio_function_remap *remap = ++ sensor_config->function_maps; ++ ++ for (; remap->documented; ++remap) ++ if (!strcmp(func, remap->documented)) { ++ func = remap->actual; ++ break; ++ } ++ } ++ ++ /* Functions mapped to NULL should not be mapped to the sensor */ ++ if (!func) ++ return 0; ++ ++ status = acpi_get_handle(NULL, path, &handle); ++ if (ACPI_FAILURE(status)) ++ return -EINVAL; ++ ++ ret = acpi_bus_get_device(handle, &adev); ++ if (ret) ++ return -ENODEV; ++ ++ table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; ++ table_entry->key = acpi_dev_name(adev); ++ table_entry->chip_hwnum = ares->data.gpio.pin_table[0]; ++ table_entry->con_id = func; ++ table_entry->idx = 0; ++ table_entry->flags = polarity; ++ ++ int3472->n_sensor_gpios++; ++ ++ return 0; ++} ++ ++static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, ++ struct acpi_resource *ares, u8 type) ++{ ++ char *path = ares->data.gpio.resource_source.string_ptr; ++ struct gpio_desc *gpio; ++ ++ switch (type) { ++ case INT3472_GPIO_TYPE_CLK_ENABLE: ++ gpio = acpi_get_gpiod(path, ares->data.gpio.pin_table[0], ++ "int3472,clk-enable"); ++ if (IS_ERR(gpio)) ++ return (PTR_ERR(gpio)); ++ ++ int3472->clock.ena_gpio = gpio; ++ break; ++ case INT3472_GPIO_TYPE_PRIVACY_LED: ++ gpio = acpi_get_gpiod(path, ares->data.gpio.pin_table[0], ++ "int3472,privacy-led"); ++ if (IS_ERR(gpio)) ++ return (PTR_ERR(gpio)); ++ ++ int3472->clock.led_gpio = gpio; ++ break; ++ default: ++ dev_err(int3472->dev, "Invalid GPIO type 0x%hx for clock\n", ++ type); ++ break; ++ } ++ ++ return 0; ++} ++ ++static int skl_int3472_register_clock(struct int3472_discrete_device *int3472) ++{ ++ struct clk_init_data init = { ++ .ops = &skl_int3472_clock_ops, ++ .flags = CLK_GET_RATE_NOCACHE, ++ }; ++ int ret = 0; ++ ++ init.name = kasprintf(GFP_KERNEL, "%s-clk", ++ acpi_dev_name(int3472->adev)); ++ if (!init.name) ++ return -ENOMEM; ++ ++ int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472); ++ ++ int3472->clock.clk_hw.init = &init; ++ int3472->clock.clk = clk_register(&int3472->adev->dev, ++ &int3472->clock.clk_hw); ++ if (IS_ERR(int3472->clock.clk)) { ++ ret = PTR_ERR(int3472->clock.clk); ++ goto out_free_init_name; ++ } ++ ++ int3472->clock.cl = clkdev_create(int3472->clock.clk, NULL, ++ int3472->sensor_name); ++ if (!int3472->clock.cl) { ++ ret = -ENOMEM; ++ goto err_unregister_clk; ++ } ++ ++ goto out_free_init_name; ++ ++err_unregister_clk: ++ clk_unregister(int3472->clock.clk); ++out_free_init_name: ++ kfree(init.name); ++ ++ return ret; ++} ++ ++static int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, ++ struct acpi_resource *ares) ++{ ++ char *path = ares->data.gpio.resource_source.string_ptr; ++ struct int3472_sensor_config *sensor_config; ++ struct regulator_init_data init_data = { }; ++ struct regulator_config cfg = { }; ++ int ret; ++ ++ sensor_config = int3472->sensor_config; ++ if (IS_ERR_OR_NULL(sensor_config)) { ++ dev_err(int3472->dev, "No sensor module config\n"); ++ return PTR_ERR(sensor_config); ++ } ++ ++ if (!sensor_config->supply_map.supply) { ++ dev_err(int3472->dev, "No supply name defined\n"); ++ return -ENODEV; ++ } ++ ++ init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; ++ init_data.num_consumer_supplies = 1; ++ sensor_config->supply_map.dev_name = int3472->sensor_name; ++ init_data.consumer_supplies = &sensor_config->supply_map; ++ ++ snprintf(int3472->regulator.regulator_name, ++ sizeof(int3472->regulator.regulator_name), "%s-regulator", ++ acpi_dev_name(int3472->adev)); ++ snprintf(int3472->regulator.supply_name, ++ GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0"); ++ ++ int3472->regulator.rdesc = INT3472_REGULATOR( ++ int3472->regulator.regulator_name, ++ int3472->regulator.supply_name, ++ &int3472_gpio_regulator_ops); ++ ++ int3472->regulator.gpio = acpi_get_gpiod(path, ++ ares->data.gpio.pin_table[0], ++ "int3472,regulator"); ++ if (IS_ERR(int3472->regulator.gpio)) { ++ dev_err(int3472->dev, "Failed to get regulator GPIO lines\n"); ++ return PTR_ERR(int3472->regulator.gpio); ++ } ++ ++ cfg.dev = &int3472->adev->dev; ++ cfg.init_data = &init_data; ++ cfg.ena_gpiod = int3472->regulator.gpio; ++ ++ int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, ++ &cfg); ++ if (IS_ERR(int3472->regulator.rdev)) { ++ ret = PTR_ERR(int3472->regulator.rdev); ++ goto err_free_gpio; ++ } ++ ++ return 0; ++ ++err_free_gpio: ++ gpiod_put(int3472->regulator.gpio); ++ ++ return ret; ++} ++ ++/** ++ * skl_int3472_handle_gpio_resources: Map PMIC resources to consuming sensor ++ * @ares: A pointer to a &struct acpi_resource ++ * @data: A pointer to a &struct int3472_discrete_device ++ * ++ * This function handles GPIO resources that are against an INT3472 ++ * ACPI device, by checking the value of the corresponding _DSM entry. ++ * This will return a 32bit int, where the lowest byte represents the ++ * function of the GPIO pin: ++ * ++ * 0x00 Reset ++ * 0x01 Power down ++ * 0x0b Power enable ++ * 0x0c Clock enable ++ * 0x0d Privacy LED ++ * ++ * There are some known platform specific quirks where that does not quite ++ * hold up; for example where a pin with type 0x01 (Power down) is mapped to ++ * a sensor pin that performs a reset function or entries in _CRS and _DSM that ++ * do not actually correspond to a physical connection. These will be handled ++ * by the mapping sub-functions. ++ * ++ * GPIOs will either be mapped directly to the sensor device or else used ++ * to create clocks and regulators via the usual frameworks. ++ * ++ * Return: ++ * * 0 - When all resources found are handled properly. ++ * * -EINVAL - If the resource is not a GPIO IO resource ++ * * -ENODEV - If the resource has no corresponding _DSM entry ++ * * -Other - Errors propagated from one of the sub-functions. ++ */ ++static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, ++ void *data) ++{ ++ struct int3472_discrete_device *int3472 = data; ++ u16 pin = ares->data.gpio.pin_table[0]; ++ union acpi_object *obj; ++ char *err_msg; ++ int ret = 0; ++ u8 type; ++ ++ if (ares->type != ACPI_RESOURCE_TYPE_GPIO || ++ ares->data.gpio.connection_type != ACPI_RESOURCE_GPIO_TYPE_IO) ++ return 1; /* Deliberately positive so parsing continues */ ++ ++ /* ++ * n_gpios + 2 because the index of this _DSM function is 1-based and ++ * the first function is just a count. ++ */ ++ obj = acpi_evaluate_dsm_typed(int3472->adev->handle, ++ &int3472_gpio_guid, 0x00, ++ int3472->n_gpios + 2, ++ NULL, ACPI_TYPE_INTEGER); ++ ++ if (!obj) { ++ dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", pin); ++ return 1; ++ } ++ ++ type = obj->integer.value & 0xff; ++ ++ switch (type) { ++ case INT3472_GPIO_TYPE_RESET: ++ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, "reset", ++ GPIO_ACTIVE_LOW); ++ if (ret) ++ err_msg = "Failed to map reset pin to sensor\n"; ++ ++ break; ++ case INT3472_GPIO_TYPE_POWERDOWN: ++ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, ++ "powerdown", ++ GPIO_ACTIVE_LOW); ++ if (ret) ++ err_msg = "Failed to map powerdown pin to sensor\n"; ++ ++ break; ++ case INT3472_GPIO_TYPE_CLK_ENABLE: ++ case INT3472_GPIO_TYPE_PRIVACY_LED: ++ ret = skl_int3472_map_gpio_to_clk(int3472, ares, type); ++ if (ret) ++ err_msg = "Failed to map GPIO to clock\n"; ++ ++ break; ++ case INT3472_GPIO_TYPE_POWER_ENABLE: ++ ret = skl_int3472_register_regulator(int3472, ares); ++ if (ret) ++ err_msg = "Failed to map regulator to sensor\n"; ++ ++ break; ++ default: ++ dev_warn(int3472->dev, ++ "GPIO type 0x%02x unknown; the sensor may not work\n", ++ type); ++ ret = 1; ++ break; ++ } ++ ++ if (ret < 0 && ret != -EPROBE_DEFER) ++ dev_err(int3472->dev, err_msg); ++ ++ int3472->n_gpios++; ++ ACPI_FREE(obj); ++ ++ return ret; ++} ++ ++static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) ++{ ++ struct list_head resource_list; ++ int ret; ++ ++ INIT_LIST_HEAD(&resource_list); ++ ++ int3472->sensor_config = skl_int3472_get_sensor_module_config(int3472); ++ ++ ret = acpi_dev_get_resources(int3472->adev, &resource_list, ++ skl_int3472_handle_gpio_resources, ++ int3472); ++ if (ret) ++ goto out_free_res_list; ++ ++ if (int3472->clock.ena_gpio) { ++ ret = skl_int3472_register_clock(int3472); ++ if (ret) ++ goto out_free_res_list; ++ } else { ++ if (int3472->clock.led_gpio) ++ dev_warn(int3472->dev, ++ "No clk GPIO. The privacy LED won't work\n"); ++ } ++ ++ int3472->gpios.dev_id = int3472->sensor_name; ++ gpiod_add_lookup_table(&int3472->gpios); ++ ++out_free_res_list: ++ acpi_dev_free_resource_list(&resource_list); ++ ++ return ret; ++} ++ ++int skl_int3472_discrete_probe(struct platform_device *pdev) ++{ ++ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); ++ struct int3472_discrete_device *int3472; ++ struct int3472_cldb cldb; ++ int ret; ++ ++ ret = skl_int3472_fill_cldb(adev, &cldb); ++ if (ret) { ++ dev_err(&pdev->dev, "Couldn't fill CLDB structure\n"); ++ return ret; ++ } ++ ++ if (cldb.control_logic_type != 1) { ++ dev_err(&pdev->dev, "Unsupported control logic type %u\n", ++ cldb.control_logic_type); ++ return -EINVAL; ++ } ++ ++ /* Max num GPIOs we've seen plus a terminator */ ++ int3472 = kzalloc(struct_size(int3472, gpios.table, ++ INT3472_MAX_SENSOR_GPIOS + 1), GFP_KERNEL); ++ if (!int3472) ++ return -ENOMEM; ++ ++ int3472->adev = adev; ++ int3472->dev = &pdev->dev; ++ platform_set_drvdata(pdev, int3472); ++ ++ int3472->sensor = acpi_dev_get_dependent_dev(adev); ++ if (IS_ERR_OR_NULL(int3472->sensor)) { ++ dev_err(&pdev->dev, ++ "INT3472 seems to have no dependents.\n"); ++ ret = -ENODEV; ++ goto err_free_int3472; ++ } ++ get_device(&int3472->sensor->dev); ++ ++ int3472->sensor_name = kasprintf(GFP_KERNEL, I2C_DEV_NAME_FORMAT, ++ acpi_dev_name(int3472->sensor)); ++ ++ ret = skl_int3472_parse_crs(int3472); ++ if (ret) { ++ skl_int3472_discrete_remove(pdev); ++ return ret; ++ } ++ ++ return 0; ++ ++err_free_int3472: ++ kfree(int3472); ++ return ret; ++} ++ ++int skl_int3472_discrete_remove(struct platform_device *pdev) ++{ ++ struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); ++ ++ if (int3472->gpios.dev_id) ++ gpiod_remove_lookup_table(&int3472->gpios); ++ ++ if (!IS_ERR(int3472->regulator.rdev)) ++ regulator_unregister(int3472->regulator.rdev); ++ ++ if (!IS_ERR(int3472->clock.clk)) ++ clk_unregister(int3472->clock.clk); ++ ++ if (int3472->clock.cl) ++ clkdev_drop(int3472->clock.cl); ++ ++ gpiod_put(int3472->regulator.gpio); ++ gpiod_put(int3472->clock.ena_gpio); ++ gpiod_put(int3472->clock.led_gpio); ++ ++ acpi_dev_put(int3472->sensor); ++ ++ kfree(int3472->sensor_name); ++ kfree(int3472); ++ ++ return 0; ++} +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c +new file mode 100644 +index 000000000000..d0d2391e263f +--- /dev/null ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c +@@ -0,0 +1,113 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Author: Dan Scally */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "intel_skl_int3472_common.h" ++ ++static const struct mfd_cell tps68470_c[] = { ++ { .name = "tps68470-gpio" }, ++ { .name = "tps68470_pmic_opregion" }, ++}; ++ ++static const struct mfd_cell tps68470_w[] = { ++ { .name = "tps68470-gpio" }, ++ { .name = "tps68470-clk" }, ++ { .name = "tps68470-regulator"}, ++}; ++ ++static const struct regmap_config tps68470_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = TPS68470_REG_MAX, ++}; ++ ++static int tps68470_chip_init(struct device *dev, struct regmap *regmap) ++{ ++ unsigned int version; ++ int ret; ++ ++ /* Force software reset */ ++ ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(regmap, TPS68470_REG_REVID, &version); ++ if (ret) { ++ dev_err(dev, "Failed to read revision register: %d\n", ret); ++ return ret; ++ } ++ ++ dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); ++ ++ return 0; ++} ++ ++int skl_int3472_tps68470_probe(struct i2c_client *client) ++{ ++ struct acpi_device *adev = ACPI_COMPANION(&client->dev); ++ struct int3472_cldb cldb = { 0 }; ++ bool cldb_present = true; ++ struct regmap *regmap; ++ int ret; ++ ++ regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); ++ if (IS_ERR(regmap)) { ++ dev_err(&client->dev, "Failed to create regmap: %ld\n", ++ PTR_ERR(regmap)); ++ return PTR_ERR(regmap); ++ } ++ ++ i2c_set_clientdata(client, regmap); ++ ++ ret = tps68470_chip_init(&client->dev, regmap); ++ if (ret < 0) { ++ dev_err(&client->dev, "TPS68470 init error %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Check CLDB buffer against the PMIC's adev. If present, then we check ++ * the value of control_logic_type field and follow one of the ++ * following scenarios: ++ * ++ * 1. No CLDB - likely ACPI tables designed for ChromeOS. We ++ * create platform devices for the GPIOs and OpRegion drivers. ++ * ++ * 2. CLDB, with control_logic_type = 2 - probably ACPI tables ++ * made for Windows 2-in-1 platforms. Register pdevs for GPIO, ++ * Clock and Regulator drivers to bind to. ++ * ++ * 3. Any other value in control_logic_type, we should never have ++ * gotten to this point; fail probe and return. ++ */ ++ ret = skl_int3472_fill_cldb(adev, &cldb); ++ if (!ret && cldb.control_logic_type != 2) { ++ dev_err(&client->dev, "Unsupported control logic type %u\n", ++ cldb.control_logic_type); ++ return -EINVAL; ++ } ++ ++ if (ret) ++ cldb_present = false; ++ ++ if (cldb_present) ++ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, ++ tps68470_w, ARRAY_SIZE(tps68470_w), ++ NULL, 0, NULL); ++ else ++ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, ++ tps68470_c, ARRAY_SIZE(tps68470_c), ++ NULL, 0, NULL); ++ ++ if (ret) { ++ dev_err(&client->dev, "Failed to add MFD devices\n"); ++ return ret; ++ } ++ ++ return 0; ++} +-- +2.31.1 + +From 6c66649aaf0b322f3c0041bd3204d7d485a7d91f Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Mon, 22 Feb 2021 13:07:35 +0000 +Subject: [PATCH] mfd: tps68470: Remove tps68470 MFD driver + +This driver only covered one scenario in which ACPI devices with _HID +INT3472 are found, and its functionality has been taken over by the +intel-skl-int3472 module, so remove it. + +Reviewed-by: Laurent Pinchart +Signed-off-by: Daniel Scally +Acked-by: Andy Shevchenko +Acked-by: Lee Jones +Patchset: cameras +--- + drivers/acpi/pmic/Kconfig | 2 +- + drivers/gpio/Kconfig | 2 +- + drivers/mfd/Kconfig | 18 -------- + drivers/mfd/Makefile | 1 - + drivers/mfd/tps68470.c | 97 --------------------------------------- + 5 files changed, 2 insertions(+), 118 deletions(-) + delete mode 100644 drivers/mfd/tps68470.c + +diff --git a/drivers/acpi/pmic/Kconfig b/drivers/acpi/pmic/Kconfig +index 56bbcb2ce61b..f84b8f6038dc 100644 +--- a/drivers/acpi/pmic/Kconfig ++++ b/drivers/acpi/pmic/Kconfig +@@ -52,7 +52,7 @@ endif # PMIC_OPREGION + + config TPS68470_PMIC_OPREGION + bool "ACPI operation region support for TPS68470 PMIC" +- depends on MFD_TPS68470 ++ depends on INTEL_SKL_INT3472 + help + This config adds ACPI operation region support for TI TPS68470 PMIC. + TPS68470 device is an advanced power management unit that powers +diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig +index e3607ec4c2e8..269d88c7d5a5 100644 +--- a/drivers/gpio/Kconfig ++++ b/drivers/gpio/Kconfig +@@ -1345,7 +1345,7 @@ config GPIO_TPS65912 + + config GPIO_TPS68470 + bool "TPS68470 GPIO" +- depends on MFD_TPS68470 ++ depends on INTEL_SKL_INT3472 + help + Select this option to enable GPIO driver for the TPS68470 + chip family. +diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig +index b74efa469e90..8a112a194c3b 100644 +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -1511,24 +1511,6 @@ config MFD_TPS65217 + This driver can also be built as a module. If so, the module + will be called tps65217. + +-config MFD_TPS68470 +- bool "TI TPS68470 Power Management / LED chips" +- depends on ACPI && PCI && I2C=y +- depends on I2C_DESIGNWARE_PLATFORM=y +- select MFD_CORE +- select REGMAP_I2C +- help +- If you say yes here you get support for the TPS68470 series of +- Power Management / LED chips. +- +- These include voltage regulators, LEDs and other features +- that are often used in portable devices. +- +- This option is a bool as it provides an ACPI operation +- region, which must be available before any of the devices +- using this are probed. This option also configures the +- designware-i2c driver to be built-in, for the same reason. +- + config MFD_TI_LP873X + tristate "TI LP873X Power Management IC" + depends on I2C +diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile +index 834f5463af28..daccc3475de5 100644 +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -105,7 +105,6 @@ obj-$(CONFIG_MFD_TPS65910) += tps65910.o + obj-$(CONFIG_MFD_TPS65912) += tps65912-core.o + obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o + obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.o +-obj-$(CONFIG_MFD_TPS68470) += tps68470.o + obj-$(CONFIG_MFD_TPS80031) += tps80031.o + obj-$(CONFIG_MENELAUS) += menelaus.o + +diff --git a/drivers/mfd/tps68470.c b/drivers/mfd/tps68470.c +deleted file mode 100644 +index 4a4df4ffd18c..000000000000 +--- a/drivers/mfd/tps68470.c ++++ /dev/null +@@ -1,97 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0 +-/* +- * TPS68470 chip Parent driver +- * +- * Copyright (C) 2017 Intel Corporation +- * +- * Authors: +- * Rajmohan Mani +- * Tianshu Qiu +- * Jian Xu Zheng +- * Yuning Pu +- */ +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +-static const struct mfd_cell tps68470s[] = { +- { .name = "tps68470-gpio" }, +- { .name = "tps68470_pmic_opregion" }, +-}; +- +-static const struct regmap_config tps68470_regmap_config = { +- .reg_bits = 8, +- .val_bits = 8, +- .max_register = TPS68470_REG_MAX, +-}; +- +-static int tps68470_chip_init(struct device *dev, struct regmap *regmap) +-{ +- unsigned int version; +- int ret; +- +- /* Force software reset */ +- ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); +- if (ret) +- return ret; +- +- ret = regmap_read(regmap, TPS68470_REG_REVID, &version); +- if (ret) { +- dev_err(dev, "Failed to read revision register: %d\n", ret); +- return ret; +- } +- +- dev_info(dev, "TPS68470 REVID: 0x%x\n", version); +- +- return 0; +-} +- +-static int tps68470_probe(struct i2c_client *client) +-{ +- struct device *dev = &client->dev; +- struct regmap *regmap; +- int ret; +- +- regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); +- if (IS_ERR(regmap)) { +- dev_err(dev, "devm_regmap_init_i2c Error %ld\n", +- PTR_ERR(regmap)); +- return PTR_ERR(regmap); +- } +- +- i2c_set_clientdata(client, regmap); +- +- ret = tps68470_chip_init(dev, regmap); +- if (ret < 0) { +- dev_err(dev, "TPS68470 Init Error %d\n", ret); +- return ret; +- } +- +- ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, tps68470s, +- ARRAY_SIZE(tps68470s), NULL, 0, NULL); +- if (ret < 0) { +- dev_err(dev, "devm_mfd_add_devices failed: %d\n", ret); +- return ret; +- } +- +- return 0; +-} +- +-static const struct acpi_device_id tps68470_acpi_ids[] = { +- {"INT3472"}, +- {}, +-}; +- +-static struct i2c_driver tps68470_driver = { +- .driver = { +- .name = "tps68470", +- .acpi_match_table = tps68470_acpi_ids, +- }, +- .probe_new = tps68470_probe, +-}; +-builtin_i2c_driver(tps68470_driver); +-- +2.31.1 + +From c71b895f160e1a133b5648ba0efe2aceb1c0a3a8 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Mon, 5 Apr 2021 23:56:53 +0100 +Subject: [PATCH] media: ipu3-cio2: Toggle sensor streaming in pm runtime ops + +The .suspend() and .resume() runtime_pm operations for the ipu3-cio2 +driver currently do not handle the sensor's stream. Setting .s_stream() on +or off for the sensor subdev means that sensors will pause and resume the +stream at the appropriate time even if their drivers don't implement those +operations. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 15 ++++++++++++++- + 1 file changed, 14 insertions(+), 1 deletion(-) + +diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +index 6e8c0c230e11..21e4d0358cdc 100644 +--- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c ++++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +@@ -1973,12 +1973,19 @@ static int __maybe_unused cio2_suspend(struct device *dev) + struct pci_dev *pci_dev = to_pci_dev(dev); + struct cio2_device *cio2 = pci_get_drvdata(pci_dev); + struct cio2_queue *q = cio2->cur_queue; ++ int r; + + dev_dbg(dev, "cio2 suspend\n"); + if (!cio2->streaming) + return 0; + + /* Stop stream */ ++ r = v4l2_subdev_call(q->sensor, video, s_stream, 0); ++ if (r) { ++ dev_err(dev, "failed to stop sensor streaming\n"); ++ return r; ++ } ++ + cio2_hw_exit(cio2, q); + synchronize_irq(pci_dev->irq); + +@@ -2013,8 +2020,14 @@ static int __maybe_unused cio2_resume(struct device *dev) + } + + r = cio2_hw_init(cio2, q); +- if (r) ++ if (r) { + dev_err(dev, "fail to init cio2 hw\n"); ++ return r; ++ } ++ ++ r = v4l2_subdev_call(q->sensor, video, s_stream, 1); ++ if (r) ++ dev_err(dev, "fail to start sensor streaming\n"); + + return r; + } +-- +2.31.1 + +From 8432e3e455f946d974d66de60b0b8a397c02ef3d Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Mon, 5 Apr 2021 23:56:54 +0100 +Subject: [PATCH] media: i2c: Add support for ov5693 sensor + +The OV5693 is a 5 Mpx CMOS image sensor, connected via MIPI CSI-2. The +chip is capable of a single lane configuration, but currently only two +lanes are supported. + +Most of the sensor's features are supported, with the main exception +being the lens correction algorithm. + +The driver provides all mandatory, optional and recommended V4L2 controls +for maximum compatibility with libcamera. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + MAINTAINERS | 7 + + drivers/media/i2c/Kconfig | 11 + + drivers/media/i2c/Makefile | 1 + + drivers/media/i2c/ov5693.c | 1557 ++++++++++++++++++++++++++++++++++++ + 4 files changed, 1576 insertions(+) + create mode 100644 drivers/media/i2c/ov5693.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index 2a421d2e4b07..b270caa6f766 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -13276,6 +13276,13 @@ S: Maintained + T: git git://linuxtv.org/media_tree.git + F: drivers/media/i2c/ov5675.c + ++OMNIVISION OV5693 SENSOR DRIVER ++M: Daniel Scally ++L: linux-media@vger.kernel.org ++S: Maintained ++T: git git://linuxtv.org/media_tree.git ++F: drivers/media/i2c/ov5693.c ++ + OMNIVISION OV5695 SENSOR DRIVER + M: Shunqian Zheng + L: linux-media@vger.kernel.org +diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +index 462c0e059754..2893e74af99a 100644 +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -999,6 +999,17 @@ config VIDEO_OV5675 + To compile this driver as a module, choose M here: the + module will be called ov5675. + ++config VIDEO_OV5693 ++ tristate "OmniVision OV5693 sensor support" ++ depends on I2C && VIDEO_V4L2 ++ select V4L2_FWNODE ++ help ++ This is a Video4Linux2 sensor driver for the OmniVision ++ OV5693 camera. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ov5693. ++ + config VIDEO_OV5695 + tristate "OmniVision OV5695 sensor support" + depends on I2C && VIDEO_V4L2 +diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile +index 0c067beca066..7d0884bc89f1 100644 +--- a/drivers/media/i2c/Makefile ++++ b/drivers/media/i2c/Makefile +@@ -75,6 +75,7 @@ obj-$(CONFIG_VIDEO_OV5647) += ov5647.o + obj-$(CONFIG_VIDEO_OV5648) += ov5648.o + obj-$(CONFIG_VIDEO_OV5670) += ov5670.o + obj-$(CONFIG_VIDEO_OV5675) += ov5675.o ++obj-$(CONFIG_VIDEO_OV5693) += ov5693.o + obj-$(CONFIG_VIDEO_OV5695) += ov5695.o + obj-$(CONFIG_VIDEO_OV6650) += ov6650.o + obj-$(CONFIG_VIDEO_OV7251) += ov7251.o +diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c +new file mode 100644 +index 000000000000..da2ca99a7ad3 +--- /dev/null ++++ b/drivers/media/i2c/ov5693.c +@@ -0,0 +1,1557 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. ++ * ++ * Adapted from the atomisp-ov5693 driver, with contributions from: ++ * ++ * Daniel Scally ++ * Jean-Michel Hautbois ++ * Fabian Wuthrich ++ * Tsuchiya Yuto ++ * Jordan Hand ++ * Jake Day ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* System Control */ ++#define OV5693_SW_RESET_REG 0x0103 ++#define OV5693_SW_STREAM_REG 0x0100 ++#define OV5693_START_STREAMING 0x01 ++#define OV5693_STOP_STREAMING 0x00 ++#define OV5693_SW_RESET 0x01 ++ ++#define OV5693_REG_CHIP_ID_H 0x300a ++#define OV5693_REG_CHIP_ID_L 0x300b ++/* Yes, this is right. The datasheet for the OV5693 gives its ID as 0x5690 */ ++#define OV5693_CHIP_ID 0x5690 ++ ++/* Exposure */ ++#define OV5693_EXPOSURE_L_CTRL_HH_REG 0x3500 ++#define OV5693_EXPOSURE_L_CTRL_H_REG 0x3501 ++#define OV5693_EXPOSURE_L_CTRL_L_REG 0x3502 ++#define OV5693_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(14, 12)) >> 12) ++#define OV5693_EXPOSURE_CTRL_H(v) (((v) & GENMASK(11, 4)) >> 4) ++#define OV5693_EXPOSURE_CTRL_L(v) (((v) & GENMASK(3, 0)) << 4) ++#define OV5693_INTEGRATION_TIME_MARGIN 8 ++#define OV5693_EXPOSURE_MIN 1 ++#define OV5693_EXPOSURE_STEP 1 ++ ++/* Analogue Gain */ ++#define OV5693_GAIN_CTRL_H_REG 0x350a ++#define OV5693_GAIN_CTRL_H(v) (((v) >> 4) & GENMASK(2, 0)) ++#define OV5693_GAIN_CTRL_L_REG 0x350b ++#define OV5693_GAIN_CTRL_L(v) (((v) << 4) & GENMASK(7, 4)) ++#define OV5693_GAIN_MIN 1 ++#define OV5693_GAIN_MAX 127 ++#define OV5693_GAIN_DEF 8 ++#define OV5693_GAIN_STEP 1 ++ ++/* Digital Gain */ ++#define OV5693_MWB_RED_GAIN_H_REG 0x3400 ++#define OV5693_MWB_RED_GAIN_L_REG 0x3401 ++#define OV5693_MWB_GREEN_GAIN_H_REG 0x3402 ++#define OV5693_MWB_GREEN_GAIN_L_REG 0x3403 ++#define OV5693_MWB_BLUE_GAIN_H_REG 0x3404 ++#define OV5693_MWB_BLUE_GAIN_L_REG 0x3405 ++#define OV5693_MWB_GAIN_H_CTRL(v) (((v) >> 8) & GENMASK(3, 0)) ++#define OV5693_MWB_GAIN_L_CTRL(v) ((v) & GENMASK(7, 0)) ++#define OV5693_MWB_GAIN_MAX 0x0fff ++#define OV5693_DIGITAL_GAIN_MIN 1 ++#define OV5693_DIGITAL_GAIN_MAX 4095 ++#define OV5693_DIGITAL_GAIN_DEF 1024 ++#define OV5693_DIGITAL_GAIN_STEP 1 ++ ++/* Timing and Format */ ++#define OV5693_CROP_START_X_H_REG 0x3800 ++#define OV5693_CROP_START_X_H(v) (((v) & GENMASK(12, 8)) >> 8) ++#define OV5693_CROP_START_X_L_REG 0x3801 ++#define OV5693_CROP_START_X_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_CROP_START_Y_H_REG 0x3802 ++#define OV5693_CROP_START_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) ++#define OV5693_CROP_START_Y_L_REG 0x3803 ++#define OV5693_CROP_START_Y_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_CROP_END_X_H_REG 0x3804 ++#define OV5693_CROP_END_X_H(v) (((v) & GENMASK(12, 8)) >> 8) ++#define OV5693_CROP_END_X_L_REG 0x3805 ++#define OV5693_CROP_END_X_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_CROP_END_Y_H_REG 0x3806 ++#define OV5693_CROP_END_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) ++#define OV5693_CROP_END_Y_L_REG 0x3807 ++#define OV5693_CROP_END_Y_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_OUTPUT_SIZE_X_H_REG 0x3808 ++#define OV5693_OUTPUT_SIZE_X_H(v) (((v) & GENMASK(15, 8)) >> 8) ++#define OV5693_OUTPUT_SIZE_X_L_REG 0x3809 ++#define OV5693_OUTPUT_SIZE_X_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_OUTPUT_SIZE_Y_H_REG 0x380a ++#define OV5693_OUTPUT_SIZE_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) ++#define OV5693_OUTPUT_SIZE_Y_L_REG 0x380b ++#define OV5693_OUTPUT_SIZE_Y_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_TIMING_HTS_H_REG 0x380c ++#define OV5693_TIMING_HTS_H(v) (((v) & GENMASK(15, 8)) >> 8) ++#define OV5693_TIMING_HTS_L_REG 0x380d ++#define OV5693_TIMING_HTS_L(v) ((v) & GENMASK(7, 0)) ++#define OV5693_FIXED_PPL 2688U ++ ++#define OV5693_TIMING_VTS_H_REG 0x380e ++#define OV5693_TIMING_VTS_H(v) (((v) & GENMASK(15, 8)) >> 8) ++#define OV5693_TIMING_VTS_L_REG 0x380f ++#define OV5693_TIMING_VTS_L(v) ((v) & GENMASK(7, 0)) ++#define OV5693_TIMING_MAX_VTS 0xffff ++#define OV5693_TIMING_MIN_VTS 0x04 ++ ++#define OV5693_OFFSET_START_X_H_REG 0x3810 ++#define OV5693_OFFSET_START_X_H(v) (((v) & GENMASK(15, 8)) >> 8) ++#define OV5693_OFFSET_START_X_L_REG 0x3811 ++#define OV5693_OFFSET_START_X_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_OFFSET_START_Y_H_REG 0x3812 ++#define OV5693_OFFSET_START_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) ++#define OV5693_OFFSET_START_Y_L_REG 0x3813 ++#define OV5693_OFFSET_START_Y_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_SUB_INC_X_REG 0x3814 ++#define OV5693_SUB_INC_Y_REG 0x3815 ++ ++#define OV5693_FORMAT1_REG 0x3820 ++#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) ++#define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) ++#define OV5693_FORMAT1_VBIN_EN BIT(0) ++#define OV5693_FORMAT2_REG 0x3821 ++#define OV5693_FORMAT2_HDR_EN BIT(7) ++#define OV5693_FORMAT2_FLIP_HORZ_ISP_EN BIT(2) ++#define OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN BIT(1) ++#define OV5693_FORMAT2_HBIN_EN BIT(0) ++ ++#define OV5693_ISP_CTRL2_REG 0x5002 ++#define OV5693_ISP_SCALE_ENABLE BIT(7) ++ ++/* Pixel Array */ ++#define OV5693_NATIVE_WIDTH 2624 ++#define OV5693_NATIVE_HEIGHT 1956 ++#define OV5693_NATIVE_START_LEFT 0 ++#define OV5693_NATIVE_START_TOP 0 ++#define OV5693_ACTIVE_WIDTH 2592 ++#define OV5693_ACTIVE_HEIGHT 1944 ++#define OV5693_ACTIVE_START_LEFT 16 ++#define OV5693_ACTIVE_START_TOP 6 ++#define OV5693_MIN_CROP_WIDTH 2 ++#define OV5693_MIN_CROP_HEIGHT 2 ++ ++/* Test Pattern */ ++#define OV5693_TEST_PATTERN_REG 0x5e00 ++#define OV5693_TEST_PATTERN_ENABLE BIT(7) ++#define OV5693_TEST_PATTERN_ROLLING BIT(6) ++#define OV5693_TEST_PATTERN_RANDOM 0x01 ++#define OV5693_TEST_PATTERN_BARS 0x00 ++ ++/* System Frequencies */ ++#define OV5693_XVCLK_FREQ 19200000 ++#define OV5693_LINK_FREQ_400MHZ 400000000 ++#define OV5693_PIXEL_RATE 160000000 ++ ++/* Miscellaneous */ ++#define OV5693_NUM_SUPPLIES 2 ++ ++#define to_ov5693_sensor(x) container_of(x, struct ov5693_device, sd) ++ ++struct ov5693_reg { ++ u16 reg; ++ u8 val; ++}; ++ ++struct ov5693_reg_list { ++ u32 num_regs; ++ const struct ov5693_reg *regs; ++}; ++ ++struct ov5693_device { ++ struct i2c_client *client; ++ struct device *dev; ++ ++ /* Protect against concurrent changes to controls */ ++ struct mutex lock; ++ ++ struct gpio_desc *reset; ++ struct gpio_desc *powerdown; ++ struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; ++ struct clk *clk; ++ ++ struct ov5693_mode { ++ struct v4l2_rect crop; ++ struct v4l2_mbus_framefmt format; ++ bool binning_x; ++ bool binning_y; ++ unsigned int inc_x_odd; ++ unsigned int inc_y_odd; ++ unsigned int vts; ++ } mode; ++ bool streaming; ++ ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ ++ struct ov5693_v4l2_ctrls { ++ struct v4l2_ctrl_handler handler; ++ struct v4l2_ctrl *link_freq; ++ struct v4l2_ctrl *pixel_rate; ++ struct v4l2_ctrl *exposure; ++ struct v4l2_ctrl *analogue_gain; ++ struct v4l2_ctrl *digital_gain; ++ struct v4l2_ctrl *hflip; ++ struct v4l2_ctrl *vflip; ++ struct v4l2_ctrl *hblank; ++ struct v4l2_ctrl *vblank; ++ struct v4l2_ctrl *test_pattern; ++ } ctrls; ++}; ++ ++static const struct ov5693_reg ov5693_global_regs[] = { ++ {0x3016, 0xf0}, ++ {0x3017, 0xf0}, ++ {0x3018, 0xf0}, ++ {0x3022, 0x01}, ++ {0x3028, 0x44}, ++ {0x3098, 0x02}, ++ {0x3099, 0x19}, ++ {0x309a, 0x02}, ++ {0x309b, 0x01}, ++ {0x309c, 0x00}, ++ {0x30a0, 0xd2}, ++ {0x30a2, 0x01}, ++ {0x30b2, 0x00}, ++ {0x30b3, 0x7d}, ++ {0x30b4, 0x03}, ++ {0x30b5, 0x04}, ++ {0x30b6, 0x01}, ++ {0x3104, 0x21}, ++ {0x3106, 0x00}, ++ {0x3406, 0x01}, ++ {0x3503, 0x07}, ++ {0x350b, 0x40}, ++ {0x3601, 0x0a}, ++ {0x3602, 0x38}, ++ {0x3612, 0x80}, ++ {0x3620, 0x54}, ++ {0x3621, 0xc7}, ++ {0x3622, 0x0f}, ++ {0x3625, 0x10}, ++ {0x3630, 0x55}, ++ {0x3631, 0xf4}, ++ {0x3632, 0x00}, ++ {0x3633, 0x34}, ++ {0x3634, 0x02}, ++ {0x364d, 0x0d}, ++ {0x364f, 0xdd}, ++ {0x3660, 0x04}, ++ {0x3662, 0x10}, ++ {0x3663, 0xf1}, ++ {0x3665, 0x00}, ++ {0x3666, 0x20}, ++ {0x3667, 0x00}, ++ {0x366a, 0x80}, ++ {0x3680, 0xe0}, ++ {0x3681, 0x00}, ++ {0x3700, 0x42}, ++ {0x3701, 0x14}, ++ {0x3702, 0xa0}, ++ {0x3703, 0xd8}, ++ {0x3704, 0x78}, ++ {0x3705, 0x02}, ++ {0x370a, 0x00}, ++ {0x370b, 0x20}, ++ {0x370c, 0x0c}, ++ {0x370d, 0x11}, ++ {0x370e, 0x00}, ++ {0x370f, 0x40}, ++ {0x3710, 0x00}, ++ {0x371a, 0x1c}, ++ {0x371b, 0x05}, ++ {0x371c, 0x01}, ++ {0x371e, 0xa1}, ++ {0x371f, 0x0c}, ++ {0x3721, 0x00}, ++ {0x3724, 0x10}, ++ {0x3726, 0x00}, ++ {0x372a, 0x01}, ++ {0x3730, 0x10}, ++ {0x3738, 0x22}, ++ {0x3739, 0xe5}, ++ {0x373a, 0x50}, ++ {0x373b, 0x02}, ++ {0x373c, 0x41}, ++ {0x373f, 0x02}, ++ {0x3740, 0x42}, ++ {0x3741, 0x02}, ++ {0x3742, 0x18}, ++ {0x3743, 0x01}, ++ {0x3744, 0x02}, ++ {0x3747, 0x10}, ++ {0x374c, 0x04}, ++ {0x3751, 0xf0}, ++ {0x3752, 0x00}, ++ {0x3753, 0x00}, ++ {0x3754, 0xc0}, ++ {0x3755, 0x00}, ++ {0x3756, 0x1a}, ++ {0x3758, 0x00}, ++ {0x3759, 0x0f}, ++ {0x376b, 0x44}, ++ {0x375c, 0x04}, ++ {0x3774, 0x10}, ++ {0x3776, 0x00}, ++ {0x377f, 0x08}, ++ {0x3780, 0x22}, ++ {0x3781, 0x0c}, ++ {0x3784, 0x2c}, ++ {0x3785, 0x1e}, ++ {0x378f, 0xf5}, ++ {0x3791, 0xb0}, ++ {0x3795, 0x00}, ++ {0x3796, 0x64}, ++ {0x3797, 0x11}, ++ {0x3798, 0x30}, ++ {0x3799, 0x41}, ++ {0x379a, 0x07}, ++ {0x379b, 0xb0}, ++ {0x379c, 0x0c}, ++ {0x3a04, 0x06}, ++ {0x3a05, 0x14}, ++ {0x3e07, 0x20}, ++ {0x4000, 0x08}, ++ {0x4001, 0x04}, ++ {0x4004, 0x08}, ++ {0x4006, 0x20}, ++ {0x4008, 0x24}, ++ {0x4009, 0x10}, ++ {0x4058, 0x00}, ++ {0x4101, 0xb2}, ++ {0x4307, 0x31}, ++ {0x4511, 0x05}, ++ {0x4512, 0x01}, ++ {0x481f, 0x30}, ++ {0x4826, 0x2c}, ++ {0x4d02, 0xfd}, ++ {0x4d03, 0xf5}, ++ {0x4d04, 0x0c}, ++ {0x4d05, 0xcc}, ++ {0x4837, 0x0a}, ++ {0x5003, 0x20}, ++ {0x5013, 0x00}, ++ {0x5842, 0x01}, ++ {0x5843, 0x2b}, ++ {0x5844, 0x01}, ++ {0x5845, 0x92}, ++ {0x5846, 0x01}, ++ {0x5847, 0x8f}, ++ {0x5848, 0x01}, ++ {0x5849, 0x0c}, ++ {0x5e10, 0x0c}, ++ {0x3820, 0x00}, ++ {0x3821, 0x1e}, ++ {0x5041, 0x14} ++}; ++ ++static const struct ov5693_reg_list ov5693_global_setting = { ++ .num_regs = ARRAY_SIZE(ov5693_global_regs), ++ .regs = ov5693_global_regs, ++}; ++ ++static const struct v4l2_rect ov5693_default_crop = { ++ .left = OV5693_ACTIVE_START_LEFT, ++ .top = OV5693_ACTIVE_START_TOP, ++ .width = OV5693_ACTIVE_WIDTH, ++ .height = OV5693_ACTIVE_HEIGHT, ++}; ++ ++static const struct v4l2_mbus_framefmt ov5693_default_fmt = { ++ .width = OV5693_ACTIVE_WIDTH, ++ .height = OV5693_ACTIVE_HEIGHT, ++ .code = MEDIA_BUS_FMT_SBGGR10_1X10, ++}; ++ ++static const s64 link_freq_menu_items[] = { ++ OV5693_LINK_FREQ_400MHZ ++}; ++ ++static const char * const ov5693_supply_names[] = { ++ "avdd", ++ "dovdd", ++}; ++ ++static const char * const ov5693_test_pattern_menu[] = { ++ "Disabled", ++ "Random Data", ++ "Colour Bars", ++ "Colour Bars with Rolling Bar" ++}; ++ ++static const u8 ov5693_test_pattern_bits[] = { ++ 0, ++ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_RANDOM, ++ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS, ++ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS | ++ OV5693_TEST_PATTERN_ROLLING, ++}; ++ ++/* I2C I/O Operations */ ++ ++static int ov5693_read_reg(struct ov5693_device *ov5693, u16 addr, u8 *value) ++{ ++ struct i2c_client *client = ov5693->client; ++ struct i2c_msg msgs[2]; ++ u8 addr_buf[2]; ++ u8 data_buf; ++ int ret; ++ ++ put_unaligned_be16(addr, addr_buf); ++ ++ /* Write register address */ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = 0; ++ msgs[0].len = ARRAY_SIZE(addr_buf); ++ msgs[0].buf = addr_buf; ++ ++ /* Read register value */ ++ msgs[1].addr = client->addr; ++ msgs[1].flags = I2C_M_RD; ++ msgs[1].len = 1; ++ msgs[1].buf = &data_buf; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret != ARRAY_SIZE(msgs)) ++ return -EIO; ++ ++ *value = data_buf; ++ ++ return 0; ++} ++ ++static void ov5693_write_reg(struct ov5693_device *ov5693, u16 addr, u8 value, ++ int *error) ++{ ++ unsigned char data[3] = { addr >> 8, addr & 0xff, value }; ++ int ret; ++ ++ if (*error < 0) ++ return; ++ ++ ret = i2c_master_send(ov5693->client, data, sizeof(data)); ++ if (ret < 0) { ++ dev_dbg(ov5693->dev, "i2c send error at address 0x%04x: %d\n", ++ addr, ret); ++ *error = ret; ++ } ++} ++ ++static int ov5693_write_reg_array(struct ov5693_device *ov5693, ++ const struct ov5693_reg_list *reglist) ++{ ++ unsigned int i; ++ int ret = 0; ++ ++ for (i = 0; i < reglist->num_regs; i++) ++ ov5693_write_reg(ov5693, reglist->regs[i].reg, ++ reglist->regs[i].val, &ret); ++ ++ return ret; ++} ++ ++static int ov5693_update_bits(struct ov5693_device *ov5693, u16 address, ++ u16 mask, u16 bits) ++{ ++ u8 value = 0; ++ int ret; ++ ++ ret = ov5693_read_reg(ov5693, address, &value); ++ if (ret) ++ return ret; ++ ++ value &= ~mask; ++ value |= bits; ++ ++ ov5693_write_reg(ov5693, address, value, &ret); ++ ++ return ret; ++} ++ ++/* V4L2 Controls Functions */ ++ ++static int ov5693_flip_vert_configure(struct ov5693_device *ov5693, bool enable) ++{ ++ u8 bits = OV5693_FORMAT1_FLIP_VERT_ISP_EN | ++ OV5693_FORMAT1_FLIP_VERT_SENSOR_EN; ++ int ret; ++ ++ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, bits, ++ enable ? bits : 0); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int ov5693_flip_horz_configure(struct ov5693_device *ov5693, bool enable) ++{ ++ u8 bits = OV5693_FORMAT2_FLIP_HORZ_ISP_EN | ++ OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN; ++ int ret; ++ ++ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, bits, ++ enable ? bits : 0); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int ov5693_get_exposure(struct ov5693_device *ov5693, s32 *value) ++{ ++ u8 exposure_hh = 0, exposure_h = 0, exposure_l = 0; ++ int ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, &exposure_hh); ++ if (ret) ++ return ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, &exposure_h); ++ if (ret) ++ return ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, &exposure_l); ++ if (ret) ++ return ret; ++ ++ /* The lowest 4 bits are unsupported fractional bits */ ++ *value = ((exposure_hh << 16) | (exposure_h << 8) | exposure_l) >> 4; ++ ++ return 0; ++} ++ ++static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) ++{ ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, ++ OV5693_EXPOSURE_CTRL_HH(exposure), &ret); ++ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, ++ OV5693_EXPOSURE_CTRL_H(exposure), &ret); ++ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, ++ OV5693_EXPOSURE_CTRL_L(exposure), &ret); ++ ++ return ret; ++} ++ ++static int ov5693_get_gain(struct ov5693_device *ov5693, u32 *gain) ++{ ++ u8 gain_l = 0, gain_h = 0; ++ int ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_H_REG, &gain_h); ++ if (ret) ++ return ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_L_REG, &gain_l); ++ if (ret) ++ return ret; ++ ++ /* As with exposure, the lowest 4 bits are fractional bits. */ ++ *gain = ((gain_h << 8) | gain_l) >> 4; ++ ++ return ret; ++} ++ ++static int ov5693_digital_gain_configure(struct ov5693_device *ov5693, u32 gain) ++{ ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_H_REG, ++ OV5693_MWB_GAIN_H_CTRL(gain), &ret); ++ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_L_REG, ++ OV5693_MWB_GAIN_L_CTRL(gain), &ret); ++ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_H_REG, ++ OV5693_MWB_GAIN_H_CTRL(gain), &ret); ++ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_L_REG, ++ OV5693_MWB_GAIN_L_CTRL(gain), &ret); ++ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_H_REG, ++ OV5693_MWB_GAIN_H_CTRL(gain), &ret); ++ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_L_REG, ++ OV5693_MWB_GAIN_L_CTRL(gain), &ret); ++ ++ return ret; ++} ++ ++static int ov5693_analog_gain_configure(struct ov5693_device *ov5693, u32 gain) ++{ ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_L_REG, ++ OV5693_GAIN_CTRL_L(gain), &ret); ++ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_H_REG, ++ OV5693_GAIN_CTRL_H(gain), &ret); ++ ++ return ret; ++} ++ ++static int ov5693_vts_configure(struct ov5693_device *ov5693, u32 vblank) ++{ ++ u16 vts = ov5693->mode.format.height + vblank; ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, ++ OV5693_TIMING_VTS_H(vts), &ret); ++ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, ++ OV5693_TIMING_VTS_L(vts), &ret); ++ ++ return ret; ++} ++ ++static int ov5693_test_pattern_configure(struct ov5693_device *ov5693, u32 idx) ++{ ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_TEST_PATTERN_REG, ++ ov5693_test_pattern_bits[idx], &ret); ++ ++ return ret; ++} ++ ++static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct ov5693_device *ov5693 = ++ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); ++ int ret = 0; ++ ++ /* If VBLANK is altered we need to update exposure to compensate */ ++ if (ctrl->id == V4L2_CID_VBLANK) { ++ int exposure_max; ++ ++ exposure_max = ov5693->mode.format.height + ctrl->val - ++ OV5693_INTEGRATION_TIME_MARGIN; ++ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, ++ ov5693->ctrls.exposure->minimum, ++ exposure_max, ++ ov5693->ctrls.exposure->step, ++ min(ov5693->ctrls.exposure->val, exposure_max)); ++ } ++ ++ /* Only apply changes to the controls if the device is powered up */ ++ if (!pm_runtime_get_if_in_use(ov5693->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_EXPOSURE: ++ ret = ov5693_exposure_configure(ov5693, ctrl->val); ++ break; ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = ov5693_analog_gain_configure(ov5693, ctrl->val); ++ break; ++ case V4L2_CID_DIGITAL_GAIN: ++ ret = ov5693_digital_gain_configure(ov5693, ctrl->val); ++ break; ++ case V4L2_CID_HFLIP: ++ ret = ov5693_flip_horz_configure(ov5693, !!ctrl->val); ++ break; ++ case V4L2_CID_VFLIP: ++ ret = ov5693_flip_vert_configure(ov5693, !!ctrl->val); ++ break; ++ case V4L2_CID_VBLANK: ++ ret = ov5693_vts_configure(ov5693, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN: ++ ret = ov5693_test_pattern_configure(ov5693, ctrl->val); ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ ++ pm_runtime_put(ov5693->dev); ++ ++ return ret; ++} ++ ++static int ov5693_g_volatile_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct ov5693_device *ov5693 = ++ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); ++ ++ switch (ctrl->id) { ++ case V4L2_CID_EXPOSURE_ABSOLUTE: ++ return ov5693_get_exposure(ov5693, &ctrl->val); ++ case V4L2_CID_AUTOGAIN: ++ return ov5693_get_gain(ov5693, &ctrl->val); ++ default: ++ return -EINVAL; ++ } ++} ++ ++static const struct v4l2_ctrl_ops ov5693_ctrl_ops = { ++ .s_ctrl = ov5693_s_ctrl, ++ .g_volatile_ctrl = ov5693_g_volatile_ctrl ++}; ++ ++/* System Control Functions */ ++ ++static int ov5693_mode_configure(struct ov5693_device *ov5693) ++{ ++ const struct ov5693_mode *mode = &ov5693->mode; ++ int ret = 0; ++ ++ /* Crop Start X */ ++ ov5693_write_reg(ov5693, OV5693_CROP_START_X_H_REG, ++ OV5693_CROP_START_X_H(mode->crop.left), &ret); ++ ov5693_write_reg(ov5693, OV5693_CROP_START_X_L_REG, ++ OV5693_CROP_START_X_L(mode->crop.left), &ret); ++ ++ /* Offset X */ ++ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_H_REG, ++ OV5693_OFFSET_START_X_H(0), &ret); ++ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_L_REG, ++ OV5693_OFFSET_START_X_L(0), &ret); ++ ++ /* Output Size X */ ++ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_H_REG, ++ OV5693_OUTPUT_SIZE_X_H(mode->format.width), &ret); ++ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_L_REG, ++ OV5693_OUTPUT_SIZE_X_L(mode->format.width), &ret); ++ ++ /* Crop End X */ ++ ov5693_write_reg(ov5693, OV5693_CROP_END_X_H_REG, ++ OV5693_CROP_END_X_H(mode->crop.left + mode->crop.width), ++ &ret); ++ ov5693_write_reg(ov5693, OV5693_CROP_END_X_L_REG, ++ OV5693_CROP_END_X_L(mode->crop.left + mode->crop.width), ++ &ret); ++ ++ /* Horizontal Total Size */ ++ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_H_REG, ++ OV5693_TIMING_HTS_H(OV5693_FIXED_PPL), &ret); ++ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_L_REG, ++ OV5693_TIMING_HTS_L(OV5693_FIXED_PPL), &ret); ++ ++ /* Crop Start Y */ ++ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_H_REG, ++ OV5693_CROP_START_Y_H(mode->crop.top), &ret); ++ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_L_REG, ++ OV5693_CROP_START_Y_L(mode->crop.top), &ret); ++ ++ /* Offset Y */ ++ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_H_REG, ++ OV5693_OFFSET_START_Y_H(0), &ret); ++ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_L_REG, ++ OV5693_OFFSET_START_Y_L(0), &ret); ++ ++ /* Output Size Y */ ++ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_H_REG, ++ OV5693_OUTPUT_SIZE_Y_H(mode->format.height), &ret); ++ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_L_REG, ++ OV5693_OUTPUT_SIZE_Y_L(mode->format.height), &ret); ++ ++ /* Crop End Y */ ++ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_H_REG, ++ OV5693_CROP_END_Y_H(mode->crop.top + mode->crop.height), ++ &ret); ++ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_L_REG, ++ OV5693_CROP_END_Y_L(mode->crop.top + mode->crop.height), ++ &ret); ++ ++ /* Vertical Total Size */ ++ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, ++ OV5693_TIMING_VTS_H(mode->vts), &ret); ++ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, ++ OV5693_TIMING_VTS_L(mode->vts), &ret); ++ ++ /* Subsample X increase */ ++ ov5693_write_reg(ov5693, OV5693_SUB_INC_X_REG, ++ ((mode->inc_x_odd << 4) & 0xf0) | 0x01, &ret); ++ /* Subsample Y increase */ ++ ov5693_write_reg(ov5693, OV5693_SUB_INC_Y_REG, ++ ((mode->inc_y_odd << 4) & 0xf0) | 0x01, &ret); ++ ++ /* Binning */ ++ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, ++ OV5693_FORMAT1_VBIN_EN, ++ mode->binning_y ? OV5693_FORMAT1_VBIN_EN : 0); ++ if (ret) ++ return ret; ++ ++ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, ++ OV5693_FORMAT2_HBIN_EN, ++ mode->binning_x ? OV5693_FORMAT2_HBIN_EN : 0); ++ ++ return ret; ++} ++ ++static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby) ++{ ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_SW_STREAM_REG, ++ standby ? OV5693_STOP_STREAMING : OV5693_START_STREAMING, ++ &ret); ++ ++ return ret; ++} ++ ++static int ov5693_sw_reset(struct ov5693_device *ov5693) ++{ ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_SW_RESET_REG, OV5693_SW_RESET, &ret); ++ ++ return ret; ++} ++ ++static int ov5693_sensor_init(struct ov5693_device *ov5693) ++{ ++ int ret = 0; ++ ++ ret = ov5693_sw_reset(ov5693); ++ if (ret) { ++ dev_err(ov5693->dev, "%s software reset error\n", __func__); ++ return ret; ++ } ++ ++ ret = ov5693_write_reg_array(ov5693, &ov5693_global_setting); ++ if (ret) { ++ dev_err(ov5693->dev, "%s global settings error\n", __func__); ++ return ret; ++ } ++ ++ ret = ov5693_mode_configure(ov5693); ++ if (ret) { ++ dev_err(ov5693->dev, "%s mode configure error\n", __func__); ++ return ret; ++ } ++ ++ ret = ov5693_sw_standby(ov5693, true); ++ if (ret) ++ dev_err(ov5693->dev, "%s software standby error\n", __func__); ++ ++ return ret; ++} ++ ++static void ov5693_sensor_powerdown(struct ov5693_device *ov5693) ++{ ++ gpiod_set_value_cansleep(ov5693->reset, 1); ++ gpiod_set_value_cansleep(ov5693->powerdown, 1); ++ ++ regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies); ++ ++ clk_disable_unprepare(ov5693->clk); ++} ++ ++static int ov5693_sensor_powerup(struct ov5693_device *ov5693) ++{ ++ int ret; ++ ++ gpiod_set_value_cansleep(ov5693->reset, 1); ++ gpiod_set_value_cansleep(ov5693->powerdown, 1); ++ ++ ret = clk_prepare_enable(ov5693->clk); ++ if (ret) { ++ dev_err(ov5693->dev, "Failed to enable clk\n"); ++ goto fail_power; ++ } ++ ++ ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, ov5693->supplies); ++ if (ret) { ++ dev_err(ov5693->dev, "Failed to enable regulators\n"); ++ goto fail_power; ++ } ++ ++ gpiod_set_value_cansleep(ov5693->powerdown, 0); ++ gpiod_set_value_cansleep(ov5693->reset, 0); ++ ++ usleep_range(5000, 7500); ++ ++ return 0; ++ ++fail_power: ++ ov5693_sensor_powerdown(ov5693); ++ return ret; ++} ++ ++static int __maybe_unused ov5693_sensor_suspend(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ ++ ov5693_sensor_powerdown(ov5693); ++ ++ return 0; ++} ++ ++static int __maybe_unused ov5693_sensor_resume(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ int ret; ++ ++ mutex_lock(&ov5693->lock); ++ ++ ret = ov5693_sensor_powerup(ov5693); ++ if (ret) ++ goto out_unlock; ++ ++ ret = ov5693_sensor_init(ov5693); ++ if (ret) { ++ dev_err(dev, "ov5693 sensor init failure\n"); ++ goto err_power; ++ } ++ ++ goto out_unlock; ++ ++err_power: ++ ov5693_sensor_powerdown(ov5693); ++out_unlock: ++ mutex_unlock(&ov5693->lock); ++ return ret; ++} ++ ++static int ov5693_detect(struct ov5693_device *ov5693) ++{ ++ u8 id_l = 0, id_h = 0; ++ u16 id = 0; ++ int ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_H, &id_h); ++ if (ret) ++ return ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_L, &id_l); ++ if (ret) ++ return ret; ++ ++ id = (id_h << 8) | id_l; ++ ++ if (id != OV5693_CHIP_ID) { ++ dev_err(ov5693->dev, "sensor ID mismatch. Found 0x%04x\n", id); ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++/* V4L2 Framework callbacks */ ++ ++static unsigned int __ov5693_calc_vts(u32 height) ++{ ++ /* ++ * We need to set a sensible default VTS for whatever format height we ++ * happen to be given from set_fmt(). This function just targets ++ * an even multiple of 30fps. ++ */ ++ ++ unsigned int tgt_fps; ++ ++ tgt_fps = rounddown(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / height, 30); ++ ++ return ALIGN_DOWN(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / tgt_fps, 2); ++} ++ ++static struct v4l2_mbus_framefmt * ++__ov5693_get_pad_format(struct ov5693_device *ov5693, ++ struct v4l2_subdev_pad_config *cfg, ++ unsigned int pad, enum v4l2_subdev_format_whence which) ++{ ++ switch (which) { ++ case V4L2_SUBDEV_FORMAT_TRY: ++ return v4l2_subdev_get_try_format(&ov5693->sd, cfg, pad); ++ case V4L2_SUBDEV_FORMAT_ACTIVE: ++ return &ov5693->mode.format; ++ default: ++ return NULL; ++ } ++} ++ ++static struct v4l2_rect * ++__ov5693_get_pad_crop(struct ov5693_device *ov5693, ++ struct v4l2_subdev_pad_config *cfg, ++ unsigned int pad, enum v4l2_subdev_format_whence which) ++{ ++ switch (which) { ++ case V4L2_SUBDEV_FORMAT_TRY: ++ return v4l2_subdev_get_try_crop(&ov5693->sd, cfg, pad); ++ case V4L2_SUBDEV_FORMAT_ACTIVE: ++ return &ov5693->mode.crop; ++ } ++ ++ return NULL; ++} ++ ++static int ov5693_get_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_pad_config *cfg, ++ struct v4l2_subdev_format *format) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ ++ format->format = ov5693->mode.format; ++ ++ return 0; ++} ++ ++static int ov5693_set_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_pad_config *cfg, ++ struct v4l2_subdev_format *format) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ const struct v4l2_rect *crop; ++ struct v4l2_mbus_framefmt *fmt; ++ unsigned int hratio, vratio; ++ unsigned int width, height; ++ unsigned int hblank; ++ int exposure_max; ++ int ret = 0; ++ ++ crop = __ov5693_get_pad_crop(ov5693, cfg, format->pad, format->which); ++ ++ /* ++ * Align to two to simplify the binning calculations below, and clamp ++ * the requested format at the crop rectangle ++ */ ++ width = clamp_t(unsigned int, ALIGN(format->format.width, 2), ++ OV5693_MIN_CROP_WIDTH, crop->width); ++ height = clamp_t(unsigned int, ALIGN(format->format.height, 2), ++ OV5693_MIN_CROP_HEIGHT, crop->height); ++ ++ /* ++ * We can only support setting either the dimensions of the crop rect ++ * or those dimensions binned (separately) by a factor of two. ++ */ ++ hratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->width, width), 1, 2); ++ vratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->height, height), 1, 2); ++ ++ fmt = __ov5693_get_pad_format(ov5693, cfg, format->pad, format->which); ++ ++ fmt->width = crop->width / hratio; ++ fmt->height = crop->height / vratio; ++ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; ++ ++ format->format = *fmt; ++ ++ if (format->which == V4L2_SUBDEV_FORMAT_TRY) ++ return ret; ++ ++ mutex_lock(&ov5693->lock); ++ ++ ov5693->mode.binning_x = hratio > 1 ? true : false; ++ ov5693->mode.inc_x_odd = hratio > 1 ? 3 : 1; ++ ov5693->mode.binning_y = vratio > 1 ? true : false; ++ ov5693->mode.inc_y_odd = vratio > 1 ? 3 : 1; ++ ++ ov5693->mode.vts = __ov5693_calc_vts(fmt->height); ++ ++ __v4l2_ctrl_modify_range(ov5693->ctrls.vblank, ++ OV5693_TIMING_MIN_VTS, ++ OV5693_TIMING_MAX_VTS - fmt->height, ++ 1, ov5693->mode.vts - fmt->height); ++ __v4l2_ctrl_s_ctrl(ov5693->ctrls.vblank, ++ ov5693->mode.vts - fmt->height); ++ ++ hblank = OV5693_FIXED_PPL - fmt->width; ++ __v4l2_ctrl_modify_range(ov5693->ctrls.hblank, hblank, hblank, 1, ++ hblank); ++ ++ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; ++ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, ++ ov5693->ctrls.exposure->minimum, exposure_max, ++ ov5693->ctrls.exposure->step, ++ min(ov5693->ctrls.exposure->val, exposure_max)); ++ ++ mutex_unlock(&ov5693->lock); ++ return ret; ++} ++ ++static int ov5693_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_pad_config *cfg, ++ struct v4l2_subdev_selection *sel) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: ++ mutex_lock(&ov5693->lock); ++ sel->r = *__ov5693_get_pad_crop(ov5693, cfg, sel->pad, ++ sel->which); ++ mutex_unlock(&ov5693->lock); ++ break; ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = 0; ++ sel->r.left = 0; ++ sel->r.width = OV5693_NATIVE_WIDTH; ++ sel->r.height = OV5693_NATIVE_HEIGHT; ++ break; ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ sel->r.top = OV5693_ACTIVE_START_TOP; ++ sel->r.left = OV5693_ACTIVE_START_LEFT; ++ sel->r.width = OV5693_ACTIVE_WIDTH; ++ sel->r.height = OV5693_ACTIVE_HEIGHT; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int ov5693_set_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_pad_config *cfg, ++ struct v4l2_subdev_selection *sel) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ struct v4l2_mbus_framefmt *format; ++ struct v4l2_rect *__crop; ++ struct v4l2_rect rect; ++ ++ if (sel->target != V4L2_SEL_TGT_CROP) ++ return -EINVAL; ++ ++ /* ++ * Clamp the boundaries of the crop rectangle to the size of the sensor ++ * pixel array. Align to multiples of 2 to ensure Bayer pattern isn't ++ * disrupted. ++ */ ++ rect.left = clamp(ALIGN(sel->r.left, 2), OV5693_NATIVE_START_LEFT, ++ OV5693_NATIVE_WIDTH); ++ rect.top = clamp(ALIGN(sel->r.top, 2), OV5693_NATIVE_START_TOP, ++ OV5693_NATIVE_HEIGHT); ++ rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), ++ OV5693_MIN_CROP_WIDTH, OV5693_NATIVE_WIDTH); ++ rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), ++ OV5693_MIN_CROP_HEIGHT, OV5693_NATIVE_HEIGHT); ++ ++ /* Make sure the crop rectangle isn't outside the bounds of the array */ ++ rect.width = min_t(unsigned int, rect.width, ++ OV5693_NATIVE_WIDTH - rect.left); ++ rect.height = min_t(unsigned int, rect.height, ++ OV5693_NATIVE_HEIGHT - rect.top); ++ ++ __crop = __ov5693_get_pad_crop(ov5693, cfg, sel->pad, sel->which); ++ ++ if (rect.width != __crop->width || rect.height != __crop->height) { ++ /* ++ * Reset the output image size if the crop rectangle size has ++ * been modified. ++ */ ++ format = __ov5693_get_pad_format(ov5693, cfg, sel->pad, sel->which); ++ format->width = rect.width; ++ format->height = rect.height; ++ } ++ ++ *__crop = rect; ++ sel->r = rect; ++ ++ return 0; ++} ++ ++static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ int ret; ++ ++ if (enable) { ++ ret = pm_runtime_get_sync(ov5693->dev); ++ if (ret < 0) ++ goto err_power_down; ++ ++ ret = __v4l2_ctrl_handler_setup(&ov5693->ctrls.handler); ++ if (ret) ++ goto err_power_down; ++ } ++ ++ mutex_lock(&ov5693->lock); ++ ret = ov5693_sw_standby(ov5693, !enable); ++ mutex_unlock(&ov5693->lock); ++ ++ if (ret) ++ goto err_power_down; ++ ov5693->streaming = !!enable; ++ ++ if (!enable) ++ pm_runtime_put(ov5693->dev); ++ ++ return 0; ++err_power_down: ++ pm_runtime_put_noidle(ov5693->dev); ++ return ret; ++} ++ ++static int ov5693_g_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_frame_interval *interval) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ unsigned int framesize = OV5693_FIXED_PPL * (ov5693->mode.format.height + ++ ov5693->ctrls.vblank->val); ++ unsigned int fps = DIV_ROUND_CLOSEST(OV5693_PIXEL_RATE, framesize); ++ ++ interval->interval.numerator = 1; ++ interval->interval.denominator = fps; ++ ++ return 0; ++} ++ ++static int ov5693_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_pad_config *cfg, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ /* Only a single mbus format is supported */ ++ if (code->index > 0) ++ return -EINVAL; ++ ++ code->code = MEDIA_BUS_FMT_SBGGR10_1X10; ++ return 0; ++} ++ ++static int ov5693_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_pad_config *cfg, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ struct v4l2_rect *__crop; ++ ++ if (fse->index > 1 || fse->code != MEDIA_BUS_FMT_SBGGR10_1X10) ++ return -EINVAL; ++ ++ __crop = __ov5693_get_pad_crop(ov5693, cfg, fse->pad, fse->which); ++ if (!__crop) ++ return -EINVAL; ++ ++ fse->min_width = __crop->width / (fse->index + 1); ++ fse->min_height = __crop->height / (fse->index + 1); ++ fse->max_width = fse->min_width; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_video_ops ov5693_video_ops = { ++ .s_stream = ov5693_s_stream, ++ .g_frame_interval = ov5693_g_frame_interval, ++}; ++ ++static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { ++ .enum_mbus_code = ov5693_enum_mbus_code, ++ .enum_frame_size = ov5693_enum_frame_size, ++ .get_fmt = ov5693_get_fmt, ++ .set_fmt = ov5693_set_fmt, ++ .get_selection = ov5693_get_selection, ++ .set_selection = ov5693_set_selection, ++}; ++ ++static const struct v4l2_subdev_ops ov5693_ops = { ++ .video = &ov5693_video_ops, ++ .pad = &ov5693_pad_ops, ++}; ++ ++/* Sensor and Driver Configuration Functions */ ++ ++static int ov5693_init_controls(struct ov5693_device *ov5693) ++{ ++ const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; ++ struct v4l2_fwnode_device_properties props; ++ int vblank_max, vblank_def; ++ int exposure_max; ++ int hblank; ++ int ret; ++ ++ ret = v4l2_ctrl_handler_init(&ov5693->ctrls.handler, 12); ++ if (ret) ++ return ret; ++ ++ /* link freq */ ++ ov5693->ctrls.link_freq = v4l2_ctrl_new_int_menu(&ov5693->ctrls.handler, ++ NULL, V4L2_CID_LINK_FREQ, ++ 0, 0, link_freq_menu_items); ++ if (ov5693->ctrls.link_freq) ++ ov5693->ctrls.link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ++ /* pixel rate */ ++ ov5693->ctrls.pixel_rate = v4l2_ctrl_new_std(&ov5693->ctrls.handler, NULL, ++ V4L2_CID_PIXEL_RATE, 0, ++ OV5693_PIXEL_RATE, 1, ++ OV5693_PIXEL_RATE); ++ ++ /* Exposure */ ++ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; ++ ov5693->ctrls.exposure = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, ++ V4L2_CID_EXPOSURE, ++ OV5693_EXPOSURE_MIN, ++ exposure_max, ++ OV5693_EXPOSURE_STEP, ++ exposure_max); ++ ++ /* Gain */ ++ ov5693->ctrls.analogue_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ++ ops, V4L2_CID_ANALOGUE_GAIN, ++ OV5693_GAIN_MIN, ++ OV5693_GAIN_MAX, ++ OV5693_GAIN_STEP, ++ OV5693_GAIN_DEF); ++ ++ ov5693->ctrls.digital_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, ++ V4L2_CID_DIGITAL_GAIN, ++ OV5693_DIGITAL_GAIN_MIN, ++ OV5693_DIGITAL_GAIN_MAX, ++ OV5693_DIGITAL_GAIN_STEP, ++ OV5693_DIGITAL_GAIN_DEF); ++ ++ /* Flip */ ++ ov5693->ctrls.hflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, ++ V4L2_CID_HFLIP, 0, 1, 1, 0); ++ ++ ov5693->ctrls.vflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, ++ V4L2_CID_VFLIP, 0, 1, 1, 0); ++ ++ hblank = OV5693_FIXED_PPL - ov5693->mode.format.width; ++ ov5693->ctrls.hblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, ++ V4L2_CID_HBLANK, hblank, ++ hblank, 1, hblank); ++ ++ if (ov5693->ctrls.hblank) ++ ov5693->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ++ vblank_max = OV5693_TIMING_MAX_VTS - ov5693->mode.format.height; ++ vblank_def = ov5693->mode.vts - ov5693->mode.format.height; ++ ov5693->ctrls.vblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, ++ V4L2_CID_VBLANK, ++ OV5693_TIMING_MIN_VTS, ++ vblank_max, 1, vblank_def); ++ ++ ov5693->ctrls.test_pattern = v4l2_ctrl_new_std_menu_items( ++ &ov5693->ctrls.handler, ops, ++ V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(ov5693_test_pattern_menu) - 1, ++ 0, 0, ov5693_test_pattern_menu); ++ ++ if (ov5693->ctrls.handler.error) { ++ dev_err(ov5693->dev, "Error initialising v4l2 ctrls\n"); ++ ret = ov5693->ctrls.handler.error; ++ goto err_free_handler; ++ } ++ ++ /* set properties from fwnode (e.g. rotation, orientation) */ ++ ret = v4l2_fwnode_device_parse(ov5693->dev, &props); ++ if (ret) ++ goto err_free_handler; ++ ++ ret = v4l2_ctrl_new_fwnode_properties(&ov5693->ctrls.handler, ops, ++ &props); ++ if (ret) ++ goto err_free_handler; ++ ++ /* Use same lock for controls as for everything else. */ ++ ov5693->ctrls.handler.lock = &ov5693->lock; ++ ov5693->sd.ctrl_handler = &ov5693->ctrls.handler; ++ ++ return 0; ++ ++err_free_handler: ++ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); ++ return ret; ++} ++ ++static int ov5693_configure_gpios(struct ov5693_device *ov5693) ++{ ++ ov5693->reset = devm_gpiod_get_optional(ov5693->dev, "reset", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(ov5693->reset)) { ++ dev_err(ov5693->dev, "Error fetching reset GPIO\n"); ++ return PTR_ERR(ov5693->reset); ++ } ++ ++ ov5693->powerdown = devm_gpiod_get_optional(ov5693->dev, "powerdown", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(ov5693->powerdown)) { ++ dev_err(ov5693->dev, "Error fetching powerdown GPIO\n"); ++ return PTR_ERR(ov5693->powerdown); ++ } ++ ++ return 0; ++} ++ ++static int ov5693_get_regulators(struct ov5693_device *ov5693) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < OV5693_NUM_SUPPLIES; i++) ++ ov5693->supplies[i].supply = ov5693_supply_names[i]; ++ ++ return devm_regulator_bulk_get(ov5693->dev, OV5693_NUM_SUPPLIES, ++ ov5693->supplies); ++} ++ ++static int ov5693_probe(struct i2c_client *client) ++{ ++ struct fwnode_handle *fwnode = dev_fwnode(&client->dev); ++ struct fwnode_handle *endpoint; ++ struct ov5693_device *ov5693; ++ u32 clk_rate; ++ int ret = 0; ++ ++ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); ++ if (!endpoint && !IS_ERR_OR_NULL(fwnode->secondary)) ++ endpoint = fwnode_graph_get_next_endpoint(fwnode->secondary, NULL); ++ if (!endpoint) ++ return -EPROBE_DEFER; ++ ++ ov5693 = devm_kzalloc(&client->dev, sizeof(*ov5693), GFP_KERNEL); ++ if (!ov5693) ++ return -ENOMEM; ++ ++ ov5693->client = client; ++ ov5693->dev = &client->dev; ++ ++ mutex_init(&ov5693->lock); ++ ++ v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops); ++ ++ ov5693->clk = devm_clk_get(&client->dev, "xvclk"); ++ if (IS_ERR(ov5693->clk)) { ++ dev_err(&client->dev, "Error getting clock\n"); ++ return PTR_ERR(ov5693->clk); ++ } ++ ++ clk_rate = clk_get_rate(ov5693->clk); ++ if (clk_rate != OV5693_XVCLK_FREQ) { ++ dev_err(&client->dev, "Unsupported clk freq %u, expected %u\n", ++ clk_rate, OV5693_XVCLK_FREQ); ++ return -EINVAL; ++ } ++ ++ ret = ov5693_configure_gpios(ov5693); ++ if (ret) ++ return ret; ++ ++ ret = ov5693_get_regulators(ov5693); ++ if (ret) { ++ dev_err(&client->dev, "Error fetching regulators\n"); ++ return ret; ++ } ++ ++ ov5693->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ++ ov5693->pad.flags = MEDIA_PAD_FL_SOURCE; ++ ov5693->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ++ ov5693->mode.crop = ov5693_default_crop; ++ ov5693->mode.format = ov5693_default_fmt; ++ ov5693->mode.vts = __ov5693_calc_vts(ov5693->mode.format.height); ++ ++ ret = ov5693_init_controls(ov5693); ++ if (ret) ++ return ret; ++ ++ ret = media_entity_pads_init(&ov5693->sd.entity, 1, &ov5693->pad); ++ if (ret) ++ goto err_ctrl_handler_free; ++ ++ /* ++ * We need the driver to work in the event that pm runtime is disable in ++ * the kernel, so power up and verify the chip now. In the event that ++ * runtime pm is disabled this will leave the chip on, so that streaming ++ * will work. ++ */ ++ ++ ret = ov5693_sensor_powerup(ov5693); ++ if (ret) ++ goto err_media_entity_cleanup; ++ ++ ret = ov5693_detect(ov5693); ++ if (ret) ++ goto err_powerdown; ++ ++ pm_runtime_set_active(&client->dev); ++ pm_runtime_get_noresume(&client->dev); ++ pm_runtime_enable(&client->dev); ++ ++ ret = v4l2_async_register_subdev_sensor_common(&ov5693->sd); ++ if (ret) { ++ dev_err(&client->dev, "failed to register V4L2 subdev: %d", ++ ret); ++ goto err_pm_runtime; ++ } ++ ++ pm_runtime_set_autosuspend_delay(&client->dev, 1000); ++ pm_runtime_use_autosuspend(&client->dev); ++ pm_runtime_put_autosuspend(&client->dev); ++ ++ return ret; ++ ++err_pm_runtime: ++ pm_runtime_disable(&client->dev); ++ pm_runtime_put_noidle(&client->dev); ++err_powerdown: ++ ov5693_sensor_powerdown(ov5693); ++err_media_entity_cleanup: ++ media_entity_cleanup(&ov5693->sd.entity); ++err_ctrl_handler_free: ++ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); ++ ++ return ret; ++} ++ ++static int ov5693_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ ++ v4l2_async_unregister_subdev(sd); ++ media_entity_cleanup(&ov5693->sd.entity); ++ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); ++ mutex_destroy(&ov5693->lock); ++ ++ /* ++ * Disable runtime PM. In case runtime PM is disabled in the kernel, ++ * make sure to turn power off manually. ++ */ ++ pm_runtime_disable(&client->dev); ++ if (!pm_runtime_status_suspended(&client->dev)) ++ ov5693_sensor_powerdown(ov5693); ++ pm_runtime_set_suspended(&client->dev); ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops ov5693_pm_ops = { ++ SET_RUNTIME_PM_OPS(ov5693_sensor_suspend, ov5693_sensor_resume, NULL) ++}; ++ ++static const struct acpi_device_id ov5693_acpi_match[] = { ++ {"INT33BE"}, ++ {}, ++}; ++MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); ++ ++static struct i2c_driver ov5693_driver = { ++ .driver = { ++ .name = "ov5693", ++ .acpi_match_table = ov5693_acpi_match, ++ .pm = &ov5693_pm_ops, ++ }, ++ .probe_new = ov5693_probe, ++ .remove = ov5693_remove, ++}; ++module_i2c_driver(ov5693_driver); ++ ++MODULE_DESCRIPTION("A low-level driver for OmniVision 5693 sensors"); ++MODULE_LICENSE("GPL"); +-- +2.31.1 + +From 50a9c72845f5a7f0ce6411b59a49a724b04cb92d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= +Date: Fri, 22 Jan 2021 20:58:13 +0100 +Subject: [PATCH] cio2-bridge: Parse sensor orientation and rotation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The sensor orientation is read from the _PLC ACPI buffer and converted +to a v4l2 format. + +See https://uefi.org/sites/default/files/resources/ACPI_6_3_final_Jan30.pdf +page 351 for a definition of the Panel property. + +The sensor rotation is read from the SSDB ACPI buffer and converted into +degrees. + +Signed-off-by: Fabian Wüthrich +Patchset: cameras +--- + drivers/media/pci/intel/ipu3/cio2-bridge.c | 45 ++++++++++++++++++++-- + drivers/media/pci/intel/ipu3/cio2-bridge.h | 3 ++ + 2 files changed, 44 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c +index c2199042d3db..9a8a4a55d6a7 100644 +--- a/drivers/media/pci/intel/ipu3/cio2-bridge.c ++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c +@@ -29,6 +29,7 @@ static const struct cio2_sensor_config cio2_supported_sensors[] = { + static const struct cio2_property_names prop_names = { + .clock_frequency = "clock-frequency", + .rotation = "rotation", ++ .orientation = "orientation", + .bus_type = "bus-type", + .data_lanes = "data-lanes", + .remote_endpoint = "remote-endpoint", +@@ -72,11 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, + return ret; + } + ++static u32 cio2_bridge_parse_rotation(u8 rotation) ++{ ++ if (rotation == 1) ++ return 180; ++ return 0; ++} ++ ++static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) ++{ ++ switch (panel) { ++ case 4: ++ return V4L2_FWNODE_ORIENTATION_FRONT; ++ case 5: ++ return V4L2_FWNODE_ORIENTATION_BACK; ++ default: ++ return V4L2_FWNODE_ORIENTATION_EXTERNAL; ++ } ++} ++ + static void cio2_bridge_create_fwnode_properties( + struct cio2_sensor *sensor, + struct cio2_bridge *bridge, + const struct cio2_sensor_config *cfg) + { ++ u32 rotation; ++ enum v4l2_fwnode_orientation orientation; ++ ++ rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); ++ orientation = cio2_bridge_parse_orientation(sensor->pld->panel); ++ + sensor->prop_names = prop_names; + + sensor->local_ref[0].node = &sensor->swnodes[SWNODE_CIO2_ENDPOINT]; +@@ -85,9 +111,12 @@ static void cio2_bridge_create_fwnode_properties( + sensor->dev_properties[0] = PROPERTY_ENTRY_U32( + sensor->prop_names.clock_frequency, + sensor->ssdb.mclkspeed); +- sensor->dev_properties[1] = PROPERTY_ENTRY_U8( ++ sensor->dev_properties[1] = PROPERTY_ENTRY_U32( + sensor->prop_names.rotation, +- sensor->ssdb.degree); ++ rotation); ++ sensor->dev_properties[2] = PROPERTY_ENTRY_U32( ++ sensor->prop_names.orientation, ++ orientation); + + sensor->ep_properties[0] = PROPERTY_ENTRY_U32( + sensor->prop_names.bus_type, +@@ -159,6 +188,7 @@ static void cio2_bridge_unregister_sensors(struct cio2_bridge *bridge) + for (i = 0; i < bridge->n_sensors; i++) { + sensor = &bridge->sensors[i]; + software_node_unregister_nodes(sensor->swnodes); ++ ACPI_FREE(sensor->pld); + acpi_dev_put(sensor->adev); + } + } +@@ -170,6 +200,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, + struct fwnode_handle *fwnode; + struct cio2_sensor *sensor; + struct acpi_device *adev; ++ acpi_status status; + int ret; + + for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { +@@ -193,11 +224,15 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, + if (ret) + goto err_put_adev; + ++ status = acpi_get_physical_device_location(adev->handle, &sensor->pld); ++ if (ACPI_FAILURE(status)) ++ goto err_put_adev; ++ + if (sensor->ssdb.lanes > CIO2_MAX_LANES) { + dev_err(&adev->dev, + "Number of lanes in SSDB is invalid\n"); + ret = -EINVAL; +- goto err_put_adev; ++ goto err_free_pld; + } + + cio2_bridge_create_fwnode_properties(sensor, bridge, cfg); +@@ -205,7 +240,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, + + ret = software_node_register_nodes(sensor->swnodes); + if (ret) +- goto err_put_adev; ++ goto err_free_pld; + + fwnode = software_node_fwnode(&sensor->swnodes[ + SWNODE_SENSOR_HID]); +@@ -226,6 +261,8 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, + + err_free_swnodes: + software_node_unregister_nodes(sensor->swnodes); ++err_free_pld: ++ ACPI_FREE(sensor->pld); + err_put_adev: + acpi_dev_put(sensor->adev); + err_out: +diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h +index dd0ffcafa489..924d99d20328 100644 +--- a/drivers/media/pci/intel/ipu3/cio2-bridge.h ++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h +@@ -80,6 +80,7 @@ struct cio2_sensor_ssdb { + struct cio2_property_names { + char clock_frequency[16]; + char rotation[9]; ++ char orientation[12]; + char bus_type[9]; + char data_lanes[11]; + char remote_endpoint[16]; +@@ -106,6 +107,8 @@ struct cio2_sensor { + struct cio2_node_names node_names; + + struct cio2_sensor_ssdb ssdb; ++ struct acpi_pld_info *pld; ++ + struct cio2_property_names prop_names; + struct property_entry ep_properties[5]; + struct property_entry dev_properties[3]; +-- +2.31.1 + +From b8227ed525b8a609ae311c7c7d310498c29e94fd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= +Date: Sun, 24 Jan 2021 11:07:42 +0100 +Subject: [PATCH] cio2-bridge: Use macros and add warnings +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Use macros for the _PLD panel as defined in the ACPI spec 6.3 and emit +a warning if we see an unknown value. + +Signed-off-by: Fabian Wüthrich +Patchset: cameras +--- + drivers/media/pci/intel/ipu3/cio2-bridge.c | 33 ++++++++++++++++------ + drivers/media/pci/intel/ipu3/cio2-bridge.h | 13 +++++++++ + 2 files changed, 37 insertions(+), 9 deletions(-) + +diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c +index 9a8a4a55d6a7..503809907b92 100644 +--- a/drivers/media/pci/intel/ipu3/cio2-bridge.c ++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c +@@ -73,21 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, + return ret; + } + +-static u32 cio2_bridge_parse_rotation(u8 rotation) ++static u32 cio2_bridge_parse_rotation(struct cio2_sensor *sensor) + { +- if (rotation == 1) ++ switch (sensor->ssdb.degree) { ++ case CIO2_SENSOR_ROTATION_NORMAL: ++ return 0; ++ case CIO2_SENSOR_ROTATION_INVERTED: + return 180; +- return 0; ++ default: ++ dev_warn(&sensor->adev->dev, ++ "Unknown rotation %d. Assume 0 degree rotation\n", ++ sensor->ssdb.degree); ++ return 0; ++ } + } + +-static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) ++static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(struct cio2_sensor *sensor) + { +- switch (panel) { +- case 4: ++ switch (sensor->pld->panel) { ++ case CIO2_PLD_PANEL_FRONT: + return V4L2_FWNODE_ORIENTATION_FRONT; +- case 5: ++ case CIO2_PLD_PANEL_BACK: + return V4L2_FWNODE_ORIENTATION_BACK; ++ case CIO2_PLD_PANEL_TOP: ++ case CIO2_PLD_PANEL_LEFT: ++ case CIO2_PLD_PANEL_RIGHT: ++ case CIO2_PLD_PANEL_UNKNOWN: ++ return V4L2_FWNODE_ORIENTATION_EXTERNAL; + default: ++ dev_warn(&sensor->adev->dev, "Unknown _PLD panel value %d\n", ++ sensor->pld->panel); + return V4L2_FWNODE_ORIENTATION_EXTERNAL; + } + } +@@ -100,8 +115,8 @@ static void cio2_bridge_create_fwnode_properties( + u32 rotation; + enum v4l2_fwnode_orientation orientation; + +- rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); +- orientation = cio2_bridge_parse_orientation(sensor->pld->panel); ++ rotation = cio2_bridge_parse_rotation(sensor); ++ orientation = cio2_bridge_parse_orientation(sensor); + + sensor->prop_names = prop_names; + +diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h +index 924d99d20328..e1e388cc9f45 100644 +--- a/drivers/media/pci/intel/ipu3/cio2-bridge.h ++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h +@@ -12,6 +12,19 @@ + #define CIO2_MAX_LANES 4 + #define MAX_NUM_LINK_FREQS 3 + ++/* Values are estimated guesses as we don't have a spec */ ++#define CIO2_SENSOR_ROTATION_NORMAL 0 ++#define CIO2_SENSOR_ROTATION_INVERTED 1 ++ ++/* Panel position defined in _PLD section of ACPI Specification 6.3 */ ++#define CIO2_PLD_PANEL_TOP 0 ++#define CIO2_PLD_PANEL_BOTTOM 1 ++#define CIO2_PLD_PANEL_LEFT 2 ++#define CIO2_PLD_PANEL_RIGHT 3 ++#define CIO2_PLD_PANEL_FRONT 4 ++#define CIO2_PLD_PANEL_BACK 5 ++#define CIO2_PLD_PANEL_UNKNOWN 6 ++ + #define CIO2_SENSOR_CONFIG(_HID, _NR, ...) \ + (const struct cio2_sensor_config) { \ + .hid = _HID, \ +-- +2.31.1 +