From 24686c656a230f642f8ed6c09c184660c08cf46c Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 9 Jun 2024 19:48:58 +0200 Subject: [PATCH] Revert "efi/x86: Set the PE/COFF header's NX compat flag unconditionally" This reverts commit 891f8890a4a3663da7056542757022870b499bc1. Revert because of compatibility issues of MS Surface devices and GRUB with NX. In short, these devices get stuck on boot with NX advertised. So to not advertise it, add the respective option back in. Signed-off-by: Maximilian Luz Patchset: secureboot --- arch/x86/boot/header.S | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/x86/boot/header.S b/arch/x86/boot/header.S index b5c79f43359bc..a1bbedd989e42 100644 --- a/arch/x86/boot/header.S +++ b/arch/x86/boot/header.S @@ -111,7 +111,11 @@ extra_header_fields: .long salign # SizeOfHeaders .long 0 # CheckSum .word IMAGE_SUBSYSTEM_EFI_APPLICATION # Subsystem (EFI application) +#ifdef CONFIG_EFI_DXE_MEM_ATTRIBUTES .word IMAGE_DLL_CHARACTERISTICS_NX_COMPAT # DllCharacteristics +#else + .word 0 # DllCharacteristics +#endif #ifdef CONFIG_X86_32 .long 0 # SizeOfStackReserve .long 0 # SizeOfStackCommit -- 2.45.1 From a494cdb84ee162accff966a0012992e36e4b0c0a 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 c15ed7a12784a..1ec8edb5aafaf 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 d0d24a53df746..43e06166a5d95 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3777,6 +3777,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 5e2ec60e2954b..207868c699f29 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.45.1 From 1abf1feb3b521abe9f9c9e8d68d2014e90ecb20d 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 5f997becdbaa2..9a9929424513a 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie.c +++ b/drivers/net/wireless/marvell/mwifiex/pcie.c @@ -1702,9 +1702,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) static void 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 */ 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 dd6d21f1dbfd7..f46b06f8d6435 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c @@ -13,7 +13,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_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Pro 5", @@ -22,7 +23,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_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Pro 5 (LTE)", @@ -31,7 +33,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_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Pro 6", @@ -39,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_NAME, "Surface Pro 6"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Book 1", @@ -47,7 +51,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_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Book 2", @@ -55,7 +60,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_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Laptop 1", @@ -63,7 +69,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_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Laptop 2", @@ -71,7 +78,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_DO_FLR_ON_BRIDGE), }, {} }; @@ -89,6 +97,8 @@ 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_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 d6ff964aec5bf..5d30ae39d65ec 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h @@ -4,6 +4,7 @@ #include "pcie.h" #define QUIRK_FW_RST_D3COLD BIT(0) +#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) void mwifiex_initialize_quirks(struct pcie_service_card *card); int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -- 2.45.1 From 0574bff98f5f5af132783f8f72e8ef22e3f36097 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 9a9929424513a..2273e30297766 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie.c +++ b/drivers/net/wireless/marvell/mwifiex/pcie.c @@ -377,6 +377,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", @@ -418,6 +419,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 f46b06f8d6435..99b024ecbadea 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c @@ -14,7 +14,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_DO_FLR_ON_BRIDGE), + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Pro 5", @@ -24,7 +25,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_DO_FLR_ON_BRIDGE), + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Pro 5 (LTE)", @@ -34,7 +36,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_DO_FLR_ON_BRIDGE), + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Pro 6", @@ -43,7 +46,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_DO_FLR_ON_BRIDGE), + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Book 1", @@ -52,7 +56,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_DO_FLR_ON_BRIDGE), + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Book 2", @@ -61,7 +66,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_DO_FLR_ON_BRIDGE), + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Laptop 1", @@ -70,7 +76,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_DO_FLR_ON_BRIDGE), + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, { .ident = "Surface Laptop 2", @@ -79,7 +86,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_DO_FLR_ON_BRIDGE), + QUIRK_DO_FLR_ON_BRIDGE | + QUIRK_NO_BRIDGE_D3), }, {} }; @@ -99,6 +107,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) dev_info(&pdev->dev, "quirk do_flr_on_bridge 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 5d30ae39d65ec..c14eb56eb9118 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h @@ -5,6 +5,7 @@ #define QUIRK_FW_RST_D3COLD BIT(0) #define QUIRK_DO_FLR_ON_BRIDGE 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.45.1 From 8a4ee131ced8068371a8fa09da17d82414e6d835 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 fb716849b60f3..1e7b3798108f7 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -65,6 +65,7 @@ static struct usb_driver btusb_driver; #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) #define BTUSB_ACTIONS_SEMI BIT(27) +#define BTUSB_LOWER_LESCAN_INTERVAL BIT(28) static const struct usb_device_id btusb_table[] = { /* Generic Bluetooth USB device */ @@ -468,6 +469,7 @@ static const struct usb_device_id quirks_table[] = { { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, /* Intel Bluetooth devices */ { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, @@ -4417,6 +4419,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.45.1 From fc56de38d725edc7c3856c2a2d369e1f170f202f 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 fa5e2e6518313..8921b0ebf36b7 100644 --- a/drivers/net/wireless/ath/ath10k/core.c +++ b/drivers/net/wireless/ath/ath10k/core.c @@ -39,6 +39,9 @@ static bool fw_diag_log; /* frame mode values are mapped as per enum ath10k_hw_txrx_mode */ unsigned int ath10k_frame_mode = ATH10K_HW_TXRX_NATIVE_WIFI; +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); @@ -51,6 +54,9 @@ module_param(fw_diag_log, bool, 0644); module_param_named(frame_mode, ath10k_frame_mode, uint, 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"); @@ -60,6 +66,9 @@ MODULE_PARM_DESC(frame_mode, 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, @@ -931,6 +940,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) @@ -945,6 +990,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.45.1 From ee272e8a81f073b5475a3bb2c3085b55f181bc24 Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Thu, 30 Jul 2020 13:21:53 +0200 Subject: [PATCH] mei: me: Add Icelake device ID for iTouch Signed-off-by: Dorian Stoll 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 c3a6657dcd4a2..82eef2f4eb0a8 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -92,6 +92,7 @@ #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ +#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 7f59dd38c32f5..a56ad5b3f7790 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, -- 2.45.1 From 8536601cfc6d7671f4c11cab4dca57674f59b349 Mon Sep 17 00:00:00 2001 From: Liban Hannan Date: Tue, 12 Apr 2022 23:31:12 +0100 Subject: [PATCH] iommu: Use IOMMU passthrough mode for IPTS Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr 0x104ea3000 [fault reason 0x06] PTE Read access is not set This is very similar to the bug described at: https://bugs.launchpad.net/bugs/1958004 Fixed with the following patch which this patch basically copies: https://launchpadlibrarian.net/586396847/43255ca.diff Signed-off-by: Dorian Stoll Patchset: ipts --- drivers/iommu/intel/iommu.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index e4a03588a8a0f..61bc54299a591 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -39,6 +39,11 @@ #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) +#define IS_IPTS(pdev) ( \ + ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x9D3E) || \ + ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ + ) + #define IOAPIC_RANGE_START (0xfee00000) #define IOAPIC_RANGE_END (0xfeefffff) #define IOVA_START_ADDR (0x1000) @@ -221,12 +226,14 @@ int intel_iommu_sm = IS_ENABLED(CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON); int intel_iommu_enabled = 0; EXPORT_SYMBOL_GPL(intel_iommu_enabled); +static int dmar_map_ipts = 1; static int intel_iommu_superpage = 1; static int iommu_identity_mapping; static int iommu_skip_te_disable; static int disable_igfx_iommu; #define IDENTMAP_AZALIA 4 +#define IDENTMAP_IPTS 16 const struct iommu_ops intel_iommu_ops; static const struct iommu_dirty_ops intel_dirty_ops; @@ -2401,6 +2408,9 @@ static int device_def_domain_type(struct device *dev) if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) return IOMMU_DOMAIN_IDENTITY; + + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; } return 0; @@ -2701,6 +2711,9 @@ static int __init init_dmars(void) iommu_set_root_entry(iommu); } + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + check_tylersburg_isoch(); ret = si_domain_init(hw_pass_through); @@ -4871,6 +4884,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) disable_igfx_iommu = 1; } +static void quirk_iommu_ipts(struct pci_dev *dev) +{ + if (!IS_IPTS(dev)) + return; + + if (risky_device(dev)) + return; + + pci_info(dev, "Disabling IOMMU for IPTS\n"); + dmar_map_ipts = 0; +} + /* G4x/GM45 integrated gfx dmar support is totally busted. */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); @@ -4906,6 +4931,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); +/* disable IPTS dmar support */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); + static void quirk_iommu_rwbf(struct pci_dev *dev) { if (risky_device(dev)) -- 2.45.1 From 664128ab9984f6c774d4064548d9b247041d2520 Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Sun, 11 Dec 2022 12:00:59 +0100 Subject: [PATCH] hid: Add support for Intel Precise Touch and Stylus Based on linux-surface/intel-precise-touch@8abe268 Signed-off-by: Dorian Stoll Patchset: ipts --- drivers/hid/Kconfig | 2 + drivers/hid/Makefile | 2 + drivers/hid/ipts/Kconfig | 14 + drivers/hid/ipts/Makefile | 16 ++ drivers/hid/ipts/cmd.c | 61 +++++ drivers/hid/ipts/cmd.h | 60 ++++ drivers/hid/ipts/context.h | 52 ++++ drivers/hid/ipts/control.c | 486 +++++++++++++++++++++++++++++++++ drivers/hid/ipts/control.h | 126 +++++++++ drivers/hid/ipts/desc.h | 80 ++++++ drivers/hid/ipts/eds1.c | 103 +++++++ drivers/hid/ipts/eds1.h | 35 +++ drivers/hid/ipts/eds2.c | 144 ++++++++++ drivers/hid/ipts/eds2.h | 35 +++ drivers/hid/ipts/hid.c | 225 +++++++++++++++ drivers/hid/ipts/hid.h | 24 ++ drivers/hid/ipts/main.c | 126 +++++++++ drivers/hid/ipts/mei.c | 188 +++++++++++++ drivers/hid/ipts/mei.h | 66 +++++ drivers/hid/ipts/receiver.c | 250 +++++++++++++++++ drivers/hid/ipts/receiver.h | 16 ++ drivers/hid/ipts/resources.c | 131 +++++++++ drivers/hid/ipts/resources.h | 41 +++ drivers/hid/ipts/spec-data.h | 100 +++++++ drivers/hid/ipts/spec-device.h | 290 ++++++++++++++++++++ drivers/hid/ipts/spec-hid.h | 34 +++ drivers/hid/ipts/thread.c | 84 ++++++ drivers/hid/ipts/thread.h | 59 ++++ 28 files changed, 2850 insertions(+) create mode 100644 drivers/hid/ipts/Kconfig create mode 100644 drivers/hid/ipts/Makefile create mode 100644 drivers/hid/ipts/cmd.c create mode 100644 drivers/hid/ipts/cmd.h create mode 100644 drivers/hid/ipts/context.h create mode 100644 drivers/hid/ipts/control.c create mode 100644 drivers/hid/ipts/control.h create mode 100644 drivers/hid/ipts/desc.h create mode 100644 drivers/hid/ipts/eds1.c create mode 100644 drivers/hid/ipts/eds1.h create mode 100644 drivers/hid/ipts/eds2.c create mode 100644 drivers/hid/ipts/eds2.h create mode 100644 drivers/hid/ipts/hid.c create mode 100644 drivers/hid/ipts/hid.h create mode 100644 drivers/hid/ipts/main.c create mode 100644 drivers/hid/ipts/mei.c create mode 100644 drivers/hid/ipts/mei.h create mode 100644 drivers/hid/ipts/receiver.c create mode 100644 drivers/hid/ipts/receiver.h create mode 100644 drivers/hid/ipts/resources.c create mode 100644 drivers/hid/ipts/resources.h create mode 100644 drivers/hid/ipts/spec-data.h create mode 100644 drivers/hid/ipts/spec-device.h create mode 100644 drivers/hid/ipts/spec-hid.h create mode 100644 drivers/hid/ipts/thread.c create mode 100644 drivers/hid/ipts/thread.h diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 4c682c6507040..a263e49b2ae29 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1351,4 +1351,6 @@ source "drivers/hid/amd-sfh-hid/Kconfig" source "drivers/hid/surface-hid/Kconfig" +source "drivers/hid/ipts/Kconfig" + endif # HID_SUPPORT diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 082a728eac600..f4bad1b8d813f 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -170,3 +170,5 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + +obj-$(CONFIG_HID_IPTS) += ipts/ diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig new file mode 100644 index 0000000000000..297401bd388dd --- /dev/null +++ b/drivers/hid/ipts/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +config HID_IPTS + tristate "Intel Precise Touch & Stylus" + depends on INTEL_MEI + depends on HID + 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. diff --git a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile new file mode 100644 index 0000000000000..883896f68e6ad --- /dev/null +++ b/drivers/hid/ipts/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Makefile for the IPTS touchscreen driver +# + +obj-$(CONFIG_HID_IPTS) += ipts.o +ipts-objs := cmd.o +ipts-objs += control.o +ipts-objs += eds1.o +ipts-objs += eds2.o +ipts-objs += hid.o +ipts-objs += main.o +ipts-objs += mei.o +ipts-objs += receiver.o +ipts-objs += resources.o +ipts-objs += thread.o diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c new file mode 100644 index 0000000000000..63a4934bbc5fa --- /dev/null +++ b/drivers/hid/ipts/cmd.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include + +#include "cmd.h" +#include "context.h" +#include "mei.h" +#include "spec-device.h" + +int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, + struct ipts_response *rsp, u64 timeout) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (!rsp) + return -EFAULT; + + /* + * In a response, the command code will have the most significant bit flipped to 1. + * If code is passed to ipts_mei_recv as is, no messages will be received. + */ + ret = ipts_mei_recv(&ipts->mei, code | IPTS_RSP_BIT, rsp, timeout); + if (ret < 0) + return ret; + + dev_dbg(ipts->dev, "Received 0x%02X with status 0x%02X\n", code, rsp->status); + + /* + * Some devices will always return this error. + * It is allowed to ignore it and to try continuing. + */ + if (rsp->status == IPTS_STATUS_COMPAT_CHECK_FAIL) + rsp->status = IPTS_STATUS_SUCCESS; + + return 0; +} + +int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size) +{ + struct ipts_command cmd = { 0 }; + + if (!ipts) + return -EFAULT; + + cmd.cmd = code; + + if (data && size > 0) + memcpy(cmd.payload, data, size); + + dev_dbg(ipts->dev, "Sending 0x%02X with %ld bytes payload\n", code, size); + return ipts_mei_send(&ipts->mei, &cmd, sizeof(cmd.cmd) + size); +} diff --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h new file mode 100644 index 0000000000000..2b4079075b642 --- /dev/null +++ b/drivers/hid/ipts/cmd.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_CMD_H +#define IPTS_CMD_H + +#include + +#include "context.h" +#include "spec-device.h" + +/* + * The default timeout for receiving responses + */ +#define IPTS_CMD_DEFAULT_TIMEOUT 1000 + +/** + * ipts_cmd_recv_timeout() - Receives a response to a command. + * @ipts: The IPTS driver context. + * @code: The type of the command / response. + * @rsp: The address that the received response will be copied to. + * @timeout: How many milliseconds the function will wait at most. + * + * A negative timeout means to wait forever. + * + * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. + */ +int ipts_cmd_recv_timeout(struct ipts_context *ipts, enum ipts_command_code code, + struct ipts_response *rsp, u64 timeout); + +/** + * ipts_cmd_recv() - Receives a response to a command. + * @ipts: The IPTS driver context. + * @code: The type of the command / response. + * @rsp: The address that the received response will be copied to. + * + * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. + */ +static inline int ipts_cmd_recv(struct ipts_context *ipts, enum ipts_command_code code, + struct ipts_response *rsp) +{ + return ipts_cmd_recv_timeout(ipts, code, rsp, IPTS_CMD_DEFAULT_TIMEOUT); +} + +/** + * ipts_cmd_send() - Executes a command on the device. + * @ipts: The IPTS driver context. + * @code: The type of the command to execute. + * @data: The payload containing parameters for the command. + * @size: The size of the payload. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); + +#endif /* IPTS_CMD_H */ diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h new file mode 100644 index 0000000000000..ba33259f1f7c5 --- /dev/null +++ b/drivers/hid/ipts/context.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_CONTEXT_H +#define IPTS_CONTEXT_H + +#include +#include +#include +#include +#include +#include +#include + +#include "mei.h" +#include "resources.h" +#include "spec-device.h" +#include "thread.h" + +struct ipts_context { + struct device *dev; + struct ipts_mei mei; + + enum ipts_mode mode; + + /* + * Prevents concurrent GET_FEATURE reports. + */ + struct mutex feature_lock; + struct completion feature_event; + + /* + * These are not inside of struct ipts_resources + * because they don't own the memory they point to. + */ + struct ipts_buffer feature_report; + struct ipts_buffer descriptor; + + bool hid_active; + struct hid_device *hid; + + struct ipts_device_info info; + struct ipts_resources resources; + + struct ipts_thread receiver_loop; +}; + +#endif /* IPTS_CONTEXT_H */ diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c new file mode 100644 index 0000000000000..5360842d260ba --- /dev/null +++ b/drivers/hid/ipts/control.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include + +#include "cmd.h" +#include "context.h" +#include "control.h" +#include "desc.h" +#include "hid.h" +#include "receiver.h" +#include "resources.h" +#include "spec-data.h" +#include "spec-device.h" + +static int ipts_control_get_device_info(struct ipts_context *ipts, struct ipts_device_info *info) +{ + int ret = 0; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + if (!info) + return -EFAULT; + + ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); + if (ret) { + dev_err(ipts->dev, "GET_DEVICE_INFO: send failed: %d\n", ret); + return ret; + } + + ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DEVICE_INFO, &rsp); + if (ret) { + dev_err(ipts->dev, "GET_DEVICE_INFO: recv failed: %d\n", ret); + return ret; + } + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "GET_DEVICE_INFO: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + memcpy(info, rsp.payload, sizeof(*info)); + return 0; +} + +static int ipts_control_set_mode(struct ipts_context *ipts, enum ipts_mode mode) +{ + int ret = 0; + struct ipts_set_mode cmd = { 0 }; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + cmd.mode = mode; + + ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MODE, &cmd, sizeof(cmd)); + if (ret) { + dev_err(ipts->dev, "SET_MODE: send failed: %d\n", ret); + return ret; + } + + ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MODE, &rsp); + if (ret) { + dev_err(ipts->dev, "SET_MODE: recv failed: %d\n", ret); + return ret; + } + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "SET_MODE: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + return 0; +} + +static int ipts_control_set_mem_window(struct ipts_context *ipts, struct ipts_resources *res) +{ + int i = 0; + int ret = 0; + struct ipts_mem_window cmd = { 0 }; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + if (!res) + return -EFAULT; + + for (i = 0; i < IPTS_BUFFERS; i++) { + cmd.data_addr_lower[i] = lower_32_bits(res->data[i].dma_address); + cmd.data_addr_upper[i] = upper_32_bits(res->data[i].dma_address); + cmd.feedback_addr_lower[i] = lower_32_bits(res->feedback[i].dma_address); + cmd.feedback_addr_upper[i] = upper_32_bits(res->feedback[i].dma_address); + } + + cmd.workqueue_addr_lower = lower_32_bits(res->workqueue.dma_address); + cmd.workqueue_addr_upper = upper_32_bits(res->workqueue.dma_address); + + cmd.doorbell_addr_lower = lower_32_bits(res->doorbell.dma_address); + cmd.doorbell_addr_upper = upper_32_bits(res->doorbell.dma_address); + + cmd.hid2me_addr_lower = lower_32_bits(res->hid2me.dma_address); + cmd.hid2me_addr_upper = upper_32_bits(res->hid2me.dma_address); + + cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; + cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; + + ret = ipts_cmd_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, sizeof(cmd)); + if (ret) { + dev_err(ipts->dev, "SET_MEM_WINDOW: send failed: %d\n", ret); + return ret; + } + + ret = ipts_cmd_recv(ipts, IPTS_CMD_SET_MEM_WINDOW, &rsp); + if (ret) { + dev_err(ipts->dev, "SET_MEM_WINDOW: recv failed: %d\n", ret); + return ret; + } + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "SET_MEM_WINDOW: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + return 0; +} + +static int ipts_control_get_descriptor(struct ipts_context *ipts) +{ + int ret = 0; + struct ipts_data_header *header = NULL; + struct ipts_get_descriptor cmd = { 0 }; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + if (!ipts->resources.descriptor.address) + return -EFAULT; + + memset(ipts->resources.descriptor.address, 0, ipts->resources.descriptor.size); + + cmd.addr_lower = lower_32_bits(ipts->resources.descriptor.dma_address); + cmd.addr_upper = upper_32_bits(ipts->resources.descriptor.dma_address); + cmd.magic = 8; + + ret = ipts_cmd_send(ipts, IPTS_CMD_GET_DESCRIPTOR, &cmd, sizeof(cmd)); + if (ret) { + dev_err(ipts->dev, "GET_DESCRIPTOR: send failed: %d\n", ret); + return ret; + } + + ret = ipts_cmd_recv(ipts, IPTS_CMD_GET_DESCRIPTOR, &rsp); + if (ret) { + dev_err(ipts->dev, "GET_DESCRIPTOR: recv failed: %d\n", ret); + return ret; + } + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "GET_DESCRIPTOR: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + header = (struct ipts_data_header *)ipts->resources.descriptor.address; + + if (header->type == IPTS_DATA_TYPE_DESCRIPTOR) { + ipts->descriptor.address = &header->data[8]; + ipts->descriptor.size = header->size - 8; + + return 0; + } + + return -ENODATA; +} + +int ipts_control_request_flush(struct ipts_context *ipts) +{ + int ret = 0; + struct ipts_quiesce_io cmd = { 0 }; + + if (!ipts) + return -EFAULT; + + ret = ipts_cmd_send(ipts, IPTS_CMD_QUIESCE_IO, &cmd, sizeof(cmd)); + if (ret) + dev_err(ipts->dev, "QUIESCE_IO: send failed: %d\n", ret); + + return ret; +} + +int ipts_control_wait_flush(struct ipts_context *ipts) +{ + int ret = 0; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + ret = ipts_cmd_recv(ipts, IPTS_CMD_QUIESCE_IO, &rsp); + if (ret) { + dev_err(ipts->dev, "QUIESCE_IO: recv failed: %d\n", ret); + return ret; + } + + if (rsp.status == IPTS_STATUS_TIMEOUT) + return -EAGAIN; + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "QUIESCE_IO: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + return 0; +} + +int ipts_control_request_data(struct ipts_context *ipts) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + ret = ipts_cmd_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); + if (ret) + dev_err(ipts->dev, "READY_FOR_DATA: send failed: %d\n", ret); + + return ret; +} + +int ipts_control_wait_data(struct ipts_context *ipts, bool shutdown) +{ + int ret = 0; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + if (!shutdown) + ret = ipts_cmd_recv_timeout(ipts, IPTS_CMD_READY_FOR_DATA, &rsp, 0); + else + ret = ipts_cmd_recv(ipts, IPTS_CMD_READY_FOR_DATA, &rsp); + + if (ret) { + if (ret != -EAGAIN) + dev_err(ipts->dev, "READY_FOR_DATA: recv failed: %d\n", ret); + + return ret; + } + + /* + * During shutdown, it is possible that the sensor has already been disabled. + */ + if (rsp.status == IPTS_STATUS_SENSOR_DISABLED) + return 0; + + if (rsp.status == IPTS_STATUS_TIMEOUT) + return -EAGAIN; + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "READY_FOR_DATA: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + return 0; +} + +int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) +{ + int ret = 0; + struct ipts_feedback cmd = { 0 }; + struct ipts_response rsp = { 0 }; + + if (!ipts) + return -EFAULT; + + cmd.buffer = buffer; + + ret = ipts_cmd_send(ipts, IPTS_CMD_FEEDBACK, &cmd, sizeof(cmd)); + if (ret) { + dev_err(ipts->dev, "FEEDBACK: send failed: %d\n", ret); + return ret; + } + + ret = ipts_cmd_recv(ipts, IPTS_CMD_FEEDBACK, &rsp); + if (ret) { + dev_err(ipts->dev, "FEEDBACK: recv failed: %d\n", ret); + return ret; + } + + /* + * We don't know what feedback data looks like so we are sending zeros. + * See also ipts_control_refill_buffer. + */ + if (rsp.status == IPTS_STATUS_INVALID_PARAMS) + return 0; + + if (rsp.status != IPTS_STATUS_SUCCESS) { + dev_err(ipts->dev, "FEEDBACK: cmd failed: %d\n", rsp.status); + return -EBADR; + } + + return 0; +} + +int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, + enum ipts_feedback_data_type type, void *data, size_t size) +{ + struct ipts_feedback_header *header = NULL; + + if (!ipts) + return -EFAULT; + + if (!ipts->resources.hid2me.address) + return -EFAULT; + + memset(ipts->resources.hid2me.address, 0, ipts->resources.hid2me.size); + header = (struct ipts_feedback_header *)ipts->resources.hid2me.address; + + header->cmd_type = cmd; + header->data_type = type; + header->size = size; + header->buffer = IPTS_HID2ME_BUFFER; + + if (size + sizeof(*header) > ipts->resources.hid2me.size) + return -EINVAL; + + if (data && size > 0) + memcpy(header->payload, data, size); + + return ipts_control_send_feedback(ipts, IPTS_HID2ME_BUFFER); +} + +int ipts_control_start(struct ipts_context *ipts) +{ + int ret = 0; + struct ipts_device_info info = { 0 }; + + if (!ipts) + return -EFAULT; + + dev_info(ipts->dev, "Starting IPTS\n"); + + ret = ipts_control_get_device_info(ipts, &info); + if (ret) { + dev_err(ipts->dev, "Failed to get device info: %d\n", ret); + return ret; + } + + ipts->info = info; + + ret = ipts_resources_init(&ipts->resources, ipts->dev, info.data_size, info.feedback_size); + if (ret) { + dev_err(ipts->dev, "Failed to allocate buffers: %d", ret); + return ret; + } + + dev_info(ipts->dev, "IPTS EDS Version: %d\n", info.intf_eds); + + /* + * Handle newer devices + */ + if (info.intf_eds > 1) { + /* + * Fetching the descriptor will only work on newer devices. + * For older devices, a fallback descriptor will be used. + */ + ret = ipts_control_get_descriptor(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to fetch HID descriptor: %d\n", ret); + return ret; + } + + /* + * Newer devices can be directly initialized in polling mode. + */ + ipts->mode = IPTS_MODE_POLL; + } + + ret = ipts_control_set_mode(ipts, ipts->mode); + if (ret) { + dev_err(ipts->dev, "Failed to set mode: %d\n", ret); + return ret; + } + + ret = ipts_control_set_mem_window(ipts, &ipts->resources); + if (ret) { + dev_err(ipts->dev, "Failed to set memory window: %d\n", ret); + return ret; + } + + ret = ipts_receiver_start(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to start receiver: %d\n", ret); + return ret; + } + + ret = ipts_control_request_data(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to request data: %d\n", ret); + return ret; + } + + ipts_hid_enable(ipts); + + ret = ipts_hid_init(ipts, info); + if (ret) { + dev_err(ipts->dev, "Failed to initialize HID device: %d\n", ret); + return ret; + } + + return 0; +} + +static int _ipts_control_stop(struct ipts_context *ipts) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + ipts_hid_disable(ipts); + dev_info(ipts->dev, "Stopping IPTS\n"); + + ret = ipts_receiver_stop(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to stop receiver: %d\n", ret); + return ret; + } + + ret = ipts_resources_free(&ipts->resources); + if (ret) { + dev_err(ipts->dev, "Failed to free resources: %d\n", ret); + return ret; + } + + return 0; +} + +int ipts_control_stop(struct ipts_context *ipts) +{ + int ret = 0; + + ret = _ipts_control_stop(ipts); + if (ret) + return ret; + + ret = ipts_hid_free(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to free HID device: %d\n", ret); + return ret; + } + + return 0; +} + +int ipts_control_restart(struct ipts_context *ipts) +{ + int ret = 0; + + ret = _ipts_control_stop(ipts); + if (ret) + return ret; + + /* + * Wait a second to give the sensor time to fully shut down. + */ + msleep(1000); + + ret = ipts_control_start(ipts); + if (ret) + return ret; + + return 0; +} diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h new file mode 100644 index 0000000000000..26629c5144edb --- /dev/null +++ b/drivers/hid/ipts/control.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_CONTROL_H +#define IPTS_CONTROL_H + +#include + +#include "context.h" +#include "spec-data.h" +#include "spec-device.h" + +/** + * ipts_control_request_flush() - Stop the data flow. + * @ipts: The IPTS driver context. + * + * Runs the command to stop the data flow on the device. + * All outstanding data needs to be acknowledged using feedback before the command will return. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_request_flush(struct ipts_context *ipts); + +/** + * ipts_control_wait_flush() - Wait until data flow has been stopped. + * @ipts: The IPTS driver context. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_wait_flush(struct ipts_context *ipts); + +/** + * ipts_control_wait_flush() - Notify the device that the driver can receive new data. + * @ipts: The IPTS driver context. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_request_data(struct ipts_context *ipts); + +/** + * ipts_control_wait_data() - Wait until new data is available. + * @ipts: The IPTS driver context. + * @block: Whether to block execution until data is available. + * + * In poll mode, this function will never return while the data flow is active. Instead, + * the poll will be incremented when new data is available. + * + * Returns: 0 on success, <0 on error, -EAGAIN if no data is available. + */ +int ipts_control_wait_data(struct ipts_context *ipts, bool block); + +/** + * ipts_control_send_feedback() - Submits a feedback buffer to the device. + * @ipts: The IPTS driver context. + * @buffer: The ID of the buffer containing feedback data. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); + +/** + * ipts_control_hid2me_feedback() - Sends HID2ME feedback, a special type of feedback. + * @ipts: The IPTS driver context. + * @cmd: The command that will be run on the device. + * @type: The type of the payload that is sent to the device. + * @data: The payload of the feedback command. + * @size: The size of the payload. + * + * HID2ME feedback is a special type of feedback, because it allows interfacing with + * the HID API of the device at any moment, without requiring a buffer that has to + * be acknowledged. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_hid2me_feedback(struct ipts_context *ipts, enum ipts_feedback_cmd_type cmd, + enum ipts_feedback_data_type type, void *data, size_t size); + +/** + * ipts_control_refill_buffer() - Acknowledges that data in a buffer has been processed. + * @ipts: The IPTS driver context. + * @buffer: The buffer that has been processed and can be refilled. + * + * Returns: 0 on success, <0 on error. + */ +static inline int ipts_control_refill_buffer(struct ipts_context *ipts, u32 buffer) +{ + /* + * IPTS expects structured data in the feedback buffer matching the buffer that will be + * refilled. We don't know what that data looks like, so we just keep the buffer empty. + * This results in an INVALID_PARAMS error, but the buffer gets refilled without an issue. + * Sending a minimal structure with the buffer ID fixes the error, but breaks refilling + * the buffers on some devices. + */ + + return ipts_control_send_feedback(ipts, buffer); +} + +/** + * ipts_control_start() - Initialized the device and starts the data flow. + * @ipts: The IPTS driver context. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_start(struct ipts_context *ipts); + +/** + * ipts_control_stop() - Stops the data flow and resets the device. + * @ipts: The IPTS driver context. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_stop(struct ipts_context *ipts); + +/** + * ipts_control_restart() - Stops the device and starts it again. + * @ipts: The IPTS driver context. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_control_restart(struct ipts_context *ipts); + +#endif /* IPTS_CONTROL_H */ diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h new file mode 100644 index 0000000000000..307438c7c80cd --- /dev/null +++ b/drivers/hid/ipts/desc.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2022-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_DESC_H +#define IPTS_DESC_H + +#include + +#define IPTS_HID_REPORT_SINGLETOUCH 64 +#define IPTS_HID_REPORT_DATA 65 +#define IPTS_HID_REPORT_SET_MODE 66 + +#define IPTS_HID_REPORT_DATA_SIZE 7485 + +/* + * HID descriptor for singletouch data. + * This descriptor should be present on all IPTS devices. + */ +static const u8 ipts_singletouch_descriptor[] = { + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x04, /* Usage (Touchscreen), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x40, /* Report ID (64), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x15, 0x00, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x07, /* Report Count (7), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x30, /* Usage (X), */ + 0x75, 0x10, /* Report Size (16), */ + 0x95, 0x01, /* Report Count (1), */ + 0xA4, /* Push, */ + 0x55, 0x0E, /* Unit Exponent (14), */ + 0x65, 0x11, /* Unit (Centimeter), */ + 0x46, 0x76, 0x0B, /* Physical Maximum (2934), */ + 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x31, /* Usage (Y), */ + 0x46, 0x74, 0x06, /* Physical Maximum (1652), */ + 0x26, 0xFF, 0x7F, /* Logical Maximum (32767), */ + 0x81, 0x02, /* Input (Variable), */ + 0xB4, /* Pop, */ + 0xC0, /* End Collection */ +}; + +/* + * Fallback HID descriptor for older devices that do not have + * the ability to query their HID descriptor. + */ +static const u8 ipts_fallback_descriptor[] = { + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x0F, /* Usage (Capacitive Hm Digitizer), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x41, /* Report ID (65), */ + 0x09, 0x56, /* Usage (Scan Time), */ + 0x95, 0x01, /* Report Count (1), */ + 0x75, 0x10, /* Report Size (16), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x61, /* Usage (Gesture Char Quality), */ + 0x75, 0x08, /* Report Size (8), */ + 0x96, 0x3D, 0x1D, /* Report Count (7485), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x85, 0x42, /* Report ID (66), */ + 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ + 0x09, 0xC8, /* Usage (C8h), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x01, /* Report Count (1), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0xC0, /* End Collection, */ +}; + +#endif /* IPTS_DESC_H */ diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c new file mode 100644 index 0000000000000..ecbb3a8bdaf60 --- /dev/null +++ b/drivers/hid/ipts/eds1.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "desc.h" +#include "spec-device.h" + +int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) +{ + size_t size = 0; + u8 *buffer = NULL; + + if (!ipts) + return -EFAULT; + + if (!desc_buffer) + return -EFAULT; + + if (!desc_size) + return -EFAULT; + + size = sizeof(ipts_singletouch_descriptor) + sizeof(ipts_fallback_descriptor); + + buffer = kzalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); + memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts_fallback_descriptor, + sizeof(ipts_fallback_descriptor)); + + *desc_size = size; + *desc_buffer = buffer; + + return 0; +} + +static int ipts_eds1_switch_mode(struct ipts_context *ipts, enum ipts_mode mode) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (ipts->mode == mode) + return 0; + + ipts->mode = mode; + + ret = ipts_control_restart(ipts); + if (ret) + dev_err(ipts->dev, "Failed to switch modes: %d\n", ret); + + return ret; +} + +int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum hid_report_type report_type, enum hid_class_request request_type) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (!buffer) + return -EFAULT; + + if (report_id != IPTS_HID_REPORT_SET_MODE) + return -EIO; + + if (report_type != HID_FEATURE_REPORT) + return -EIO; + + if (size != 2) + return -EINVAL; + + /* + * Implement mode switching report for older devices without native HID support. + */ + + if (request_type == HID_REQ_GET_REPORT) { + memset(buffer, 0, size); + buffer[0] = report_id; + buffer[1] = ipts->mode; + } else if (request_type == HID_REQ_SET_REPORT) { + return ipts_eds1_switch_mode(ipts, buffer[1]); + } else { + return -EIO; + } + + return ret; +} diff --git a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h new file mode 100644 index 0000000000000..eeeb6575e3e89 --- /dev/null +++ b/drivers/hid/ipts/eds1.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include + +#include "context.h" + +/** + * ipts_eds1_get_descriptor() - Assembles the HID descriptor of the device. + * @ipts: The IPTS driver context. + * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. + * @desc_size: A pointer to the location where the size of the allocated buffer is stored. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); + +/** + * ipts_eds1_raw_request() - Executes an output or feature report on the device. + * @ipts: The IPTS driver context. + * @buffer: The buffer containing the report. + * @size: The size of the buffer. + * @report_id: The HID report ID. + * @report_type: Whether this report is an output or a feature report. + * @request_type: Whether this report requests or sends data. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum hid_report_type report_type, enum hid_class_request request_type); diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c new file mode 100644 index 0000000000000..198dc65d78876 --- /dev/null +++ b/drivers/hid/ipts/eds2.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "desc.h" +#include "spec-data.h" + +int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) +{ + size_t size = 0; + u8 *buffer = NULL; + + if (!ipts) + return -EFAULT; + + if (!desc_buffer) + return -EFAULT; + + if (!desc_size) + return -EFAULT; + + size = sizeof(ipts_singletouch_descriptor) + ipts->descriptor.size; + + buffer = kzalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + memcpy(buffer, ipts_singletouch_descriptor, sizeof(ipts_singletouch_descriptor)); + memcpy(&buffer[sizeof(ipts_singletouch_descriptor)], ipts->descriptor.address, + ipts->descriptor.size); + + *desc_size = size; + *desc_buffer = buffer; + + return 0; +} + +static int ipts_eds2_get_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum ipts_feedback_data_type type) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (!buffer) + return -EFAULT; + + mutex_lock(&ipts->feature_lock); + + memset(buffer, 0, size); + buffer[0] = report_id; + + memset(&ipts->feature_report, 0, sizeof(ipts->feature_report)); + reinit_completion(&ipts->feature_event); + + ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); + if (ret) { + dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); + goto out; + } + + ret = wait_for_completion_timeout(&ipts->feature_event, msecs_to_jiffies(5000)); + if (ret == 0) { + dev_warn(ipts->dev, "GET_FEATURES timed out!\n"); + ret = -EIO; + goto out; + } + + if (!ipts->feature_report.address) { + ret = -EFAULT; + goto out; + } + + if (ipts->feature_report.size > size) { + ret = -ETOOSMALL; + goto out; + } + + ret = ipts->feature_report.size; + memcpy(buffer, ipts->feature_report.address, ipts->feature_report.size); + +out: + mutex_unlock(&ipts->feature_lock); + return ret; +} + +static int ipts_eds2_set_feature(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum ipts_feedback_data_type type) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (!buffer) + return -EFAULT; + + buffer[0] = report_id; + + ret = ipts_control_hid2me_feedback(ipts, IPTS_FEEDBACK_CMD_TYPE_NONE, type, buffer, size); + if (ret) + dev_err(ipts->dev, "Failed to send hid2me feedback: %d\n", ret); + + return ret; +} + +int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum hid_report_type report_type, enum hid_class_request request_type) +{ + enum ipts_feedback_data_type feedback_type = IPTS_FEEDBACK_DATA_TYPE_VENDOR; + + if (!ipts) + return -EFAULT; + + if (!buffer) + return -EFAULT; + + if (report_type == HID_OUTPUT_REPORT && request_type == HID_REQ_SET_REPORT) + feedback_type = IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; + else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_GET_REPORT) + feedback_type = IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES; + else if (report_type == HID_FEATURE_REPORT && request_type == HID_REQ_SET_REPORT) + feedback_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; + else + return -EIO; + + if (request_type == HID_REQ_GET_REPORT) + return ipts_eds2_get_feature(ipts, buffer, size, report_id, feedback_type); + else + return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); +} diff --git a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h new file mode 100644 index 0000000000000..064e3716907ab --- /dev/null +++ b/drivers/hid/ipts/eds2.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include + +#include "context.h" + +/** + * ipts_eds2_get_descriptor() - Assembles the HID descriptor of the device. + * @ipts: The IPTS driver context. + * @desc_buffer: A pointer to the location where the address of the allocated buffer is stored. + * @desc_size: A pointer to the location where the size of the allocated buffer is stored. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size); + +/** + * ipts_eds2_raw_request() - Executes an output or feature report on the device. + * @ipts: The IPTS driver context. + * @buffer: The buffer containing the report. + * @size: The size of the buffer. + * @report_id: The HID report ID. + * @report_type: Whether this report is an output or a feature report. + * @request_type: Whether this report requests or sends data. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum hid_report_type report_type, enum hid_class_request request_type); diff --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c new file mode 100644 index 0000000000000..e34a1a4f9fa77 --- /dev/null +++ b/drivers/hid/ipts/hid.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "context.h" +#include "desc.h" +#include "eds1.h" +#include "eds2.h" +#include "hid.h" +#include "spec-data.h" +#include "spec-hid.h" + +void ipts_hid_enable(struct ipts_context *ipts) +{ + WRITE_ONCE(ipts->hid_active, true); +} + +void ipts_hid_disable(struct ipts_context *ipts) +{ + WRITE_ONCE(ipts->hid_active, false); +} + +static int ipts_hid_start(struct hid_device *hid) +{ + return 0; +} + +static void ipts_hid_stop(struct hid_device *hid) +{ +} + +static int ipts_hid_parse(struct hid_device *hid) +{ + int ret = 0; + struct ipts_context *ipts = NULL; + + u8 *buffer = NULL; + size_t size = 0; + + if (!hid) + return -ENODEV; + + ipts = hid->driver_data; + + if (!ipts) + return -EFAULT; + + if (!READ_ONCE(ipts->hid_active)) + return -ENODEV; + + if (ipts->info.intf_eds == 1) + ret = ipts_eds1_get_descriptor(ipts, &buffer, &size); + else + ret = ipts_eds2_get_descriptor(ipts, &buffer, &size); + + if (ret) { + dev_err(ipts->dev, "Failed to allocate HID descriptor: %d\n", ret); + return ret; + } + + ret = hid_parse_report(hid, buffer, size); + kfree(buffer); + + if (ret) { + dev_err(ipts->dev, "Failed to parse HID descriptor: %d\n", ret); + return ret; + } + + return 0; +} + +static int ipts_hid_raw_request(struct hid_device *hid, unsigned char report_id, __u8 *buffer, + size_t size, unsigned char report_type, int request_type) +{ + struct ipts_context *ipts = NULL; + + if (!hid) + return -ENODEV; + + ipts = hid->driver_data; + + if (!ipts) + return -EFAULT; + + if (!READ_ONCE(ipts->hid_active)) + return -ENODEV; + + if (ipts->info.intf_eds == 1) { + return ipts_eds1_raw_request(ipts, buffer, size, report_id, report_type, + request_type); + } else { + return ipts_eds2_raw_request(ipts, buffer, size, report_id, report_type, + request_type); + } +} + +static struct hid_ll_driver ipts_hid_driver = { + .start = ipts_hid_start, + .stop = ipts_hid_stop, + .open = ipts_hid_start, + .close = ipts_hid_stop, + .parse = ipts_hid_parse, + .raw_request = ipts_hid_raw_request, +}; + +int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer) +{ + u8 *temp = NULL; + struct ipts_hid_header *frame = NULL; + struct ipts_data_header *header = NULL; + + if (!ipts) + return -EFAULT; + + if (!ipts->hid) + return -ENODEV; + + if (!READ_ONCE(ipts->hid_active)) + return -ENODEV; + + header = (struct ipts_data_header *)ipts->resources.data[buffer].address; + + temp = ipts->resources.report.address; + memset(temp, 0, ipts->resources.report.size); + + if (!header) + return -EFAULT; + + if (header->size == 0) + return 0; + + if (header->type == IPTS_DATA_TYPE_HID) + return hid_input_report(ipts->hid, HID_INPUT_REPORT, header->data, header->size, 1); + + if (header->type == IPTS_DATA_TYPE_GET_FEATURES) { + ipts->feature_report.address = header->data; + ipts->feature_report.size = header->size; + + complete_all(&ipts->feature_event); + return 0; + } + + if (header->type != IPTS_DATA_TYPE_FRAME) + return 0; + + if (header->size + 3 + sizeof(struct ipts_hid_header) > IPTS_HID_REPORT_DATA_SIZE) + return -ERANGE; + + /* + * Synthesize a HID report matching the devices that natively send HID reports + */ + temp[0] = IPTS_HID_REPORT_DATA; + + frame = (struct ipts_hid_header *)&temp[3]; + frame->type = IPTS_HID_FRAME_TYPE_RAW; + frame->size = header->size + sizeof(*frame); + + memcpy(frame->data, header->data, header->size); + + return hid_input_report(ipts->hid, HID_INPUT_REPORT, temp, IPTS_HID_REPORT_DATA_SIZE, 1); +} + +int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (ipts->hid) + return 0; + + ipts->hid = hid_allocate_device(); + if (IS_ERR(ipts->hid)) { + int err = PTR_ERR(ipts->hid); + + dev_err(ipts->dev, "Failed to allocate HID device: %d\n", err); + return err; + } + + ipts->hid->driver_data = ipts; + ipts->hid->dev.parent = ipts->dev; + ipts->hid->ll_driver = &ipts_hid_driver; + + ipts->hid->vendor = info.vendor; + ipts->hid->product = info.product; + ipts->hid->group = HID_GROUP_GENERIC; + + snprintf(ipts->hid->name, sizeof(ipts->hid->name), "IPTS %04X:%04X", info.vendor, + info.product); + + ret = hid_add_device(ipts->hid); + if (ret) { + dev_err(ipts->dev, "Failed to add HID device: %d\n", ret); + ipts_hid_free(ipts); + return ret; + } + + return 0; +} + +int ipts_hid_free(struct ipts_context *ipts) +{ + if (!ipts) + return -EFAULT; + + if (!ipts->hid) + return 0; + + hid_destroy_device(ipts->hid); + ipts->hid = NULL; + + return 0; +} diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h new file mode 100644 index 0000000000000..1ebe77447903a --- /dev/null +++ b/drivers/hid/ipts/hid.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2022-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_HID_H +#define IPTS_HID_H + +#include + +#include "context.h" +#include "spec-device.h" + +void ipts_hid_enable(struct ipts_context *ipts); +void ipts_hid_disable(struct ipts_context *ipts); + +int ipts_hid_input_data(struct ipts_context *ipts, u32 buffer); + +int ipts_hid_init(struct ipts_context *ipts, struct ipts_device_info info); +int ipts_hid_free(struct ipts_context *ipts); + +#endif /* IPTS_HID_H */ diff --git a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c new file mode 100644 index 0000000000000..fb5b5c13ee3ea --- /dev/null +++ b/drivers/hid/ipts/main.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "mei.h" +#include "receiver.h" +#include "spec-device.h" + +/* + * The MEI client ID for IPTS functionality. + */ +#define IPTS_ID UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) + +static int ipts_set_dma_mask(struct mei_cl_device *cldev) +{ + if (!cldev) + return -EFAULT; + + if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) + return 0; + + return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); +} + +static int ipts_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) +{ + int ret = 0; + struct ipts_context *ipts = NULL; + + if (!cldev) + return -EFAULT; + + ret = ipts_set_dma_mask(cldev); + if (ret) { + dev_err(&cldev->dev, "Failed to set DMA mask for IPTS: %d\n", ret); + return ret; + } + + ret = mei_cldev_enable(cldev); + if (ret) { + dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); + return ret; + } + + ipts = devm_kzalloc(&cldev->dev, sizeof(*ipts), GFP_KERNEL); + if (!ipts) { + mei_cldev_disable(cldev); + return -ENOMEM; + } + + ret = ipts_mei_init(&ipts->mei, cldev); + if (ret) { + dev_err(&cldev->dev, "Failed to init MEI bus logic: %d\n", ret); + return ret; + } + + ipts->dev = &cldev->dev; + ipts->mode = IPTS_MODE_EVENT; + + mutex_init(&ipts->feature_lock); + init_completion(&ipts->feature_event); + + mei_cldev_set_drvdata(cldev, ipts); + + ret = ipts_control_start(ipts); + if (ret) { + dev_err(&cldev->dev, "Failed to start IPTS: %d\n", ret); + return ret; + } + + return 0; +} + +static void ipts_remove(struct mei_cl_device *cldev) +{ + int ret = 0; + struct ipts_context *ipts = NULL; + + if (!cldev) { + pr_err("MEI device is NULL!"); + return; + } + + ipts = mei_cldev_get_drvdata(cldev); + + ret = ipts_control_stop(ipts); + if (ret) + dev_err(&cldev->dev, "Failed to stop IPTS: %d\n", ret); + + mei_cldev_disable(cldev); +} + +static struct mei_cl_device_id ipts_device_id_table[] = { + { .uuid = IPTS_ID, .version = MEI_CL_VERSION_ANY }, + {}, +}; +MODULE_DEVICE_TABLE(mei, ipts_device_id_table); + +static struct mei_cl_driver ipts_driver = { + .id_table = ipts_device_id_table, + .name = "ipts", + .probe = ipts_probe, + .remove = ipts_remove, +}; +module_mei_cl_driver(ipts_driver); + +MODULE_DESCRIPTION("IPTS touchscreen driver"); +MODULE_AUTHOR("Dorian Stoll "); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c new file mode 100644 index 0000000000000..1e0395ceae4a4 --- /dev/null +++ b/drivers/hid/ipts/mei.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "context.h" +#include "mei.h" + +static void locked_list_add(struct list_head *new, struct list_head *head, + struct rw_semaphore *lock) +{ + down_write(lock); + list_add(new, head); + up_write(lock); +} + +static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) +{ + down_write(lock); + list_del(entry); + up_write(lock); +} + +static void ipts_mei_incoming(struct mei_cl_device *cldev) +{ + ssize_t ret = 0; + struct ipts_mei_message *entry = NULL; + struct ipts_context *ipts = NULL; + + if (!cldev) { + pr_err("MEI device is NULL!"); + return; + } + + ipts = mei_cldev_get_drvdata(cldev); + if (!ipts) { + pr_err("IPTS driver context is NULL!"); + return; + } + + entry = devm_kzalloc(ipts->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return; + + INIT_LIST_HEAD(&entry->list); + + do { + ret = mei_cldev_recv(cldev, (u8 *)&entry->rsp, sizeof(entry->rsp)); + } while (ret == -EINTR); + + if (ret < 0) { + dev_err(ipts->dev, "Error while reading response: %ld\n", ret); + return; + } + + if (ret == 0) { + dev_err(ipts->dev, "Received empty response\n"); + return; + } + + locked_list_add(&entry->list, &ipts->mei.messages, &ipts->mei.message_lock); + wake_up_all(&ipts->mei.message_queue); +} + +static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, + struct ipts_response *rsp) +{ + struct ipts_mei_message *entry = NULL; + + if (!mei) + return -EFAULT; + + if (!rsp) + return -EFAULT; + + down_read(&mei->message_lock); + + /* + * Iterate over the list of received messages, and check if there is one + * matching the requested command code. + */ + list_for_each_entry(entry, &mei->messages, list) { + if (entry->rsp.cmd == code) + break; + } + + up_read(&mei->message_lock); + + /* + * If entry is not the list head, this means that the loop above has been stopped early, + * and that we found a matching element. We drop the message from the list and return it. + */ + if (!list_entry_is_head(entry, &mei->messages, list)) { + locked_list_del(&entry->list, &mei->message_lock); + + *rsp = entry->rsp; + devm_kfree(&mei->cldev->dev, entry); + + return 0; + } + + return -EAGAIN; +} + +int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, + u64 timeout) +{ + int ret = 0; + + if (!mei) + return -EFAULT; + + /* + * A timeout of 0 means check and return immideately. + */ + if (timeout == 0) + return ipts_mei_search(mei, code, rsp); + + /* + * A timeout of less than 0 means to wait forever. + */ + if (timeout < 0) { + wait_event(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0); + return 0; + } + + ret = wait_event_timeout(mei->message_queue, ipts_mei_search(mei, code, rsp) == 0, + msecs_to_jiffies(timeout)); + + if (ret > 0) + return 0; + + return -EAGAIN; +} + +int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length) +{ + int ret = 0; + + if (!mei) + return -EFAULT; + + if (!mei->cldev) + return -EFAULT; + + if (!data) + return -EFAULT; + + do { + ret = mei_cldev_send(mei->cldev, (u8 *)data, length); + } while (ret == -EINTR); + + if (ret < 0) + return ret; + + return 0; +} + +int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev) +{ + if (!mei) + return -EFAULT; + + if (!cldev) + return -EFAULT; + + mei->cldev = cldev; + + INIT_LIST_HEAD(&mei->messages); + init_waitqueue_head(&mei->message_queue); + init_rwsem(&mei->message_lock); + + mei_cldev_register_rx_cb(cldev, ipts_mei_incoming); + + return 0; +} diff --git a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h new file mode 100644 index 0000000000000..973bade6b0fdd --- /dev/null +++ b/drivers/hid/ipts/mei.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_MEI_H +#define IPTS_MEI_H + +#include +#include +#include +#include +#include + +#include "spec-device.h" + +struct ipts_mei_message { + struct list_head list; + struct ipts_response rsp; +}; + +struct ipts_mei { + struct mei_cl_device *cldev; + + struct list_head messages; + + wait_queue_head_t message_queue; + struct rw_semaphore message_lock; +}; + +/** + * ipts_mei_recv() - Receive data from a MEI device. + * @mei: The IPTS MEI device context. + * @code: The IPTS command code to look for. + * @rsp: The address that the received data will be copied to. + * @timeout: How many milliseconds the function will wait at most. + * + * A negative timeout means to wait forever. + * + * Returns: 0 on success, <0 on error, -EAGAIN if no response has been received. + */ +int ipts_mei_recv(struct ipts_mei *mei, enum ipts_command_code code, struct ipts_response *rsp, + u64 timeout); + +/** + * ipts_mei_send() - Send data to a MEI device. + * @ipts: The IPTS MEI device context. + * @data: The data to send. + * @size: The size of the data. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_mei_send(struct ipts_mei *mei, void *data, size_t length); + +/** + * ipts_mei_init() - Initialize the MEI device context. + * @mei: The MEI device context to initialize. + * @cldev: The MEI device the context will be bound to. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); + +#endif /* IPTS_MEI_H */ diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c new file mode 100644 index 0000000000000..ef66c3c9db807 --- /dev/null +++ b/drivers/hid/ipts/receiver.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include + +#include "cmd.h" +#include "context.h" +#include "control.h" +#include "hid.h" +#include "resources.h" +#include "spec-device.h" +#include "thread.h" + +static void ipts_receiver_next_doorbell(struct ipts_context *ipts) +{ + u32 *doorbell = (u32 *)ipts->resources.doorbell.address; + *doorbell = *doorbell + 1; +} + +static u32 ipts_receiver_current_doorbell(struct ipts_context *ipts) +{ + u32 *doorbell = (u32 *)ipts->resources.doorbell.address; + return *doorbell; +} + +static void ipts_receiver_backoff(time64_t last, u32 n) +{ + /* + * If the last change was less than n seconds ago, + * sleep for a shorter period so that new data can be + * processed quickly. If there was no change for more than + * n seconds, sleep longer to avoid wasting CPU cycles. + */ + if (last + n > ktime_get_seconds()) + usleep_range(1 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); + else + msleep(200); +} + +static int ipts_receiver_event_loop(struct ipts_thread *thread) +{ + int ret = 0; + u32 buffer = 0; + + struct ipts_context *ipts = NULL; + time64_t last = ktime_get_seconds(); + + if (!thread) + return -EFAULT; + + ipts = thread->data; + + if (!ipts) + return -EFAULT; + + dev_info(ipts->dev, "IPTS running in event mode\n"); + + while (!ipts_thread_should_stop(thread)) { + int i = 0; + + for (i = 0; i < IPTS_BUFFERS; i++) { + ret = ipts_control_wait_data(ipts, false); + if (ret == -EAGAIN) + break; + + if (ret) { + dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); + continue; + } + + buffer = ipts_receiver_current_doorbell(ipts) % IPTS_BUFFERS; + ipts_receiver_next_doorbell(ipts); + + ret = ipts_hid_input_data(ipts, buffer); + if (ret) + dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); + + ret = ipts_control_refill_buffer(ipts, buffer); + if (ret) + dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); + + ret = ipts_control_request_data(ipts); + if (ret) + dev_err(ipts->dev, "Failed to request data: %d\n", ret); + + last = ktime_get_seconds(); + } + + ipts_receiver_backoff(last, 5); + } + + ret = ipts_control_request_flush(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to request flush: %d\n", ret); + return ret; + } + + ret = ipts_control_wait_data(ipts, true); + if (ret) { + dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); + + if (ret != -EAGAIN) + return ret; + else + return 0; + } + + ret = ipts_control_wait_flush(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); + + if (ret != -EAGAIN) + return ret; + else + return 0; + } + + return 0; +} + +static int ipts_receiver_poll_loop(struct ipts_thread *thread) +{ + int ret = 0; + u32 buffer = 0; + + u32 doorbell = 0; + u32 lastdb = 0; + + struct ipts_context *ipts = NULL; + time64_t last = ktime_get_seconds(); + + if (!thread) + return -EFAULT; + + ipts = thread->data; + + if (!ipts) + return -EFAULT; + + dev_info(ipts->dev, "IPTS running in poll mode\n"); + + while (true) { + if (ipts_thread_should_stop(thread)) { + ret = ipts_control_request_flush(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to request flush: %d\n", ret); + return ret; + } + } + + doorbell = ipts_receiver_current_doorbell(ipts); + + /* + * After filling up one of the data buffers, IPTS will increment + * the doorbell. The value of the doorbell stands for the *next* + * buffer that IPTS is going to fill. + */ + while (lastdb != doorbell) { + buffer = lastdb % IPTS_BUFFERS; + + ret = ipts_hid_input_data(ipts, buffer); + if (ret) + dev_err(ipts->dev, "Failed to process buffer: %d\n", ret); + + ret = ipts_control_refill_buffer(ipts, buffer); + if (ret) + dev_err(ipts->dev, "Failed to send feedback: %d\n", ret); + + last = ktime_get_seconds(); + lastdb++; + } + + if (ipts_thread_should_stop(thread)) + break; + + ipts_receiver_backoff(last, 5); + } + + ret = ipts_control_wait_data(ipts, true); + if (ret) { + dev_err(ipts->dev, "Failed to wait for data: %d\n", ret); + + if (ret != -EAGAIN) + return ret; + else + return 0; + } + + ret = ipts_control_wait_flush(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to wait for flush: %d\n", ret); + + if (ret != -EAGAIN) + return ret; + else + return 0; + } + + return 0; +} + +int ipts_receiver_start(struct ipts_context *ipts) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + if (ipts->mode == IPTS_MODE_EVENT) { + ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_event_loop, ipts, + "ipts_event"); + } else if (ipts->mode == IPTS_MODE_POLL) { + ret = ipts_thread_start(&ipts->receiver_loop, ipts_receiver_poll_loop, ipts, + "ipts_poll"); + } else { + ret = -EINVAL; + } + + if (ret) { + dev_err(ipts->dev, "Failed to start receiver loop: %d\n", ret); + return ret; + } + + return 0; +} + +int ipts_receiver_stop(struct ipts_context *ipts) +{ + int ret = 0; + + if (!ipts) + return -EFAULT; + + ret = ipts_thread_stop(&ipts->receiver_loop); + if (ret) { + dev_err(ipts->dev, "Failed to stop receiver loop: %d\n", ret); + return ret; + } + + return 0; +} diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h new file mode 100644 index 0000000000000..3de7da62d40c1 --- /dev/null +++ b/drivers/hid/ipts/receiver.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_RECEIVER_H +#define IPTS_RECEIVER_H + +#include "context.h" + +int ipts_receiver_start(struct ipts_context *ipts); +int ipts_receiver_stop(struct ipts_context *ipts); + +#endif /* IPTS_RECEIVER_H */ diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c new file mode 100644 index 0000000000000..cc14653b2a9f5 --- /dev/null +++ b/drivers/hid/ipts/resources.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include + +#include "desc.h" +#include "resources.h" +#include "spec-device.h" + +static int ipts_resources_alloc_buffer(struct ipts_buffer *buffer, struct device *dev, size_t size) +{ + if (!buffer) + return -EFAULT; + + if (buffer->address) + return 0; + + buffer->address = dma_alloc_coherent(dev, size, &buffer->dma_address, GFP_KERNEL); + + if (!buffer->address) + return -ENOMEM; + + buffer->size = size; + buffer->device = dev; + + return 0; +} + +static void ipts_resources_free_buffer(struct ipts_buffer *buffer) +{ + if (!buffer->address) + return; + + dma_free_coherent(buffer->device, buffer->size, buffer->address, buffer->dma_address); + + buffer->address = NULL; + buffer->size = 0; + + buffer->dma_address = 0; + buffer->device = NULL; +} + +int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs) +{ + int ret = 0; + + /* + * Some compilers (AOSP clang) complain about a redefined + * variable when this is declared inside of the for loop. + */ + int i = 0; + + if (!res) + return -EFAULT; + + for (i = 0; i < IPTS_BUFFERS; i++) { + ret = ipts_resources_alloc_buffer(&res->data[i], dev, ds); + if (ret) + goto err; + } + + for (i = 0; i < IPTS_BUFFERS; i++) { + ret = ipts_resources_alloc_buffer(&res->feedback[i], dev, fs); + if (ret) + goto err; + } + + ret = ipts_resources_alloc_buffer(&res->doorbell, dev, sizeof(u32)); + if (ret) + goto err; + + ret = ipts_resources_alloc_buffer(&res->workqueue, dev, sizeof(u32)); + if (ret) + goto err; + + ret = ipts_resources_alloc_buffer(&res->hid2me, dev, fs); + if (ret) + goto err; + + ret = ipts_resources_alloc_buffer(&res->descriptor, dev, ds + 8); + if (ret) + goto err; + + if (!res->report.address) { + res->report.size = IPTS_HID_REPORT_DATA_SIZE; + res->report.address = kzalloc(res->report.size, GFP_KERNEL); + + if (!res->report.address) { + ret = -ENOMEM; + goto err; + } + } + + return 0; + +err: + + ipts_resources_free(res); + return ret; +} + +int ipts_resources_free(struct ipts_resources *res) +{ + int i = 0; + + if (!res) + return -EFAULT; + + for (i = 0; i < IPTS_BUFFERS; i++) + ipts_resources_free_buffer(&res->data[i]); + + for (i = 0; i < IPTS_BUFFERS; i++) + ipts_resources_free_buffer(&res->feedback[i]); + + ipts_resources_free_buffer(&res->doorbell); + ipts_resources_free_buffer(&res->workqueue); + ipts_resources_free_buffer(&res->hid2me); + ipts_resources_free_buffer(&res->descriptor); + + kfree(res->report.address); + res->report.address = NULL; + res->report.size = 0; + + return 0; +} diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h new file mode 100644 index 0000000000000..2068e13285f0e --- /dev/null +++ b/drivers/hid/ipts/resources.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_RESOURCES_H +#define IPTS_RESOURCES_H + +#include +#include + +#include "spec-device.h" + +struct ipts_buffer { + u8 *address; + size_t size; + + dma_addr_t dma_address; + struct device *device; +}; + +struct ipts_resources { + struct ipts_buffer data[IPTS_BUFFERS]; + struct ipts_buffer feedback[IPTS_BUFFERS]; + + struct ipts_buffer doorbell; + struct ipts_buffer workqueue; + struct ipts_buffer hid2me; + + struct ipts_buffer descriptor; + + // Buffer for synthesizing HID reports + struct ipts_buffer report; +}; + +int ipts_resources_init(struct ipts_resources *res, struct device *dev, size_t ds, size_t fs); +int ipts_resources_free(struct ipts_resources *res); + +#endif /* IPTS_RESOURCES_H */ diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h new file mode 100644 index 0000000000000..e8dd98895a7ee --- /dev/null +++ b/drivers/hid/ipts/spec-data.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_SPEC_DATA_H +#define IPTS_SPEC_DATA_H + +#include +#include + +/** + * 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 - Defines what data 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 report. + * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features report. + * @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_header - Header that is prefixed to the data in a 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 data that the buffer contains. + * @spi_offset: The offset at which to write the payload data to the sensor. + * @payload: Payload for the feedback command, or 0 if no payload is sent. + */ +struct ipts_feedback_header { + 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; + +static_assert(sizeof(struct ipts_feedback_header) == 64); + +/** + * enum ipts_data_type - Defines what type of data a buffer contains. + * @IPTS_DATA_TYPE_FRAME: Raw data frame. + * @IPTS_DATA_TYPE_ERROR: Error data. + * @IPTS_DATA_TYPE_VENDOR: Vendor specific data. + * @IPTS_DATA_TYPE_HID: A HID report. + * @IPTS_DATA_TYPE_GET_FEATURES: The response to a GET_FEATURES HID2ME command. + */ +enum ipts_data_type { + IPTS_DATA_TYPE_FRAME = 0x00, + IPTS_DATA_TYPE_ERROR = 0x01, + IPTS_DATA_TYPE_VENDOR = 0x02, + IPTS_DATA_TYPE_HID = 0x03, + IPTS_DATA_TYPE_GET_FEATURES = 0x04, + IPTS_DATA_TYPE_DESCRIPTOR = 0x05, +}; + +/** + * struct ipts_data_header - Header that is prefixed to the data in a data buffer. + * @type: What data the buffer contains. + * @size: How much data the buffer contains. + * @buffer: Which buffer the data is in. + */ +struct ipts_data_header { + enum ipts_data_type type; + u32 size; + u32 buffer; + u8 reserved[52]; + u8 data[]; +} __packed; + +static_assert(sizeof(struct ipts_data_header) == 64); + +#endif /* IPTS_SPEC_DATA_H */ diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h new file mode 100644 index 0000000000000..41845f9d90257 --- /dev/null +++ b/drivers/hid/ipts/spec-device.h @@ -0,0 +1,290 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_SPEC_DEVICE_H +#define IPTS_SPEC_DEVICE_H + +#include +#include + +/* + * The amount of buffers that IPTS can use for data transfer. + */ +#define IPTS_BUFFERS 16 + +/* + * The buffer ID that is used for HID2ME feedback + */ +#define IPTS_HID2ME_BUFFER IPTS_BUFFERS + +/** + * enum ipts_command - Commands that can be sent to the IPTS hardware. + * @IPTS_CMD_GET_DEVICE_INFO: Retrieves vendor information from the device. + * @IPTS_CMD_SET_MODE: Changes the mode that the device will operate in. + * @IPTS_CMD_SET_MEM_WINDOW: Configures memory buffers for passing data between device and driver. + * @IPTS_CMD_QUIESCE_IO: Stops the data flow from the device to the driver. + * @IPTS_CMD_READY_FOR_DATA: Informs the device that the driver is ready to receive data. + * @IPTS_CMD_FEEDBACK: Informs the device that a buffer was processed and can be refilled. + * @IPTS_CMD_CLEAR_MEM_WINDOW: Stops the data flow and clears the buffer addresses on the device. + * @IPTS_CMD_RESET_SENSOR: Resets the sensor to its default state. + * @IPTS_CMD_GET_DESCRIPTOR: Retrieves the HID descriptor of the device. + */ +enum ipts_command_code { + IPTS_CMD_GET_DEVICE_INFO = 0x01, + IPTS_CMD_SET_MODE = 0x02, + IPTS_CMD_SET_MEM_WINDOW = 0x03, + IPTS_CMD_QUIESCE_IO = 0x04, + IPTS_CMD_READY_FOR_DATA = 0x05, + IPTS_CMD_FEEDBACK = 0x06, + IPTS_CMD_CLEAR_MEM_WINDOW = 0x07, + IPTS_CMD_RESET_SENSOR = 0x0B, + IPTS_CMD_GET_DESCRIPTOR = 0x0F, +}; + +/** + * enum ipts_status - Possible status codes returned by the IPTS device. + * @IPTS_STATUS_SUCCESS: Operation completed successfully. + * @IPTS_STATUS_INVALID_PARAMS: Command contained an invalid payload. + * @IPTS_STATUS_ACCESS_DENIED: ME could not validate a buffer address. + * @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. + * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. + * @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 the driver. + * @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 an error during reset sequence. + * Further progress is not possible. + * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported an error during reset sequence. + * The driver 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 = 0x00, + IPTS_STATUS_INVALID_PARAMS = 0x01, + IPTS_STATUS_ACCESS_DENIED = 0x02, + IPTS_STATUS_CMD_SIZE_ERROR = 0x03, + IPTS_STATUS_NOT_READY = 0x04, + IPTS_STATUS_REQUEST_OUTSTANDING = 0x05, + IPTS_STATUS_NO_SENSOR_FOUND = 0x06, + IPTS_STATUS_OUT_OF_MEMORY = 0x07, + IPTS_STATUS_INTERNAL_ERROR = 0x08, + IPTS_STATUS_SENSOR_DISABLED = 0x09, + IPTS_STATUS_COMPAT_CHECK_FAIL = 0x0A, + IPTS_STATUS_SENSOR_EXPECTED_RESET = 0x0B, + IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 0x0C, + IPTS_STATUS_RESET_FAILED = 0x0D, + IPTS_STATUS_TIMEOUT = 0x0E, + IPTS_STATUS_TEST_MODE_FAIL = 0x0F, + IPTS_STATUS_SENSOR_FAIL_FATAL = 0x10, + IPTS_STATUS_SENSOR_FAIL_NONFATAL = 0x11, + IPTS_STATUS_INVALID_DEVICE_CAPS = 0x12, + IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 0x13, +}; + +/** + * struct ipts_command - Message that is sent to the device for calling a command. + * @cmd: The command that will be called. + * @payload: Payload containing parameters for the called command. + */ +struct ipts_command { + enum ipts_command_code cmd; + u8 payload[320]; +} __packed; + +static_assert(sizeof(struct ipts_command) == 324); + +/** + * enum ipts_mode - Configures what data the device produces and how its sent. + * @IPTS_MODE_EVENT: The device will send an event once a buffer was filled. + * Older devices will return singletouch data in this mode. + * @IPTS_MODE_POLL: The device will notify the driver by incrementing the doorbell value. + * Older devices will return multitouch data in this mode. + */ +enum ipts_mode { + IPTS_MODE_EVENT = 0x00, + IPTS_MODE_POLL = 0x01, +}; + +/** + * struct ipts_set_mode - Payload for the SET_MODE command. + * @mode: Changes the mode that IPTS will operate in. + */ +struct ipts_set_mode { + enum ipts_mode mode; + u8 reserved[12]; +} __packed; + +static_assert(sizeof(struct ipts_set_mode) == 16); + +#define IPTS_WORKQUEUE_SIZE 8192 +#define IPTS_WORKQUEUE_ITEM_SIZE 16 + +/** + * struct ipts_mem_window - Payload for the SET_MEM_WINDOW command. + * @data_addr_lower: Lower 32 bits of the data buffer addresses. + * @data_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. + * @feedbackaddr_lower: Lower 32 bits of the feedback buffer addresses. + * @feedbackaddr_upper: Upper 32 bits of the feedback buffer addresses. + * @hid2me_addr_lower: Lower 32 bits of the hid2me buffer address. + * @hid2me_addr_upper: Upper 32 bits of the hid2me buffer address. + * @hid2me_size: Size of the hid2me feedback buffer. + * @workqueue_item_size: Magic value. Must be 16. + * @workqueue_size: Magic value. Must be 8192. + * + * The workqueue related items in this struct are required for using + * GuC submission with binary processing firmware. Since this driver does + * not use GuC submission and instead exports raw data to userspace, these + * items are not actually used, but they need to be allocated and passed + * to the device, otherwise initialization will fail. + */ +struct ipts_mem_window { + u32 data_addr_lower[IPTS_BUFFERS]; + u32 data_addr_upper[IPTS_BUFFERS]; + u32 workqueue_addr_lower; + u32 workqueue_addr_upper; + u32 doorbell_addr_lower; + u32 doorbell_addr_upper; + u32 feedback_addr_lower[IPTS_BUFFERS]; + u32 feedback_addr_upper[IPTS_BUFFERS]; + u32 hid2me_addr_lower; + u32 hid2me_addr_upper; + u32 hid2me_size; + u8 reserved1; + u8 workqueue_item_size; + u16 workqueue_size; + u8 reserved[32]; +} __packed; + +static_assert(sizeof(struct ipts_mem_window) == 320); + +/** + * struct ipts_quiesce_io - Payload for the QUIESCE_IO command. + */ +struct ipts_quiesce_io { + u8 reserved[12]; +} __packed; + +static_assert(sizeof(struct ipts_quiesce_io) == 12); + +/** + * struct ipts_feedback - Payload for the FEEDBACK command. + * @buffer: The buffer that the device should refill. + */ +struct ipts_feedback { + u32 buffer; + u8 reserved[12]; +} __packed; + +static_assert(sizeof(struct ipts_feedback) == 16); + +/** + * enum ipts_reset_type - Possible ways of resetting the device. + * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. + * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI command. + */ +enum ipts_reset_type { + IPTS_RESET_TYPE_HARD = 0x00, + IPTS_RESET_TYPE_SOFT = 0x01, +}; + +/** + * struct ipts_reset - Payload for the RESET_SENSOR command. + * @type: How the device should get reset. + */ +struct ipts_reset_sensor { + enum ipts_reset_type type; + u8 reserved[4]; +} __packed; + +static_assert(sizeof(struct ipts_reset_sensor) == 8); + +/** + * struct ipts_get_descriptor - Payload for the GET_DESCRIPTOR command. + * @addr_lower: The lower 32 bits of the descriptor buffer address. + * @addr_upper: The upper 32 bits of the descriptor buffer address. + * @magic: A magic value. Must be 8. + */ +struct ipts_get_descriptor { + u32 addr_lower; + u32 addr_upper; + u32 magic; + u8 reserved[12]; +} __packed; + +static_assert(sizeof(struct ipts_get_descriptor) == 24); + +/* + * The type of a response is indicated by a + * command code, with the most significant bit flipped to 1. + */ +#define IPTS_RSP_BIT BIT(31) + +/** + * struct ipts_response - Data returned from the device in response to a command. + * @cmd: The command that this response answers (IPTS_RSP_BIT will be 1). + * @status: The return code of the command. + * @payload: The data that was produced by the command. + */ +struct ipts_response { + enum ipts_command_code cmd; + enum ipts_status status; + u8 payload[80]; +} __packed; + +static_assert(sizeof(struct ipts_response) == 88); + +/** + * struct ipts_device_info - Vendor information of the IPTS device. + * @vendor: Vendor ID of this device. + * @product: Product ID of this device. + * @hw_version: Hardware revision of this device. + * @fw_version: Firmware revision of this device. + * @data_size: Requested size for a data buffer. + * @feedback_size: Requested size for a feedback buffer. + * @mode: Mode that the device currently operates in. + * @max_contacts: Maximum amount of concurrent touches the sensor can process. + * @sensor_min_eds: The minimum EDS version supported by the sensor. + * @sensor_max_eds: The maximum EDS version supported by the sensor. + * @me_min_eds: The minimum EDS version supported by the ME for communicating with the sensor. + * @me_max_eds: The maximum EDS version supported by the ME for communicating with the sensor. + * @intf_eds: The EDS version implemented by the interface between ME and host. + */ +struct ipts_device_info { + u16 vendor; + u16 product; + u32 hw_version; + u32 fw_version; + u32 data_size; + u32 feedback_size; + enum ipts_mode mode; + u8 max_contacts; + u8 reserved1[3]; + u8 sensor_min_eds; + u8 sensor_maj_eds; + u8 me_min_eds; + u8 me_maj_eds; + u8 intf_eds; + u8 reserved2[11]; +} __packed; + +static_assert(sizeof(struct ipts_device_info) == 44); + +#endif /* IPTS_SPEC_DEVICE_H */ diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h new file mode 100644 index 0000000000000..5a58d4a0a610f --- /dev/null +++ b/drivers/hid/ipts/spec-hid.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2020-2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_SPEC_HID_H +#define IPTS_SPEC_HID_H + +#include +#include + +/* + * Made-up type for passing raw IPTS data in a HID report. + */ +#define IPTS_HID_FRAME_TYPE_RAW 0xEE + +/** + * struct ipts_hid_frame - Header that is prefixed to raw IPTS data wrapped in a HID report. + * @size: Size of the data inside the report, including this header. + * @type: What type of data does this report contain. + */ +struct ipts_hid_header { + u32 size; + u8 reserved1; + u8 type; + u8 reserved2; + u8 data[]; +} __packed; + +static_assert(sizeof(struct ipts_hid_header) == 7); + +#endif /* IPTS_SPEC_HID_H */ diff --git a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c new file mode 100644 index 0000000000000..355e92bea26f8 --- /dev/null +++ b/drivers/hid/ipts/thread.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include + +#include "thread.h" + +bool ipts_thread_should_stop(struct ipts_thread *thread) +{ + if (!thread) + return false; + + return READ_ONCE(thread->should_stop); +} + +static int ipts_thread_runner(void *data) +{ + int ret = 0; + struct ipts_thread *thread = data; + + if (!thread) + return -EFAULT; + + if (!thread->threadfn) + return -EFAULT; + + ret = thread->threadfn(thread); + complete_all(&thread->done); + + return ret; +} + +int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), + void *data, const char *name) +{ + if (!thread) + return -EFAULT; + + if (!threadfn) + return -EFAULT; + + init_completion(&thread->done); + + thread->data = data; + thread->should_stop = false; + thread->threadfn = threadfn; + + thread->thread = kthread_run(ipts_thread_runner, thread, name); + return PTR_ERR_OR_ZERO(thread->thread); +} + +int ipts_thread_stop(struct ipts_thread *thread) +{ + int ret = 0; + + if (!thread) + return -EFAULT; + + if (!thread->thread) + return 0; + + WRITE_ONCE(thread->should_stop, true); + + /* + * Make sure that the write has gone through before waiting. + */ + wmb(); + + wait_for_completion(&thread->done); + ret = kthread_stop(thread->thread); + + thread->thread = NULL; + thread->data = NULL; + thread->threadfn = NULL; + + return ret; +} diff --git a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h new file mode 100644 index 0000000000000..1f966b8b32c45 --- /dev/null +++ b/drivers/hid/ipts/thread.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef IPTS_THREAD_H +#define IPTS_THREAD_H + +#include +#include +#include + +/* + * This wrapper over kthread is necessary, because calling kthread_stop makes it impossible + * to issue MEI commands from that thread while it shuts itself down. By using a custom + * boolean variable and a completion object, we can call kthread_stop only when the thread + * already finished all of its work and has returned. + */ +struct ipts_thread { + struct task_struct *thread; + + bool should_stop; + struct completion done; + + void *data; + int (*threadfn)(struct ipts_thread *thread); +}; + +/** + * ipts_thread_should_stop() - Returns true if the thread is asked to terminate. + * @thread: The current thread. + * + * Returns: true if the thread should stop, false if not. + */ +bool ipts_thread_should_stop(struct ipts_thread *thread); + +/** + * ipts_thread_start() - Starts an IPTS thread. + * @thread: The thread to initialize and start. + * @threadfn: The function to execute. + * @data: An argument that will be passed to threadfn. + * @name: The name of the new thread. + * + * Returns: 0 on success, <0 on error. + */ +int ipts_thread_start(struct ipts_thread *thread, int (*threadfn)(struct ipts_thread *thread), + void *data, const char name[]); + +/** + * ipts_thread_stop() - Asks the thread to terminate and waits until it has finished. + * @thread: The thread that should stop. + * + * Returns: The return value of the thread function. + */ +int ipts_thread_stop(struct ipts_thread *thread); + +#endif /* IPTS_THREAD_H */ -- 2.45.1 From ee8823ff409dc4507cc2f7c8f8c474735b005938 Mon Sep 17 00:00:00 2001 From: Jasmin Huber Date: Mon, 15 Apr 2024 10:22:55 +0200 Subject: [PATCH] Inlude headers to avoid compiler warnings 6.8 kernels compile with -Wmissing-prototypes. Signed-off-by: Dorian Stoll Patchset: ipts --- drivers/hid/ipts/eds1.c | 1 + drivers/hid/ipts/eds2.c | 1 + drivers/hid/ipts/receiver.c | 1 + 3 files changed, 3 insertions(+) diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c index ecbb3a8bdaf60..7b9f54388a9f6 100644 --- a/drivers/hid/ipts/eds1.c +++ b/drivers/hid/ipts/eds1.c @@ -14,6 +14,7 @@ #include "context.h" #include "control.h" #include "desc.h" +#include "eds1.h" #include "spec-device.h" int ipts_eds1_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c index 198dc65d78876..639940794615d 100644 --- a/drivers/hid/ipts/eds2.c +++ b/drivers/hid/ipts/eds2.c @@ -15,6 +15,7 @@ #include "context.h" #include "control.h" #include "desc.h" +#include "eds2.h" #include "spec-data.h" int ipts_eds2_get_descriptor(struct ipts_context *ipts, u8 **desc_buffer, size_t *desc_size) diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c index ef66c3c9db807..977724c728c3e 100644 --- a/drivers/hid/ipts/receiver.c +++ b/drivers/hid/ipts/receiver.c @@ -16,6 +16,7 @@ #include "context.h" #include "control.h" #include "hid.h" +#include "receiver.h" #include "resources.h" #include "spec-device.h" #include "thread.h" -- 2.45.1 From 4b17942da35790b0e703a87267545f5a9f08e1cf Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Sun, 11 Dec 2022 12:03:38 +0100 Subject: [PATCH] iommu: intel: Disable source id verification for ITHC Signed-off-by: Dorian Stoll Patchset: ithc --- drivers/iommu/intel/irq_remapping.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c index 566297bc87ddb..a8cd8f12d5937 100644 --- a/drivers/iommu/intel/irq_remapping.c +++ b/drivers/iommu/intel/irq_remapping.c @@ -386,6 +386,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) data.busmatch_count = 0; pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + /* + * The Intel Touch Host Controller is at 00:10.6, but for some reason + * the MSI interrupts have request id 01:05.0. + * Disable id verification to work around this. + * FIXME Find proper fix or turn this into a quirk. + */ + if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { + switch(dev->device) { + case 0x98d0: case 0x98d1: // LKF + case 0xa0d0: case 0xa0d1: // TGL LP + case 0x43d0: case 0x43d1: // TGL H + set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); + return 0; + } + } + /* * DMA alias provides us with a PCI device and alias. The only case * where the it will return an alias on a different bus than the -- 2.45.1 From 14baff1c79b6499868b48e6fb4ada851db35c941 Mon Sep 17 00:00:00 2001 From: quo Date: Sun, 11 Dec 2022 12:10:54 +0100 Subject: [PATCH] hid: Add support for Intel Touch Host Controller Based on quo/ithc-linux@0b8b45d Signed-off-by: Dorian Stoll Patchset: ithc --- drivers/hid/Kconfig | 2 + drivers/hid/Makefile | 1 + drivers/hid/ithc/Kbuild | 6 + drivers/hid/ithc/Kconfig | 12 + drivers/hid/ithc/ithc-debug.c | 130 ++++++ drivers/hid/ithc/ithc-dma.c | 373 +++++++++++++++++ drivers/hid/ithc/ithc-dma.h | 69 ++++ drivers/hid/ithc/ithc-main.c | 728 ++++++++++++++++++++++++++++++++++ drivers/hid/ithc/ithc-regs.c | 96 +++++ drivers/hid/ithc/ithc-regs.h | 189 +++++++++ drivers/hid/ithc/ithc.h | 67 ++++ 11 files changed, 1673 insertions(+) create mode 100644 drivers/hid/ithc/Kbuild create mode 100644 drivers/hid/ithc/Kconfig create mode 100644 drivers/hid/ithc/ithc-debug.c create mode 100644 drivers/hid/ithc/ithc-dma.c create mode 100644 drivers/hid/ithc/ithc-dma.h create mode 100644 drivers/hid/ithc/ithc-main.c create mode 100644 drivers/hid/ithc/ithc-regs.c create mode 100644 drivers/hid/ithc/ithc-regs.h create mode 100644 drivers/hid/ithc/ithc.h diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index a263e49b2ae29..03f0f5af289a4 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1353,4 +1353,6 @@ source "drivers/hid/surface-hid/Kconfig" source "drivers/hid/ipts/Kconfig" +source "drivers/hid/ithc/Kconfig" + endif # HID_SUPPORT diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index f4bad1b8d813f..d32c194400aea 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -172,3 +172,4 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ obj-$(CONFIG_HID_IPTS) += ipts/ +obj-$(CONFIG_HID_ITHC) += ithc/ diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild new file mode 100644 index 0000000000000..aea83f2ac07b4 --- /dev/null +++ b/drivers/hid/ithc/Kbuild @@ -0,0 +1,6 @@ +obj-$(CONFIG_HID_ITHC) := ithc.o + +ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-debug.o + +ccflags-y := -std=gnu11 -Wno-declaration-after-statement + diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig new file mode 100644 index 0000000000000..ede7130236096 --- /dev/null +++ b/drivers/hid/ithc/Kconfig @@ -0,0 +1,12 @@ +config HID_ITHC + tristate "Intel Touch Host Controller" + depends on PCI + depends on HID + help + Say Y here if your system has a touchscreen using Intels + Touch Host Controller (ITHC / IPTS) technology. + + If unsure say N. + + To compile this driver as a module, choose M here: the + module will be called ithc. diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c new file mode 100644 index 0000000000000..1f1f1e33f2e5a --- /dev/null +++ b/drivers/hid/ithc/ithc-debug.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +#include "ithc.h" + +void ithc_log_regs(struct ithc *ithc) +{ + if (!ithc->prev_regs) + return; + u32 __iomem *cur = (__iomem void *)ithc->regs; + u32 *prev = (void *)ithc->prev_regs; + for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { + u32 x = readl(cur + i); + if (x != prev[i]) { + pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); + prev[i] = x; + } + } +} + +static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, + loff_t *offset) +{ + // Debug commands consist of a single letter followed by a list of numbers (decimal or + // hexadecimal, space-separated). + struct ithc *ithc = file_inode(f)->i_private; + char cmd[256]; + if (!ithc || !ithc->pci) + return -ENODEV; + if (!len) + return -EINVAL; + if (len >= sizeof(cmd)) + return -EINVAL; + if (copy_from_user(cmd, buf, len)) + return -EFAULT; + cmd[len] = 0; + if (cmd[len-1] == '\n') + cmd[len-1] = 0; + pci_info(ithc->pci, "debug command: %s\n", cmd); + + // Parse the list of arguments into a u32 array. + u32 n = 0; + const char *s = cmd + 1; + u32 a[32]; + while (*s && *s != '\n') { + if (n >= ARRAY_SIZE(a)) + return -EINVAL; + if (*s++ != ' ') + return -EINVAL; + char *e; + a[n++] = simple_strtoul(s, &e, 0); + if (e == s) + return -EINVAL; + s = e; + } + ithc_log_regs(ithc); + + // Execute the command. + switch (cmd[0]) { + case 'x': // reset + ithc_reset(ithc); + break; + case 'w': // write register: offset mask value + if (n != 3 || (a[0] & 3)) + return -EINVAL; + pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", + a[0], a[2], a[1]); + bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); + break; + case 'r': // read register: offset + if (n != 1 || (a[0] & 3)) + return -EINVAL; + pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], + readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); + break; + case 's': // spi command: cmd offset len data... + // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // set touch cfg: s 6 12 4 XX + if (n < 3 || a[2] > (n - 3) * 4) + return -EINVAL; + pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); + if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) + for (u32 i = 0; i < (a[2] + 3) / 4; i++) + pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); + break; + case 'd': // dma command: cmd len data... + // get report descriptor: d 7 8 0 0 + // enable multitouch: d 3 2 0x0105 + if (n < 2 || a[1] > (n - 2) * 4) + return -EINVAL; + pci_info(ithc->pci, "debug dma command %u with %u bytes of data\n", a[0], a[1]); + if (ithc_dma_tx(ithc, a[0], a[1], a + 2)) + pci_err(ithc->pci, "dma tx failed\n"); + break; + default: + return -EINVAL; + } + ithc_log_regs(ithc); + return len; +} + +static const struct file_operations ithc_debugfops_cmd = { + .owner = THIS_MODULE, + .write = ithc_debugfs_cmd_write, +}; + +static void ithc_debugfs_devres_release(struct device *dev, void *res) +{ + struct dentry **dbgm = res; + if (*dbgm) + debugfs_remove_recursive(*dbgm); +} + +int ithc_debug_init(struct ithc *ithc) +{ + struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); + if (!dbgm) + return -ENOMEM; + devres_add(&ithc->pci->dev, dbgm); + struct dentry *dbg = debugfs_create_dir(DEVNAME, NULL); + if (IS_ERR(dbg)) + return PTR_ERR(dbg); + *dbgm = dbg; + + struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); + if (IS_ERR(cmd)) + return PTR_ERR(cmd); + + return 0; +} + diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c new file mode 100644 index 0000000000000..ffb8689b8a780 --- /dev/null +++ b/drivers/hid/ithc/ithc-dma.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +#include "ithc.h" + +// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. +// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. +// This allows each data buffer to consist of multiple non-contiguous blocks of memory. + +static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, + unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) +{ + p->num_pages = num_pages; + p->dir = dir; + // We allocate enough space to have one PRD per data buffer page, however if the data + // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so + // some will remain unused (which is fine). + p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); + p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); + if (!p->addr) + return -ENOMEM; + if (p->dma_addr & (PAGE_SIZE - 1)) + return -EFAULT; + return 0; +} + +// Devres managed sg_table wrapper. +struct ithc_sg_table { + void *addr; + struct sg_table sgt; + enum dma_data_direction dir; +}; +static void ithc_dma_sgtable_free(struct sg_table *sgt) +{ + struct scatterlist *sg; + int i; + for_each_sgtable_sg(sgt, sg, i) { + struct page *p = sg_page(sg); + if (p) + __free_page(p); + } + sg_free_table(sgt); +} +static void ithc_dma_data_devres_release(struct device *dev, void *res) +{ + struct ithc_sg_table *sgt = res; + if (sgt->addr) + vunmap(sgt->addr); + dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); + ithc_dma_sgtable_free(&sgt->sgt); +} + +static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, + struct ithc_dma_data_buffer *b) +{ + // We don't use dma_alloc_coherent() for data buffers, because they don't have to be + // coherent (they are unidirectional) or contiguous (we can use one PRD per page). + // We could use dma_alloc_noncontiguous(), however this still always allocates a single + // DMA mapped segment, which is more restrictive than what we need. + // Instead we use an sg_table of individually allocated pages. + struct page *pages[16]; + if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) + return -EINVAL; + b->active_idx = -1; + struct ithc_sg_table *sgt = devres_alloc( + ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); + if (!sgt) + return -ENOMEM; + sgt->dir = prds->dir; + + if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { + struct scatterlist *sg; + int i; + bool ok = true; + for_each_sgtable_sg(&sgt->sgt, sg, i) { + // NOTE: don't need __GFP_DMA for PCI DMA + struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!p) { + ok = false; + break; + } + sg_set_page(sg, p, PAGE_SIZE, 0); + } + if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { + devres_add(&ithc->pci->dev, sgt); + b->sgt = &sgt->sgt; + b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); + if (!b->addr) + return -ENOMEM; + return 0; + } + ithc_dma_sgtable_free(&sgt->sgt); + } + devres_free(sgt); + return -ENOMEM; +} + +static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, + struct ithc_dma_data_buffer *b, unsigned int idx) +{ + // Give a buffer to the THC. + struct ithc_phys_region_desc *prd = prds->addr; + prd += idx * prds->num_pages; + if (b->active_idx >= 0) { + pci_err(ithc->pci, "buffer already active\n"); + return -EINVAL; + } + b->active_idx = idx; + if (prds->dir == DMA_TO_DEVICE) { + // TX buffer: Caller should have already filled the data buffer, so just fill + // the PRD and flush. + // (TODO: Support multi-page TX buffers. So far no device seems to use or need + // these though.) + if (b->data_size > PAGE_SIZE) + return -EINVAL; + prd->addr = sg_dma_address(b->sgt->sgl) >> 10; + prd->size = b->data_size | PRD_FLAG_END; + flush_kernel_vmap_range(b->addr, b->data_size); + } else if (prds->dir == DMA_FROM_DEVICE) { + // RX buffer: Reset PRDs. + struct scatterlist *sg; + int i; + for_each_sgtable_dma_sg(b->sgt, sg, i) { + prd->addr = sg_dma_address(sg) >> 10; + prd->size = sg_dma_len(sg); + prd++; + } + prd[-1].size |= PRD_FLAG_END; + } + dma_wmb(); // for the prds + dma_sync_sgtable_for_device(&ithc->pci->dev, b->sgt, prds->dir); + return 0; +} + +static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, + struct ithc_dma_data_buffer *b, unsigned int idx) +{ + // Take a buffer from the THC. + struct ithc_phys_region_desc *prd = prds->addr; + prd += idx * prds->num_pages; + // This is purely a sanity check. We don't strictly need the idx parameter for this + // function, because it should always be the same as active_idx, unless we have a bug. + if (b->active_idx != idx) { + pci_err(ithc->pci, "wrong buffer index\n"); + return -EINVAL; + } + b->active_idx = -1; + if (prds->dir == DMA_FROM_DEVICE) { + // RX buffer: Calculate actual received data size from PRDs. + dma_rmb(); // for the prds + b->data_size = 0; + struct scatterlist *sg; + int i; + for_each_sgtable_dma_sg(b->sgt, sg, i) { + unsigned int size = prd->size; + b->data_size += size & PRD_SIZE_MASK; + if (size & PRD_FLAG_END) + break; + if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { + pci_err(ithc->pci, "truncated prd\n"); + break; + } + prd++; + } + invalidate_kernel_vmap_range(b->addr, b->data_size); + } + dma_sync_sgtable_for_cpu(&ithc->pci->dev, b->sgt, prds->dir); + return 0; +} + +int ithc_dma_rx_init(struct ithc *ithc, u8 channel) +{ + struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; + mutex_init(&rx->mutex); + + // Allocate buffers. + u32 buf_size = DEVCFG_DMA_RX_SIZE(ithc->config.dma_buf_sizes); + unsigned int num_pages = (buf_size + PAGE_SIZE - 1) / PAGE_SIZE; + pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", + NUM_RX_BUF, buf_size, num_pages); + CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); + for (unsigned int i = 0; i < NUM_RX_BUF; i++) + CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); + + // Init registers. + writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); + lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); + writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); + writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); + u8 head = readb(&ithc->regs->dma_rx[channel].head); + if (head) { + pci_err(ithc->pci, "head is nonzero (%u)\n", head); + return -EIO; + } + + // Init buffers. + for (unsigned int i = 0; i < NUM_RX_BUF; i++) + CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); + + writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); + return 0; +} + +void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) +{ + bitsb_set(&ithc->regs->dma_rx[channel].control, + DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); + CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, + DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); +} + +int ithc_dma_tx_init(struct ithc *ithc) +{ + struct ithc_dma_tx *tx = &ithc->dma_tx; + mutex_init(&tx->mutex); + + // Allocate buffers. + tx->max_size = DEVCFG_DMA_TX_SIZE(ithc->config.dma_buf_sizes); + unsigned int num_pages = (tx->max_size + PAGE_SIZE - 1) / PAGE_SIZE; + pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", + tx->max_size, num_pages); + CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); + CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); + + // Init registers. + lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); + writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); + + // Init buffers. + CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + return 0; +} + +static int ithc_dma_rx_process_buf(struct ithc *ithc, struct ithc_dma_data_buffer *data, + u8 channel, u8 buf) +{ + if (buf >= NUM_RX_BUF) { + pci_err(ithc->pci, "invalid dma ringbuffer index\n"); + return -EINVAL; + } + u32 len = data->data_size; + struct ithc_dma_rx_header *hdr = data->addr; + u8 *hiddata = (void *)(hdr + 1); + if (len >= sizeof(*hdr) && hdr->code == DMA_RX_CODE_RESET) { + // The THC sends a reset request when we need to reinitialize the device. + // This usually only happens if we send an invalid command or put the device + // in a bad state. + CHECK(ithc_reset, ithc); + } else if (len < sizeof(*hdr) || len != sizeof(*hdr) + hdr->data_size) { + if (hdr->code == DMA_RX_CODE_INPUT_REPORT) { + // When the CPU enters a low power state during DMA, we can get truncated + // messages. For Surface devices, this will typically be a single touch + // report that is only 1 byte, or a multitouch report that is 257 bytes. + // See also ithc_set_active(). + } else { + pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u, code %u, data size %u\n", + channel, buf, len, hdr->code, hdr->data_size); + print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, + hdr, min(len, 0x400u), 0); + } + } else if (hdr->code == DMA_RX_CODE_REPORT_DESCRIPTOR && hdr->data_size > 8) { + // Response to a 'get report descriptor' request. + // The actual descriptor is preceded by 8 nul bytes. + CHECK(hid_parse_report, ithc->hid, hiddata + 8, hdr->data_size - 8); + WRITE_ONCE(ithc->hid_parse_done, true); + wake_up(&ithc->wait_hid_parse); + } else if (hdr->code == DMA_RX_CODE_INPUT_REPORT) { + // Standard HID input report containing touch data. + CHECK(hid_input_report, ithc->hid, HID_INPUT_REPORT, hiddata, hdr->data_size, 1); + } else if (hdr->code == DMA_RX_CODE_FEATURE_REPORT) { + // Response to a 'get feature' request. + bool done = false; + mutex_lock(&ithc->hid_get_feature_mutex); + if (ithc->hid_get_feature_buf) { + if (hdr->data_size < ithc->hid_get_feature_size) + ithc->hid_get_feature_size = hdr->data_size; + memcpy(ithc->hid_get_feature_buf, hiddata, ithc->hid_get_feature_size); + ithc->hid_get_feature_buf = NULL; + done = true; + } + mutex_unlock(&ithc->hid_get_feature_mutex); + if (done) { + wake_up(&ithc->wait_hid_get_feature); + } else { + // Received data without a matching request, or the request already + // timed out. (XXX What's the correct thing to do here?) + CHECK(hid_input_report, ithc->hid, HID_FEATURE_REPORT, + hiddata, hdr->data_size, 1); + } + } else { + pci_dbg(ithc->pci, "unhandled dma rx data! channel %u, buffer %u, size %u, code %u\n", + channel, buf, len, hdr->code); + print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, + hdr, min(len, 0x400u), 0); + } + return 0; +} + +static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) +{ + // Process all filled RX buffers from the ringbuffer. + struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; + unsigned int n = rx->num_received; + u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); + while (1) { + u8 tail = n % NUM_RX_BUF; + u8 tail_wrap = tail | ((n / NUM_RX_BUF) & 1 ? 0 : DMA_RX_WRAP_FLAG); + writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); + // ringbuffer is full if tail_wrap == head_wrap + // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG + if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) + return 0; + + // take the buffer that the device just filled + struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; + CHECK_RET(ithc_dma_data_buffer_get, ithc, &rx->prds, b, tail); + rx->num_received = ++n; + + // process data + CHECK(ithc_dma_rx_process_buf, ithc, b, channel, tail); + + // give the buffer back to the device + CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); + } +} +int ithc_dma_rx(struct ithc *ithc, u8 channel) +{ + struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; + mutex_lock(&rx->mutex); + int ret = ithc_dma_rx_unlocked(ithc, channel); + mutex_unlock(&rx->mutex); + return ret; +} + +static int ithc_dma_tx_unlocked(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) +{ + ithc_set_active(ithc, 100 * USEC_PER_MSEC); + + // Send a single TX buffer to the THC. + pci_dbg(ithc->pci, "dma tx command %u, size %u\n", cmdcode, datasize); + struct ithc_dma_tx_header *hdr; + // Data must be padded to next 4-byte boundary. + u8 padding = datasize & 3 ? 4 - (datasize & 3) : 0; + unsigned int fullsize = sizeof(*hdr) + datasize + padding; + if (fullsize > ithc->dma_tx.max_size || fullsize > PAGE_SIZE) + return -EINVAL; + CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + + // Fill the TX buffer with header and data. + ithc->dma_tx.buf.data_size = fullsize; + hdr = ithc->dma_tx.buf.addr; + hdr->code = cmdcode; + hdr->data_size = datasize; + u8 *dest = (void *)(hdr + 1); + memcpy(dest, data, datasize); + dest += datasize; + for (u8 p = 0; p < padding; p++) + *dest++ = 0; + CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + + // Let the THC process the buffer. + bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); + CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); + writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); + return 0; +} +int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) +{ + mutex_lock(&ithc->dma_tx.mutex); + int ret = ithc_dma_tx_unlocked(ithc, cmdcode, datasize, data); + mutex_unlock(&ithc->dma_tx.mutex); + return ret; +} + diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h new file mode 100644 index 0000000000000..93652e4476bf8 --- /dev/null +++ b/drivers/hid/ithc/ithc-dma.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +#define PRD_SIZE_MASK 0xffffff +#define PRD_FLAG_END 0x1000000 +#define PRD_FLAG_SUCCESS 0x2000000 +#define PRD_FLAG_ERROR 0x4000000 + +struct ithc_phys_region_desc { + u64 addr; // physical addr/1024 + u32 size; // num bytes, PRD_FLAG_END marks last prd for data split over multiple prds + u32 unused; +}; + +#define DMA_RX_CODE_INPUT_REPORT 3 +#define DMA_RX_CODE_FEATURE_REPORT 4 +#define DMA_RX_CODE_REPORT_DESCRIPTOR 5 +#define DMA_RX_CODE_RESET 7 + +struct ithc_dma_rx_header { + u32 code; + u32 data_size; + u32 _unknown[14]; +}; + +#define DMA_TX_CODE_SET_FEATURE 3 +#define DMA_TX_CODE_GET_FEATURE 4 +#define DMA_TX_CODE_OUTPUT_REPORT 5 +#define DMA_TX_CODE_GET_REPORT_DESCRIPTOR 7 + +struct ithc_dma_tx_header { + u32 code; + u32 data_size; +}; + +struct ithc_dma_prd_buffer { + void *addr; + dma_addr_t dma_addr; + u32 size; + u32 num_pages; // per data buffer + enum dma_data_direction dir; +}; + +struct ithc_dma_data_buffer { + void *addr; + struct sg_table *sgt; + int active_idx; + u32 data_size; +}; + +struct ithc_dma_tx { + struct mutex mutex; + u32 max_size; + struct ithc_dma_prd_buffer prds; + struct ithc_dma_data_buffer buf; +}; + +struct ithc_dma_rx { + struct mutex mutex; + u32 num_received; + struct ithc_dma_prd_buffer prds; + struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; +}; + +int ithc_dma_rx_init(struct ithc *ithc, u8 channel); +void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); +int ithc_dma_tx_init(struct ithc *ithc); +int ithc_dma_rx(struct ithc *ithc, u8 channel); +int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *cmddata); + diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c new file mode 100644 index 0000000000000..87ed4aa70fda0 --- /dev/null +++ b/drivers/hid/ithc/ithc-main.c @@ -0,0 +1,728 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +#include "ithc.h" + +MODULE_DESCRIPTION("Intel Touch Host Controller driver"); +MODULE_LICENSE("Dual BSD/GPL"); + +// Lakefield +#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 +#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 +// Tiger Lake +#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 +#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 +#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 +#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 +// Alder Lake +#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 +#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 +#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 +#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 +#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 +#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 +// Raptor Lake +#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 +#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 +// Meteor Lake +#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT1 0x7e48 +#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT2 0x7e4a + +static const struct pci_device_id ithc_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_MTL_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_MTL_PORT2) }, + // XXX So far the THC seems to be the only Intel PCI device with PCI_CLASS_INPUT_PEN, + // so instead of the device list we could just do: + // { .vendor = PCI_VENDOR_ID_INTEL, .device = PCI_ANY_ID, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .class = PCI_CLASS_INPUT_PEN, .class_mask = ~0, }, + {} +}; +MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); + +// Module parameters + +static bool ithc_use_polling = false; +module_param_named(poll, ithc_use_polling, bool, 0); +MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); + +// Since all known devices seem to use only channel 1, by default we disable channel 0. +static bool ithc_use_rx0 = false; +module_param_named(rx0, ithc_use_rx0, bool, 0); +MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); + +static bool ithc_use_rx1 = true; +module_param_named(rx1, ithc_use_rx1, bool, 0); +MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); + +// Values below 250 seem to work well on the SP7+. If this is set too high, you may observe cursor stuttering. +static int ithc_dma_latency_us = 200; +module_param_named(dma_latency_us, ithc_dma_latency_us, int, 0); +MODULE_PARM_DESC(dma_latency_us, "Determines the CPU latency QoS value for DMA transfers (in microseconds), -1 to disable latency QoS"); + +// Values above 1700 seem to work well on the SP7+. If this is set too low, you may observe cursor stuttering. +static unsigned int ithc_dma_early_us = 2000; +module_param_named(dma_early_us, ithc_dma_early_us, uint, 0); +MODULE_PARM_DESC(dma_early_us, "Determines how early the CPU latency QoS value is applied before the next expected IRQ (in microseconds)"); + +static bool ithc_log_regs_enabled = false; +module_param_named(logregs, ithc_log_regs_enabled, bool, 0); +MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); + +// Sysfs attributes + +static bool ithc_is_config_valid(struct ithc *ithc) +{ + return ithc->config.device_id == DEVCFG_DEVICE_ID_TIC; +} + +static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ithc *ithc = dev_get_drvdata(dev); + if (!ithc || !ithc_is_config_valid(ithc)) + return -ENODEV; + return sprintf(buf, "0x%04x", ithc->config.vendor_id); +} +static DEVICE_ATTR_RO(vendor); +static ssize_t product_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ithc *ithc = dev_get_drvdata(dev); + if (!ithc || !ithc_is_config_valid(ithc)) + return -ENODEV; + return sprintf(buf, "0x%04x", ithc->config.product_id); +} +static DEVICE_ATTR_RO(product); +static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ithc *ithc = dev_get_drvdata(dev); + if (!ithc || !ithc_is_config_valid(ithc)) + return -ENODEV; + return sprintf(buf, "%u", ithc->config.revision); +} +static DEVICE_ATTR_RO(revision); +static ssize_t fw_version_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ithc *ithc = dev_get_drvdata(dev); + if (!ithc || !ithc_is_config_valid(ithc)) + return -ENODEV; + u32 v = ithc->config.fw_version; + return sprintf(buf, "%i.%i.%i.%i", v >> 24, v >> 16 & 0xff, v >> 8 & 0xff, v & 0xff); +} +static DEVICE_ATTR_RO(fw_version); + +static const struct attribute_group *ithc_attribute_groups[] = { + &(const struct attribute_group){ + .name = DEVNAME, + .attrs = (struct attribute *[]){ + &dev_attr_vendor.attr, + &dev_attr_product.attr, + &dev_attr_revision.attr, + &dev_attr_fw_version.attr, + NULL + }, + }, + NULL +}; + +// HID setup + +static int ithc_hid_start(struct hid_device *hdev) { return 0; } +static void ithc_hid_stop(struct hid_device *hdev) { } +static int ithc_hid_open(struct hid_device *hdev) { return 0; } +static void ithc_hid_close(struct hid_device *hdev) { } + +static int ithc_hid_parse(struct hid_device *hdev) +{ + struct ithc *ithc = hdev->driver_data; + u64 val = 0; + WRITE_ONCE(ithc->hid_parse_done, false); + for (int retries = 0; ; retries++) { + CHECK_RET(ithc_dma_tx, ithc, DMA_TX_CODE_GET_REPORT_DESCRIPTOR, sizeof(val), &val); + if (wait_event_timeout(ithc->wait_hid_parse, READ_ONCE(ithc->hid_parse_done), + msecs_to_jiffies(200))) + return 0; + if (retries > 5) { + pci_err(ithc->pci, "failed to read report descriptor\n"); + return -ETIMEDOUT; + } + pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); + } +} + +static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, + size_t len, unsigned char rtype, int reqtype) +{ + struct ithc *ithc = hdev->driver_data; + if (!buf || !len) + return -EINVAL; + u32 code; + if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { + code = DMA_TX_CODE_OUTPUT_REPORT; + } else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { + code = DMA_TX_CODE_SET_FEATURE; + } else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { + code = DMA_TX_CODE_GET_FEATURE; + } else { + pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", + rtype, reqtype, reportnum); + return -EINVAL; + } + buf[0] = reportnum; + + if (reqtype == HID_REQ_GET_REPORT) { + // Prepare for response. + mutex_lock(&ithc->hid_get_feature_mutex); + ithc->hid_get_feature_buf = buf; + ithc->hid_get_feature_size = len; + mutex_unlock(&ithc->hid_get_feature_mutex); + + // Transmit 'get feature' request. + int r = CHECK(ithc_dma_tx, ithc, code, 1, buf); + if (!r) { + r = wait_event_interruptible_timeout(ithc->wait_hid_get_feature, + !ithc->hid_get_feature_buf, msecs_to_jiffies(1000)); + if (!r) + r = -ETIMEDOUT; + else if (r < 0) + r = -EINTR; + else + r = 0; + } + + // If everything went ok, the buffer has been filled with the response data. + // Return the response size. + mutex_lock(&ithc->hid_get_feature_mutex); + ithc->hid_get_feature_buf = NULL; + if (!r) + r = ithc->hid_get_feature_size; + mutex_unlock(&ithc->hid_get_feature_mutex); + return r; + } + + // 'Set feature', or 'output report'. These don't have a response. + CHECK_RET(ithc_dma_tx, ithc, code, len, buf); + return 0; +} + +static struct hid_ll_driver ithc_ll_driver = { + .start = ithc_hid_start, + .stop = ithc_hid_stop, + .open = ithc_hid_open, + .close = ithc_hid_close, + .parse = ithc_hid_parse, + .raw_request = ithc_hid_raw_request, +}; + +static void ithc_hid_devres_release(struct device *dev, void *res) +{ + struct hid_device **hidm = res; + if (*hidm) + hid_destroy_device(*hidm); +} + +static int ithc_hid_init(struct ithc *ithc) +{ + struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); + if (!hidm) + return -ENOMEM; + devres_add(&ithc->pci->dev, hidm); + struct hid_device *hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + *hidm = hid; + + strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); + strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); + hid->ll_driver = &ithc_ll_driver; + hid->bus = BUS_PCI; + hid->vendor = ithc->config.vendor_id; + hid->product = ithc->config.product_id; + hid->version = 0x100; + hid->dev.parent = &ithc->pci->dev; + hid->driver_data = ithc; + + ithc->hid = hid; + return 0; +} + +// Interrupts/polling + +static enum hrtimer_restart ithc_activity_start_timer_callback(struct hrtimer *t) +{ + struct ithc *ithc = container_of(t, struct ithc, activity_start_timer); + ithc_set_active(ithc, ithc_dma_early_us * 2 + USEC_PER_MSEC); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart ithc_activity_end_timer_callback(struct hrtimer *t) +{ + struct ithc *ithc = container_of(t, struct ithc, activity_end_timer); + cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); + return HRTIMER_NORESTART; +} + +void ithc_set_active(struct ithc *ithc, unsigned int duration_us) +{ + if (ithc_dma_latency_us < 0) + return; + // When CPU usage is very low, the CPU can enter various low power states (C2-C10). + // This disrupts DMA, causing truncated DMA messages. ERROR_FLAG_DMA_RX_TIMEOUT will be + // set when this happens. The amount of truncated messages can become very high, resulting + // in user-visible effects (laggy/stuttering cursor). To avoid this, we use a CPU latency + // QoS request to prevent the CPU from entering low power states during touch interactions. + cpu_latency_qos_update_request(&ithc->activity_qos, ithc_dma_latency_us); + hrtimer_start_range_ns(&ithc->activity_end_timer, + ns_to_ktime(duration_us * NSEC_PER_USEC), duration_us * NSEC_PER_USEC, HRTIMER_MODE_REL); +} + +static int ithc_set_device_enabled(struct ithc *ithc, bool enable) +{ + u32 x = ithc->config.touch_cfg = + (ithc->config.touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | DEVCFG_TOUCH_UNKNOWN_2 | + (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_UNKNOWN_3 | DEVCFG_TOUCH_UNKNOWN_4 : 0); + return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, + offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); +} + +static void ithc_disable_interrupts(struct ithc *ithc) +{ + writel(0, &ithc->regs->error_control); + bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); + bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); + bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); + bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); +} + +static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) +{ + writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_UNKNOWN_4 | DMA_RX_STATUS_HAVE_DATA, + &ithc->regs->dma_rx[channel].status); +} + +static void ithc_clear_interrupts(struct ithc *ithc) +{ + writel(0xffffffff, &ithc->regs->error_flags); + writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); + writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); + ithc_clear_dma_rx_interrupts(ithc, 0); + ithc_clear_dma_rx_interrupts(ithc, 1); + writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, + &ithc->regs->dma_tx.status); +} + +static void ithc_process(struct ithc *ithc) +{ + ithc_log_regs(ithc); + + bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; + bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; + + // Track time between DMA rx transfers, so we can try to predict when we need to enable CPU latency QoS for the next transfer + ktime_t t = ktime_get(); + ktime_t dt = ktime_sub(t, ithc->last_rx_time); + if (rx0 || rx1) { + ithc->last_rx_time = t; + if (dt > ms_to_ktime(100)) { + ithc->cur_rx_seq_count = 0; + ithc->cur_rx_seq_errors = 0; + } + ithc->cur_rx_seq_count++; + if (!ithc_use_polling && ithc_dma_latency_us >= 0) { + // Disable QoS, since the DMA transfer has completed (we re-enable it after a delay below) + cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); + hrtimer_try_to_cancel(&ithc->activity_end_timer); + } + } + + // Read and clear error bits + u32 err = readl(&ithc->regs->error_flags); + if (err) { + writel(err, &ithc->regs->error_flags); + if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) + pci_err(ithc->pci, "error flags: 0x%08x\n", err); + if (err & ERROR_FLAG_DMA_RX_TIMEOUT) { + // Only log an error if we see a significant number of these errors. + ithc->cur_rx_seq_errors++; + if (ithc->cur_rx_seq_errors && ithc->cur_rx_seq_errors % 50 == 0 && ithc->cur_rx_seq_errors > ithc->cur_rx_seq_count / 10) + pci_err(ithc->pci, "High number of DMA RX timeouts/errors (%u/%u, dt=%lldus). Try adjusting dma_early_us and/or dma_latency_us.\n", + ithc->cur_rx_seq_errors, ithc->cur_rx_seq_count, ktime_to_us(dt)); + } + } + + // Process DMA rx + if (ithc_use_rx0) { + ithc_clear_dma_rx_interrupts(ithc, 0); + if (rx0) + ithc_dma_rx(ithc, 0); + } + if (ithc_use_rx1) { + ithc_clear_dma_rx_interrupts(ithc, 1); + if (rx1) + ithc_dma_rx(ithc, 1); + } + + // Start timer to re-enable QoS for next rx, but only if we've seen an ERROR_FLAG_DMA_RX_TIMEOUT + if ((rx0 || rx1) && !ithc_use_polling && ithc_dma_latency_us >= 0 && ithc->cur_rx_seq_errors > 0) { + ktime_t expires = ktime_add(t, ktime_sub_us(dt, ithc_dma_early_us)); + hrtimer_start_range_ns(&ithc->activity_start_timer, expires, 10 * NSEC_PER_USEC, HRTIMER_MODE_ABS); + } + + ithc_log_regs(ithc); +} + +static irqreturn_t ithc_interrupt_thread(int irq, void *arg) +{ + struct ithc *ithc = arg; + pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", + readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), + readb(&ithc->regs->spi_cmd.control), readl(&ithc->regs->spi_cmd.status), + readb(&ithc->regs->dma_rx[0].control), readl(&ithc->regs->dma_rx[0].status), + readb(&ithc->regs->dma_rx[1].control), readl(&ithc->regs->dma_rx[1].status), + readb(&ithc->regs->dma_tx.control), readl(&ithc->regs->dma_tx.status)); + ithc_process(ithc); + return IRQ_HANDLED; +} + +static int ithc_poll_thread(void *arg) +{ + struct ithc *ithc = arg; + unsigned int sleep = 100; + while (!kthread_should_stop()) { + u32 n = ithc->dma_rx[1].num_received; + ithc_process(ithc); + // Decrease polling interval to 20ms if we received data, otherwise slowly + // increase it up to 200ms. + if (n != ithc->dma_rx[1].num_received) { + ithc_set_active(ithc, 100 * USEC_PER_MSEC); + sleep = 20; + } else { + sleep = min(200u, sleep + (sleep >> 4) + 1); + } + msleep_interruptible(sleep); + } + return 0; +} + +// Device initialization and shutdown + +static void ithc_disable(struct ithc *ithc) +{ + bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); + CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); + bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); + bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND, 0); + bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); + bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_ENABLE, 0); + bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_ENABLE, 0); + ithc_disable_interrupts(ithc); + ithc_clear_interrupts(ithc); +} + +static int ithc_init_device(struct ithc *ithc) +{ + ithc_log_regs(ithc); + bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; + ithc_disable(ithc); + CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); + + // Since we don't yet know which SPI config the device wants, use default speed and mode + // initially for reading config data. + ithc_set_spi_config(ithc, 10, 0); + + // Setting the following bit seems to make reading the config more reliable. + bitsl_set(&ithc->regs->dma_rx[0].unknown_init_bits, 0x80000000); + + // If the device was previously enabled, wait a bit to make sure it's fully shut down. + if (was_enabled) + if (msleep_interruptible(100)) + return -EINTR; + + // Take the touch device out of reset. + bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); + CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); + for (int retries = 0; ; retries++) { + ithc_log_regs(ithc); + bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); + if (!waitl(ithc, &ithc->regs->state, 0xf, 2)) + break; + if (retries > 5) { + pci_err(ithc->pci, "failed to reset device, state = 0x%08x\n", readl(&ithc->regs->state)); + return -ETIMEDOUT; + } + pci_warn(ithc->pci, "invalid state, retrying reset\n"); + bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); + if (msleep_interruptible(1000)) + return -EINTR; + } + ithc_log_regs(ithc); + + // Waiting for the following status bit makes reading config much more reliable, + // however the official driver does not seem to do this... + CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_UNKNOWN_4, DMA_RX_STATUS_UNKNOWN_4); + + // Read configuration data. + for (int retries = 0; ; retries++) { + ithc_log_regs(ithc); + memset(&ithc->config, 0, sizeof(ithc->config)); + CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(ithc->config), &ithc->config); + u32 *p = (void *)&ithc->config; + pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); + if (ithc_is_config_valid(ithc)) + break; + if (retries > 10) { + pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", + ithc->config.device_id); + return -EIO; + } + pci_warn(ithc->pci, "failed to read config, retrying\n"); + if (msleep_interruptible(100)) + return -EINTR; + } + ithc_log_regs(ithc); + + // Apply SPI config and enable touch device. + CHECK_RET(ithc_set_spi_config, ithc, + DEVCFG_SPI_MAX_FREQ(ithc->config.spi_config), + DEVCFG_SPI_MODE(ithc->config.spi_config)); + CHECK_RET(ithc_set_device_enabled, ithc, true); + ithc_log_regs(ithc); + return 0; +} + +int ithc_reset(struct ithc *ithc) +{ + // FIXME This should probably do devres_release_group()+ithc_start(). + // But because this is called during DMA processing, that would have to be done + // asynchronously (schedule_work()?). And with extra locking? + pci_err(ithc->pci, "reset\n"); + CHECK(ithc_init_device, ithc); + if (ithc_use_rx0) + ithc_dma_rx_enable(ithc, 0); + if (ithc_use_rx1) + ithc_dma_rx_enable(ithc, 1); + ithc_log_regs(ithc); + pci_dbg(ithc->pci, "reset completed\n"); + return 0; +} + +static void ithc_stop(void *res) +{ + struct ithc *ithc = res; + pci_dbg(ithc->pci, "stopping\n"); + ithc_log_regs(ithc); + + if (ithc->poll_thread) + CHECK(kthread_stop, ithc->poll_thread); + if (ithc->irq >= 0) + disable_irq(ithc->irq); + CHECK(ithc_set_device_enabled, ithc, false); + ithc_disable(ithc); + hrtimer_cancel(&ithc->activity_start_timer); + hrtimer_cancel(&ithc->activity_end_timer); + cpu_latency_qos_remove_request(&ithc->activity_qos); + + // Clear DMA config. + for (unsigned int i = 0; i < 2; i++) { + CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); + lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); + writeb(0, &ithc->regs->dma_rx[i].num_bufs); + writeb(0, &ithc->regs->dma_rx[i].num_prds); + } + lo_hi_writeq(0, &ithc->regs->dma_tx.addr); + writeb(0, &ithc->regs->dma_tx.num_prds); + + ithc_log_regs(ithc); + pci_dbg(ithc->pci, "stopped\n"); +} + +static void ithc_clear_drvdata(void *res) +{ + struct pci_dev *pci = res; + pci_set_drvdata(pci, NULL); +} + +static int ithc_start(struct pci_dev *pci) +{ + pci_dbg(pci, "starting\n"); + if (pci_get_drvdata(pci)) { + pci_err(pci, "device already initialized\n"); + return -EINVAL; + } + if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) + return -ENOMEM; + + // Allocate/init main driver struct. + struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); + if (!ithc) + return -ENOMEM; + ithc->irq = -1; + ithc->pci = pci; + snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); + init_waitqueue_head(&ithc->wait_hid_parse); + init_waitqueue_head(&ithc->wait_hid_get_feature); + mutex_init(&ithc->hid_get_feature_mutex); + pci_set_drvdata(pci, ithc); + CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); + if (ithc_log_regs_enabled) + ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); + + // PCI initialization. + CHECK_RET(pcim_enable_device, pci); + pci_set_master(pci); + CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); + CHECK_RET(dma_set_mask_and_coherent, &pci->dev, DMA_BIT_MASK(64)); + CHECK_RET(pci_set_power_state, pci, PCI_D0); + ithc->regs = pcim_iomap_table(pci)[0]; + + // Allocate IRQ. + if (!ithc_use_polling) { + CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); + ithc->irq = CHECK(pci_irq_vector, pci, 0); + if (ithc->irq < 0) + return ithc->irq; + } + + // Initialize THC and touch device. + CHECK_RET(ithc_init_device, ithc); + CHECK(devm_device_add_groups, &pci->dev, ithc_attribute_groups); + if (ithc_use_rx0) + CHECK_RET(ithc_dma_rx_init, ithc, 0); + if (ithc_use_rx1) + CHECK_RET(ithc_dma_rx_init, ithc, 1); + CHECK_RET(ithc_dma_tx_init, ithc); + + cpu_latency_qos_add_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); + hrtimer_init(&ithc->activity_start_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + ithc->activity_start_timer.function = ithc_activity_start_timer_callback; + hrtimer_init(&ithc->activity_end_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ithc->activity_end_timer.function = ithc_activity_end_timer_callback; + + // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are + // disabled BEFORE the buffers are freed. + CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); + + CHECK_RET(ithc_hid_init, ithc); + + // Start polling/IRQ. + if (ithc_use_polling) { + pci_info(pci, "using polling instead of irq\n"); + // Use a thread instead of simple timer because we want to be able to sleep. + ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); + if (IS_ERR(ithc->poll_thread)) { + int err = PTR_ERR(ithc->poll_thread); + ithc->poll_thread = NULL; + return err; + } + } else { + CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, + ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); + } + + if (ithc_use_rx0) + ithc_dma_rx_enable(ithc, 0); + if (ithc_use_rx1) + ithc_dma_rx_enable(ithc, 1); + + // hid_add_device() can only be called after irq/polling is started and DMA is enabled, + // because it calls ithc_hid_parse() which reads the report descriptor via DMA. + CHECK_RET(hid_add_device, ithc->hid); + + CHECK(ithc_debug_init, ithc); + + pci_dbg(pci, "started\n"); + return 0; +} + +static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + pci_dbg(pci, "device probe\n"); + return ithc_start(pci); +} + +static void ithc_remove(struct pci_dev *pci) +{ + pci_dbg(pci, "device remove\n"); + // all cleanup is handled by devres +} + +// For suspend/resume, we just deinitialize and reinitialize everything. +// TODO It might be cleaner to keep the HID device around, however we would then have to signal +// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set +// feature' requests. Hidraw does not seem to have a facility to do that. +static int ithc_suspend(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm suspend\n"); + devres_release_group(dev, ithc_start); + return 0; +} + +static int ithc_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm resume\n"); + return ithc_start(pci); +} + +static int ithc_freeze(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm freeze\n"); + devres_release_group(dev, ithc_start); + return 0; +} + +static int ithc_thaw(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm thaw\n"); + return ithc_start(pci); +} + +static int ithc_restore(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm restore\n"); + return ithc_start(pci); +} + +static struct pci_driver ithc_driver = { + .name = DEVNAME, + .id_table = ithc_pci_tbl, + .probe = ithc_probe, + .remove = ithc_remove, + .driver.pm = &(const struct dev_pm_ops) { + .suspend = ithc_suspend, + .resume = ithc_resume, + .freeze = ithc_freeze, + .thaw = ithc_thaw, + .restore = ithc_restore, + }, + //.dev_groups = ithc_attribute_groups, // could use this (since 5.14), however the attributes won't have valid values until config has been read anyway +}; + +static int __init ithc_init(void) +{ + return pci_register_driver(&ithc_driver); +} + +static void __exit ithc_exit(void) +{ + pci_unregister_driver(&ithc_driver); +} + +module_init(ithc_init); +module_exit(ithc_exit); + diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c new file mode 100644 index 0000000000000..e058721886e37 --- /dev/null +++ b/drivers/hid/ithc/ithc-regs.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +#include "ithc.h" + +#define reg_num(r) (0x1fff & (u16)(__force u64)(r)) + +void bitsl(__iomem u32 *reg, u32 mask, u32 val) +{ + if (val & ~mask) + pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", + reg_num(reg), val, mask); + writel((readl(reg) & ~mask) | (val & mask), reg); +} + +void bitsb(__iomem u8 *reg, u8 mask, u8 val) +{ + if (val & ~mask) + pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", + reg_num(reg), val, mask); + writeb((readb(reg) & ~mask) | (val & mask), reg); +} + +int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) +{ + pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", + reg_num(reg), mask, val); + u32 x; + if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { + pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", + reg_num(reg), mask, val); + return -ETIMEDOUT; + } + pci_dbg(ithc->pci, "done waiting\n"); + return 0; +} + +int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) +{ + pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", + reg_num(reg), mask, val); + u8 x; + if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { + pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", + reg_num(reg), mask, val); + return -ETIMEDOUT; + } + pci_dbg(ithc->pci, "done waiting\n"); + return 0; +} + +int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode) +{ + pci_dbg(ithc->pci, "setting SPI speed to %i, mode %i\n", speed, mode); + if (mode == 3) + mode = 2; + bitsl(&ithc->regs->spi_config, + SPI_CONFIG_MODE(0xff) | SPI_CONFIG_SPEED(0xff) | SPI_CONFIG_UNKNOWN_18(0xff) | SPI_CONFIG_SPEED2(0xff), + SPI_CONFIG_MODE(mode) | SPI_CONFIG_SPEED(speed) | SPI_CONFIG_UNKNOWN_18(0) | SPI_CONFIG_SPEED2(speed)); + return 0; +} + +int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) +{ + pci_dbg(ithc->pci, "SPI command %u, size %u, offset %u\n", command, size, offset); + if (size > sizeof(ithc->regs->spi_cmd.data)) + return -EINVAL; + + // Wait if the device is still busy. + CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); + // Clear result flags. + writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); + + // Init SPI command data. + writeb(command, &ithc->regs->spi_cmd.code); + writew(size, &ithc->regs->spi_cmd.size); + writel(offset, &ithc->regs->spi_cmd.offset); + u32 *p = data, n = (size + 3) / 4; + for (u32 i = 0; i < n; i++) + writel(p[i], &ithc->regs->spi_cmd.data[i]); + + // Start transmission. + bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); + CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); + + // Read response. + if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) + return -EIO; + if (readw(&ithc->regs->spi_cmd.size) != size) + return -EMSGSIZE; + for (u32 i = 0; i < n; i++) + p[i] = readl(&ithc->regs->spi_cmd.data[i]); + + writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); + return 0; +} + diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h new file mode 100644 index 0000000000000..d4007d9e2bacc --- /dev/null +++ b/drivers/hid/ithc/ithc-regs.h @@ -0,0 +1,189 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +#define CONTROL_QUIESCE BIT(1) +#define CONTROL_IS_QUIESCED BIT(2) +#define CONTROL_NRESET BIT(3) +#define CONTROL_READY BIT(29) + +#define SPI_CONFIG_MODE(x) (((x) & 3) << 2) +#define SPI_CONFIG_SPEED(x) (((x) & 7) << 4) +#define SPI_CONFIG_UNKNOWN_18(x) (((x) & 3) << 18) +#define SPI_CONFIG_SPEED2(x) (((x) & 0xf) << 20) // high bit = high speed mode? + +#define ERROR_CONTROL_UNKNOWN_0 BIT(0) +#define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs +#define ERROR_CONTROL_UNKNOWN_2 BIT(2) +#define ERROR_CONTROL_UNKNOWN_3 BIT(3) +#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_9 BIT(9) +#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_10 BIT(10) +#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_12 BIT(12) +#define ERROR_CONTROL_IRQ_DMA_UNKNOWN_13 BIT(13) +#define ERROR_CONTROL_UNKNOWN_16(x) (((x) & 0xff) << 16) // spi error code irq? +#define ERROR_CONTROL_SET_DMA_STATUS BIT(29) // sets DMA_RX_STATUS_ERROR when a DMA error occurs + +#define ERROR_STATUS_DMA BIT(28) +#define ERROR_STATUS_SPI BIT(30) + +#define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) +#define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) +#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message +#define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) +#define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) +#define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) +#define ERROR_FLAG_SPI_INTRA_PACKET_TIMEOUT BIT(18) +#define ERROR_FLAG_SPI_INVALID_RESPONSE BIT(19) +#define ERROR_FLAG_SPI_HS_RX_TIMEOUT BIT(20) +#define ERROR_FLAG_SPI_TOUCH_IC_INIT BIT(21) + +#define SPI_CMD_CONTROL_SEND BIT(0) // cleared by device when sending is complete +#define SPI_CMD_CONTROL_IRQ BIT(1) + +#define SPI_CMD_CODE_READ 4 +#define SPI_CMD_CODE_WRITE 6 + +#define SPI_CMD_STATUS_DONE BIT(0) +#define SPI_CMD_STATUS_ERROR BIT(1) +#define SPI_CMD_STATUS_BUSY BIT(3) + +#define DMA_TX_CONTROL_SEND BIT(0) // cleared by device when sending is complete +#define DMA_TX_CONTROL_IRQ BIT(3) + +#define DMA_TX_STATUS_DONE BIT(0) +#define DMA_TX_STATUS_ERROR BIT(1) +#define DMA_TX_STATUS_UNKNOWN_2 BIT(2) +#define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? + +#define DMA_RX_CONTROL_ENABLE BIT(0) +#define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? +#define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? +#define DMA_RX_CONTROL_IRQ_UNKNOWN_4 BIT(4) // rx0 only? +#define DMA_RX_CONTROL_IRQ_DATA BIT(5) + +#define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? +#define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices + +#define DMA_RX_WRAP_FLAG BIT(7) + +#define DMA_RX_STATUS_ERROR BIT(3) +#define DMA_RX_STATUS_UNKNOWN_4 BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) +#define DMA_RX_STATUS_HAVE_DATA BIT(5) +#define DMA_RX_STATUS_ENABLED BIT(8) + +// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. +#define COUNTER_RESET BIT(31) + +struct ithc_registers { + /* 0000 */ u32 _unknown_0000[1024]; + /* 1000 */ u32 _unknown_1000; + /* 1004 */ u32 _unknown_1004; + /* 1008 */ u32 control_bits; + /* 100c */ u32 _unknown_100c; + /* 1010 */ u32 spi_config; + /* 1014 */ u32 _unknown_1014[3]; + /* 1020 */ u32 error_control; + /* 1024 */ u32 error_status; // write to clear + /* 1028 */ u32 error_flags; // write to clear + /* 102c */ u32 _unknown_102c[5]; + struct { + /* 1040 */ u8 control; + /* 1041 */ u8 code; + /* 1042 */ u16 size; + /* 1044 */ u32 status; // write to clear + /* 1048 */ u32 offset; + /* 104c */ u32 data[16]; + /* 108c */ u32 _unknown_108c; + } spi_cmd; + struct { + /* 1090 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() + /* 1098 */ u8 control; + /* 1099 */ u8 _unknown_1099; + /* 109a */ u8 _unknown_109a; + /* 109b */ u8 num_prds; + /* 109c */ u32 status; // write to clear + } dma_tx; + /* 10a0 */ u32 _unknown_10a0[7]; + /* 10bc */ u32 state; // is 0xe0000402 (dev config val 0) after CONTROL_NRESET, 0xe0000461 after first touch, 0xe0000401 after DMA_RX_CODE_RESET + /* 10c0 */ u32 _unknown_10c0[8]; + /* 10e0 */ u32 _unknown_10e0_counters[3]; + /* 10ec */ u32 _unknown_10ec[5]; + struct { + /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() + /* 1108/1208 */ u8 num_bufs; + /* 1109/1209 */ u8 num_prds; + /* 110a/120a */ u16 _unknown_110a; + /* 110c/120c */ u8 control; + /* 110d/120d */ u8 head; + /* 110e/120e */ u8 tail; + /* 110f/120f */ u8 control2; + /* 1110/1210 */ u32 status; // write to clear + /* 1114/1214 */ u32 _unknown_1114; + /* 1118/1218 */ u64 _unknown_1118_guc_addr; + /* 1120/1220 */ u32 _unknown_1120_guc; + /* 1124/1224 */ u32 _unknown_1124_guc; + /* 1128/1228 */ u32 unknown_init_bits; // bit 2 = guc related, bit 3 = rx1 related, bit 4 = guc related + /* 112c/122c */ u32 _unknown_112c; + /* 1130/1230 */ u64 _unknown_1130_guc_addr; + /* 1138/1238 */ u32 _unknown_1138_guc; + /* 113c/123c */ u32 _unknown_113c; + /* 1140/1240 */ u32 _unknown_1140_guc; + /* 1144/1244 */ u32 _unknown_1144[23]; + /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; + /* 11b8/12b8 */ u32 _unknown_11b8[18]; + } dma_rx[2]; +}; +static_assert(sizeof(struct ithc_registers) == 0x1300); + +#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) +#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) + +#define DEVCFG_TOUCH_MASK 0x3f +#define DEVCFG_TOUCH_ENABLE BIT(0) +#define DEVCFG_TOUCH_UNKNOWN_1 BIT(1) +#define DEVCFG_TOUCH_UNKNOWN_2 BIT(2) +#define DEVCFG_TOUCH_UNKNOWN_3 BIT(3) +#define DEVCFG_TOUCH_UNKNOWN_4 BIT(4) +#define DEVCFG_TOUCH_UNKNOWN_5 BIT(5) +#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) + +#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" + +#define DEVCFG_SPI_MAX_FREQ(x) (((x) >> 1) & 0xf) // high bit = use high speed mode? +#define DEVCFG_SPI_MODE(x) (((x) >> 6) & 3) +#define DEVCFG_SPI_UNKNOWN_8(x) (((x) >> 8) & 0x3f) +#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat +#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) +#define DEVCFG_SPI_UNKNOWN_25 BIT(25) +#define DEVCFG_SPI_UNKNOWN_26 BIT(26) +#define DEVCFG_SPI_UNKNOWN_27 BIT(27) +#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this +#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? + +struct ithc_device_config { // (Example values are from an SP7+.) + u32 _unknown_00; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) + u32 _unknown_04; // 04 = 0x00000000 + u32 dma_buf_sizes; // 08 = 0x000a00ff + u32 touch_cfg; // 0c = 0x0000001c + u32 _unknown_10; // 10 = 0x0000001c + u32 device_id; // 14 = 0x43495424 = "$TIC" + u32 spi_config; // 18 = 0xfda00a2e + u16 vendor_id; // 1c = 0x045e = Microsoft Corp. + u16 product_id; // 1e = 0x0c1a + u32 revision; // 20 = 0x00000001 + u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) + u32 _unknown_28; // 28 = 0x00000000 + u32 fw_mode; // 2c = 0x00000000 (for fw update?) + u32 _unknown_30; // 30 = 0x00000000 + u32 _unknown_34; // 34 = 0x0404035e (u8,u8,u8,u8 = version?) + u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) + u32 _unknown_3c; // 3c = 0x00000002 +}; + +void bitsl(__iomem u32 *reg, u32 mask, u32 val); +void bitsb(__iomem u8 *reg, u8 mask, u8 val); +#define bitsl_set(reg, x) bitsl(reg, x, x) +#define bitsb_set(reg, x) bitsb(reg, x, x) +int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); +int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); +int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode); +int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); + diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h new file mode 100644 index 0000000000000..028e55a4ec53e --- /dev/null +++ b/drivers/hid/ithc/ithc.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVNAME "ithc" +#define DEVFULLNAME "Intel Touch Host Controller" + +#undef pr_fmt +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) +#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) + +#define NUM_RX_BUF 16 + +struct ithc; + +#include "ithc-regs.h" +#include "ithc-dma.h" + +struct ithc { + char phys[32]; + struct pci_dev *pci; + int irq; + struct task_struct *poll_thread; + + struct pm_qos_request activity_qos; + struct hrtimer activity_start_timer; + struct hrtimer activity_end_timer; + ktime_t last_rx_time; + unsigned int cur_rx_seq_count; + unsigned int cur_rx_seq_errors; + + struct hid_device *hid; + bool hid_parse_done; + wait_queue_head_t wait_hid_parse; + wait_queue_head_t wait_hid_get_feature; + struct mutex hid_get_feature_mutex; + void *hid_get_feature_buf; + size_t hid_get_feature_size; + + struct ithc_registers __iomem *regs; + struct ithc_registers *prev_regs; // for debugging + struct ithc_device_config config; + struct ithc_dma_rx dma_rx[2]; + struct ithc_dma_tx dma_tx; +}; + +int ithc_reset(struct ithc *ithc); +void ithc_set_active(struct ithc *ithc, unsigned int duration_us); +int ithc_debug_init(struct ithc *ithc); +void ithc_log_regs(struct ithc *ithc); + -- 2.45.1 From 2537922b2f0c1d0002a2afe6d0fc79695add8e60 Mon Sep 17 00:00:00 2001 From: quo Date: Fri, 19 Apr 2024 22:11:09 +0200 Subject: [PATCH] hid: ithc: Update from quo/ithc-linux - Added QuickSPI support for Surface Laptop Studio 2 - Use Latency Tolerance Reporting instead of manual CPU latency adjustments Based on: https://github.com/quo/ithc-linux/commit/18afc6ffacd70b49fdee2eb1ab0a8acd159edb31 Signed-off-by: Dorian Stoll Patchset: ithc --- drivers/hid/ithc/Kbuild | 2 +- drivers/hid/ithc/ithc-debug.c | 33 +- drivers/hid/ithc/ithc-debug.h | 7 + drivers/hid/ithc/ithc-dma.c | 125 ++----- drivers/hid/ithc/ithc-dma.h | 24 +- drivers/hid/ithc/ithc-hid.c | 207 +++++++++++ drivers/hid/ithc/ithc-hid.h | 32 ++ drivers/hid/ithc/ithc-legacy.c | 252 ++++++++++++++ drivers/hid/ithc/ithc-legacy.h | 8 + drivers/hid/ithc/ithc-main.c | 386 ++++----------------- drivers/hid/ithc/ithc-quickspi.c | 578 +++++++++++++++++++++++++++++++ drivers/hid/ithc/ithc-quickspi.h | 39 +++ drivers/hid/ithc/ithc-regs.c | 72 +++- drivers/hid/ithc/ithc-regs.h | 143 ++++---- drivers/hid/ithc/ithc.h | 71 ++-- 15 files changed, 1441 insertions(+), 538 deletions(-) create mode 100644 drivers/hid/ithc/ithc-debug.h create mode 100644 drivers/hid/ithc/ithc-hid.c create mode 100644 drivers/hid/ithc/ithc-hid.h create mode 100644 drivers/hid/ithc/ithc-legacy.c create mode 100644 drivers/hid/ithc/ithc-legacy.h create mode 100644 drivers/hid/ithc/ithc-quickspi.c create mode 100644 drivers/hid/ithc/ithc-quickspi.h diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild index aea83f2ac07b4..4937ba1312973 100644 --- a/drivers/hid/ithc/Kbuild +++ b/drivers/hid/ithc/Kbuild @@ -1,6 +1,6 @@ obj-$(CONFIG_HID_ITHC) := ithc.o -ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-debug.o +ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-hid.o ithc-legacy.o ithc-quickspi.o ithc-debug.o ccflags-y := -std=gnu11 -Wno-declaration-after-statement diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c index 1f1f1e33f2e5a..2d8c6afe99663 100644 --- a/drivers/hid/ithc/ithc-debug.c +++ b/drivers/hid/ithc/ithc-debug.c @@ -85,10 +85,11 @@ static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, si case 'd': // dma command: cmd len data... // get report descriptor: d 7 8 0 0 // enable multitouch: d 3 2 0x0105 - if (n < 2 || a[1] > (n - 2) * 4) + if (n < 1) return -EINVAL; - pci_info(ithc->pci, "debug dma command %u with %u bytes of data\n", a[0], a[1]); - if (ithc_dma_tx(ithc, a[0], a[1], a + 2)) + pci_info(ithc->pci, "debug dma command with %u bytes of data\n", n * 4); + struct ithc_data data = { .type = ITHC_DATA_RAW, .size = n * 4, .data = a }; + if (ithc_dma_tx(ithc, &data)) pci_err(ithc->pci, "dma tx failed\n"); break; default: @@ -98,6 +99,23 @@ static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, si return len; } +static struct dentry *dbg_dir; + +void __init ithc_debug_init_module(void) +{ + struct dentry *d = debugfs_create_dir(DEVNAME, NULL); + if (IS_ERR(d)) + pr_warn("failed to create debugfs dir (%li)\n", PTR_ERR(d)); + else + dbg_dir = d; +} + +void __exit ithc_debug_exit_module(void) +{ + debugfs_remove_recursive(dbg_dir); + dbg_dir = NULL; +} + static const struct file_operations ithc_debugfops_cmd = { .owner = THIS_MODULE, .write = ithc_debugfs_cmd_write, @@ -106,17 +124,18 @@ static const struct file_operations ithc_debugfops_cmd = { static void ithc_debugfs_devres_release(struct device *dev, void *res) { struct dentry **dbgm = res; - if (*dbgm) - debugfs_remove_recursive(*dbgm); + debugfs_remove_recursive(*dbgm); } -int ithc_debug_init(struct ithc *ithc) +int ithc_debug_init_device(struct ithc *ithc) { + if (!dbg_dir) + return -ENOENT; struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); if (!dbgm) return -ENOMEM; devres_add(&ithc->pci->dev, dbgm); - struct dentry *dbg = debugfs_create_dir(DEVNAME, NULL); + struct dentry *dbg = debugfs_create_dir(pci_name(ithc->pci), dbg_dir); if (IS_ERR(dbg)) return PTR_ERR(dbg); *dbgm = dbg; diff --git a/drivers/hid/ithc/ithc-debug.h b/drivers/hid/ithc/ithc-debug.h new file mode 100644 index 0000000000000..38c53d916bdb5 --- /dev/null +++ b/drivers/hid/ithc/ithc-debug.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +void ithc_debug_init_module(void); +void ithc_debug_exit_module(void); +int ithc_debug_init_device(struct ithc *ithc); +void ithc_log_regs(struct ithc *ithc); + diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c index ffb8689b8a780..bf4eab33062b0 100644 --- a/drivers/hid/ithc/ithc-dma.c +++ b/drivers/hid/ithc/ithc-dma.c @@ -173,10 +173,9 @@ int ithc_dma_rx_init(struct ithc *ithc, u8 channel) mutex_init(&rx->mutex); // Allocate buffers. - u32 buf_size = DEVCFG_DMA_RX_SIZE(ithc->config.dma_buf_sizes); - unsigned int num_pages = (buf_size + PAGE_SIZE - 1) / PAGE_SIZE; + unsigned int num_pages = (ithc->max_rx_size + PAGE_SIZE - 1) / PAGE_SIZE; pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", - NUM_RX_BUF, buf_size, num_pages); + NUM_RX_BUF, ithc->max_rx_size, num_pages); CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); for (unsigned int i = 0; i < NUM_RX_BUF; i++) CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); @@ -214,10 +213,9 @@ int ithc_dma_tx_init(struct ithc *ithc) mutex_init(&tx->mutex); // Allocate buffers. - tx->max_size = DEVCFG_DMA_TX_SIZE(ithc->config.dma_buf_sizes); - unsigned int num_pages = (tx->max_size + PAGE_SIZE - 1) / PAGE_SIZE; + unsigned int num_pages = (ithc->max_tx_size + PAGE_SIZE - 1) / PAGE_SIZE; pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", - tx->max_size, num_pages); + ithc->max_tx_size, num_pages); CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); @@ -230,71 +228,6 @@ int ithc_dma_tx_init(struct ithc *ithc) return 0; } -static int ithc_dma_rx_process_buf(struct ithc *ithc, struct ithc_dma_data_buffer *data, - u8 channel, u8 buf) -{ - if (buf >= NUM_RX_BUF) { - pci_err(ithc->pci, "invalid dma ringbuffer index\n"); - return -EINVAL; - } - u32 len = data->data_size; - struct ithc_dma_rx_header *hdr = data->addr; - u8 *hiddata = (void *)(hdr + 1); - if (len >= sizeof(*hdr) && hdr->code == DMA_RX_CODE_RESET) { - // The THC sends a reset request when we need to reinitialize the device. - // This usually only happens if we send an invalid command or put the device - // in a bad state. - CHECK(ithc_reset, ithc); - } else if (len < sizeof(*hdr) || len != sizeof(*hdr) + hdr->data_size) { - if (hdr->code == DMA_RX_CODE_INPUT_REPORT) { - // When the CPU enters a low power state during DMA, we can get truncated - // messages. For Surface devices, this will typically be a single touch - // report that is only 1 byte, or a multitouch report that is 257 bytes. - // See also ithc_set_active(). - } else { - pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u, code %u, data size %u\n", - channel, buf, len, hdr->code, hdr->data_size); - print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, - hdr, min(len, 0x400u), 0); - } - } else if (hdr->code == DMA_RX_CODE_REPORT_DESCRIPTOR && hdr->data_size > 8) { - // Response to a 'get report descriptor' request. - // The actual descriptor is preceded by 8 nul bytes. - CHECK(hid_parse_report, ithc->hid, hiddata + 8, hdr->data_size - 8); - WRITE_ONCE(ithc->hid_parse_done, true); - wake_up(&ithc->wait_hid_parse); - } else if (hdr->code == DMA_RX_CODE_INPUT_REPORT) { - // Standard HID input report containing touch data. - CHECK(hid_input_report, ithc->hid, HID_INPUT_REPORT, hiddata, hdr->data_size, 1); - } else if (hdr->code == DMA_RX_CODE_FEATURE_REPORT) { - // Response to a 'get feature' request. - bool done = false; - mutex_lock(&ithc->hid_get_feature_mutex); - if (ithc->hid_get_feature_buf) { - if (hdr->data_size < ithc->hid_get_feature_size) - ithc->hid_get_feature_size = hdr->data_size; - memcpy(ithc->hid_get_feature_buf, hiddata, ithc->hid_get_feature_size); - ithc->hid_get_feature_buf = NULL; - done = true; - } - mutex_unlock(&ithc->hid_get_feature_mutex); - if (done) { - wake_up(&ithc->wait_hid_get_feature); - } else { - // Received data without a matching request, or the request already - // timed out. (XXX What's the correct thing to do here?) - CHECK(hid_input_report, ithc->hid, HID_FEATURE_REPORT, - hiddata, hdr->data_size, 1); - } - } else { - pci_dbg(ithc->pci, "unhandled dma rx data! channel %u, buffer %u, size %u, code %u\n", - channel, buf, len, hdr->code); - print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, - hdr, min(len, 0x400u), 0); - } - return 0; -} - static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) { // Process all filled RX buffers from the ringbuffer. @@ -316,7 +249,16 @@ static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) rx->num_received = ++n; // process data - CHECK(ithc_dma_rx_process_buf, ithc, b, channel, tail); + struct ithc_data d; + if ((ithc->use_quickspi ? ithc_quickspi_decode_rx : ithc_legacy_decode_rx) + (ithc, b->addr, b->data_size, &d) < 0) { + pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u: %*ph\n", + channel, tail, b->data_size, min((int)b->data_size, 64), b->addr); + print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, + b->addr, min(b->data_size, 0x400u), 0); + } else { + ithc_hid_process_data(ithc, &d); + } // give the buffer back to the device CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); @@ -331,31 +273,28 @@ int ithc_dma_rx(struct ithc *ithc, u8 channel) return ret; } -static int ithc_dma_tx_unlocked(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) +static int ithc_dma_tx_unlocked(struct ithc *ithc, const struct ithc_data *data) { - ithc_set_active(ithc, 100 * USEC_PER_MSEC); - // Send a single TX buffer to the THC. - pci_dbg(ithc->pci, "dma tx command %u, size %u\n", cmdcode, datasize); - struct ithc_dma_tx_header *hdr; - // Data must be padded to next 4-byte boundary. - u8 padding = datasize & 3 ? 4 - (datasize & 3) : 0; - unsigned int fullsize = sizeof(*hdr) + datasize + padding; - if (fullsize > ithc->dma_tx.max_size || fullsize > PAGE_SIZE) - return -EINVAL; + pci_dbg(ithc->pci, "dma tx data type %u, size %u\n", data->type, data->size); CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); // Fill the TX buffer with header and data. - ithc->dma_tx.buf.data_size = fullsize; - hdr = ithc->dma_tx.buf.addr; - hdr->code = cmdcode; - hdr->data_size = datasize; - u8 *dest = (void *)(hdr + 1); - memcpy(dest, data, datasize); - dest += datasize; - for (u8 p = 0; p < padding; p++) - *dest++ = 0; + ssize_t sz; + if (data->type == ITHC_DATA_RAW) { + sz = min(data->size, ithc->max_tx_size); + memcpy(ithc->dma_tx.buf.addr, data->data, sz); + } else { + sz = (ithc->use_quickspi ? ithc_quickspi_encode_tx : ithc_legacy_encode_tx) + (ithc, data, ithc->dma_tx.buf.addr, ithc->max_tx_size); + } + ithc->dma_tx.buf.data_size = sz < 0 ? 0 : sz; CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + if (sz < 0) { + pci_err(ithc->pci, "failed to encode tx data type %i, size %u, error %i\n", + data->type, data->size, (int)sz); + return -EINVAL; + } // Let the THC process the buffer. bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); @@ -363,10 +302,10 @@ static int ithc_dma_tx_unlocked(struct ithc *ithc, u32 cmdcode, u32 datasize, vo writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); return 0; } -int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) +int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data) { mutex_lock(&ithc->dma_tx.mutex); - int ret = ithc_dma_tx_unlocked(ithc, cmdcode, datasize, data); + int ret = ithc_dma_tx_unlocked(ithc, data); mutex_unlock(&ithc->dma_tx.mutex); return ret; } diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h index 93652e4476bf8..1749a5819b3e7 100644 --- a/drivers/hid/ithc/ithc-dma.h +++ b/drivers/hid/ithc/ithc-dma.h @@ -11,27 +11,6 @@ struct ithc_phys_region_desc { u32 unused; }; -#define DMA_RX_CODE_INPUT_REPORT 3 -#define DMA_RX_CODE_FEATURE_REPORT 4 -#define DMA_RX_CODE_REPORT_DESCRIPTOR 5 -#define DMA_RX_CODE_RESET 7 - -struct ithc_dma_rx_header { - u32 code; - u32 data_size; - u32 _unknown[14]; -}; - -#define DMA_TX_CODE_SET_FEATURE 3 -#define DMA_TX_CODE_GET_FEATURE 4 -#define DMA_TX_CODE_OUTPUT_REPORT 5 -#define DMA_TX_CODE_GET_REPORT_DESCRIPTOR 7 - -struct ithc_dma_tx_header { - u32 code; - u32 data_size; -}; - struct ithc_dma_prd_buffer { void *addr; dma_addr_t dma_addr; @@ -49,7 +28,6 @@ struct ithc_dma_data_buffer { struct ithc_dma_tx { struct mutex mutex; - u32 max_size; struct ithc_dma_prd_buffer prds; struct ithc_dma_data_buffer buf; }; @@ -65,5 +43,5 @@ int ithc_dma_rx_init(struct ithc *ithc, u8 channel); void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); int ithc_dma_tx_init(struct ithc *ithc); int ithc_dma_rx(struct ithc *ithc, u8 channel); -int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *cmddata); +int ithc_dma_tx(struct ithc *ithc, const struct ithc_data *data); diff --git a/drivers/hid/ithc/ithc-hid.c b/drivers/hid/ithc/ithc-hid.c new file mode 100644 index 0000000000000..065646ab499ef --- /dev/null +++ b/drivers/hid/ithc/ithc-hid.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +#include "ithc.h" + +static int ithc_hid_start(struct hid_device *hdev) { return 0; } +static void ithc_hid_stop(struct hid_device *hdev) { } +static int ithc_hid_open(struct hid_device *hdev) { return 0; } +static void ithc_hid_close(struct hid_device *hdev) { } + +static int ithc_hid_parse(struct hid_device *hdev) +{ + struct ithc *ithc = hdev->driver_data; + const struct ithc_data get_report_desc = { .type = ITHC_DATA_REPORT_DESCRIPTOR }; + WRITE_ONCE(ithc->hid.parse_done, false); + for (int retries = 0; ; retries++) { + ithc_log_regs(ithc); + CHECK_RET(ithc_dma_tx, ithc, &get_report_desc); + if (wait_event_timeout(ithc->hid.wait_parse, READ_ONCE(ithc->hid.parse_done), + msecs_to_jiffies(200))) { + ithc_log_regs(ithc); + return 0; + } + if (retries > 5) { + ithc_log_regs(ithc); + pci_err(ithc->pci, "failed to read report descriptor\n"); + return -ETIMEDOUT; + } + pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); + } +} + +static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, + size_t len, unsigned char rtype, int reqtype) +{ + struct ithc *ithc = hdev->driver_data; + if (!buf || !len) + return -EINVAL; + + struct ithc_data d = { .size = len, .data = buf }; + buf[0] = reportnum; + + if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { + d.type = ITHC_DATA_OUTPUT_REPORT; + CHECK_RET(ithc_dma_tx, ithc, &d); + return 0; + } + + if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { + d.type = ITHC_DATA_SET_FEATURE; + CHECK_RET(ithc_dma_tx, ithc, &d); + return 0; + } + + if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { + d.type = ITHC_DATA_GET_FEATURE; + d.data = &reportnum; + d.size = 1; + + // Prepare for response. + mutex_lock(&ithc->hid.get_feature_mutex); + ithc->hid.get_feature_buf = buf; + ithc->hid.get_feature_size = len; + mutex_unlock(&ithc->hid.get_feature_mutex); + + // Transmit 'get feature' request. + int r = CHECK(ithc_dma_tx, ithc, &d); + if (!r) { + r = wait_event_interruptible_timeout(ithc->hid.wait_get_feature, + !ithc->hid.get_feature_buf, msecs_to_jiffies(1000)); + if (!r) + r = -ETIMEDOUT; + else if (r < 0) + r = -EINTR; + else + r = 0; + } + + // If everything went ok, the buffer has been filled with the response data. + // Return the response size. + mutex_lock(&ithc->hid.get_feature_mutex); + ithc->hid.get_feature_buf = NULL; + if (!r) + r = ithc->hid.get_feature_size; + mutex_unlock(&ithc->hid.get_feature_mutex); + return r; + } + + pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", + rtype, reqtype, reportnum); + return -EINVAL; +} + +// FIXME hid_input_report()/hid_parse_report() currently don't take const buffers, so we have to +// cast away the const to avoid a compiler warning... +#define NOCONST(x) ((void *)x) + +void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d) +{ + WARN_ON(!ithc->hid.dev); + if (!ithc->hid.dev) + return; + + switch (d->type) { + + case ITHC_DATA_IGNORE: + return; + + case ITHC_DATA_ERROR: + CHECK(ithc_reset, ithc); + return; + + case ITHC_DATA_REPORT_DESCRIPTOR: + // Response to the report descriptor request sent by ithc_hid_parse(). + CHECK(hid_parse_report, ithc->hid.dev, NOCONST(d->data), d->size); + WRITE_ONCE(ithc->hid.parse_done, true); + wake_up(&ithc->hid.wait_parse); + return; + + case ITHC_DATA_INPUT_REPORT: + { + // Standard HID input report. + int r = hid_input_report(ithc->hid.dev, HID_INPUT_REPORT, NOCONST(d->data), d->size, 1); + if (r < 0) { + pci_warn(ithc->pci, "hid_input_report failed with %i (size %u, report ID 0x%02x)\n", + r, d->size, d->size ? *(u8 *)d->data : 0); + print_hex_dump_debug(DEVNAME " report: ", DUMP_PREFIX_OFFSET, 32, 1, + d->data, min(d->size, 0x400u), 0); + } + return; + } + + case ITHC_DATA_GET_FEATURE: + { + // Response to a 'get feature' request sent by ithc_hid_raw_request(). + bool done = false; + mutex_lock(&ithc->hid.get_feature_mutex); + if (ithc->hid.get_feature_buf) { + if (d->size < ithc->hid.get_feature_size) + ithc->hid.get_feature_size = d->size; + memcpy(ithc->hid.get_feature_buf, d->data, ithc->hid.get_feature_size); + ithc->hid.get_feature_buf = NULL; + done = true; + } + mutex_unlock(&ithc->hid.get_feature_mutex); + if (done) { + wake_up(&ithc->hid.wait_get_feature); + } else { + // Received data without a matching request, or the request already + // timed out. (XXX What's the correct thing to do here?) + CHECK(hid_input_report, ithc->hid.dev, HID_FEATURE_REPORT, + NOCONST(d->data), d->size, 1); + } + return; + } + + default: + pci_err(ithc->pci, "unhandled data type %i\n", d->type); + return; + } +} + +static struct hid_ll_driver ithc_ll_driver = { + .start = ithc_hid_start, + .stop = ithc_hid_stop, + .open = ithc_hid_open, + .close = ithc_hid_close, + .parse = ithc_hid_parse, + .raw_request = ithc_hid_raw_request, +}; + +static void ithc_hid_devres_release(struct device *dev, void *res) +{ + struct hid_device **hidm = res; + if (*hidm) + hid_destroy_device(*hidm); +} + +int ithc_hid_init(struct ithc *ithc) +{ + struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); + if (!hidm) + return -ENOMEM; + devres_add(&ithc->pci->dev, hidm); + struct hid_device *hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + *hidm = hid; + + strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); + strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); + hid->ll_driver = &ithc_ll_driver; + hid->bus = BUS_PCI; + hid->vendor = ithc->vendor_id; + hid->product = ithc->product_id; + hid->version = 0x100; + hid->dev.parent = &ithc->pci->dev; + hid->driver_data = ithc; + + ithc->hid.dev = hid; + + init_waitqueue_head(&ithc->hid.wait_parse); + init_waitqueue_head(&ithc->hid.wait_get_feature); + mutex_init(&ithc->hid.get_feature_mutex); + + return 0; +} + diff --git a/drivers/hid/ithc/ithc-hid.h b/drivers/hid/ithc/ithc-hid.h new file mode 100644 index 0000000000000..599eb912c8c84 --- /dev/null +++ b/drivers/hid/ithc/ithc-hid.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +enum ithc_data_type { + ITHC_DATA_IGNORE, + ITHC_DATA_RAW, + ITHC_DATA_ERROR, + ITHC_DATA_REPORT_DESCRIPTOR, + ITHC_DATA_INPUT_REPORT, + ITHC_DATA_OUTPUT_REPORT, + ITHC_DATA_GET_FEATURE, + ITHC_DATA_SET_FEATURE, +}; + +struct ithc_data { + enum ithc_data_type type; + u32 size; + const void *data; +}; + +struct ithc_hid { + struct hid_device *dev; + bool parse_done; + wait_queue_head_t wait_parse; + wait_queue_head_t wait_get_feature; + struct mutex get_feature_mutex; + void *get_feature_buf; + size_t get_feature_size; +}; + +int ithc_hid_init(struct ithc *ithc); +void ithc_hid_process_data(struct ithc *ithc, struct ithc_data *d); + diff --git a/drivers/hid/ithc/ithc-legacy.c b/drivers/hid/ithc/ithc-legacy.c new file mode 100644 index 0000000000000..5c1da11e3f1d2 --- /dev/null +++ b/drivers/hid/ithc/ithc-legacy.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +#include "ithc.h" + +#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) +#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) + +#define DEVCFG_TOUCH_MASK 0x3f +#define DEVCFG_TOUCH_ENABLE BIT(0) +#define DEVCFG_TOUCH_PROP_DATA_ENABLE BIT(1) +#define DEVCFG_TOUCH_HID_REPORT_ENABLE BIT(2) +#define DEVCFG_TOUCH_POWER_STATE(x) (((x) & 7) << 3) +#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) + +#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" + +#define DEVCFG_SPI_CLKDIV(x) (((x) >> 1) & 7) +#define DEVCFG_SPI_CLKDIV_8 BIT(4) +#define DEVCFG_SPI_SUPPORTS_SINGLE BIT(5) +#define DEVCFG_SPI_SUPPORTS_DUAL BIT(6) +#define DEVCFG_SPI_SUPPORTS_QUAD BIT(7) +#define DEVCFG_SPI_MAX_TOUCH_POINTS(x) (((x) >> 8) & 0x3f) +#define DEVCFG_SPI_MIN_RESET_TIME(x) (((x) >> 16) & 0xf) +#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat +#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) +#define DEVCFG_SPI_UNKNOWN_25 BIT(25) +#define DEVCFG_SPI_UNKNOWN_26 BIT(26) +#define DEVCFG_SPI_UNKNOWN_27 BIT(27) +#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this +#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? + +struct ithc_device_config { // (Example values are from an SP7+.) + u32 irq_cause; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) + u32 error; // 04 = 0x00000000 + u32 dma_buf_sizes; // 08 = 0x000a00ff + u32 touch_cfg; // 0c = 0x0000001c + u32 touch_state; // 10 = 0x0000001c + u32 device_id; // 14 = 0x43495424 = "$TIC" + u32 spi_config; // 18 = 0xfda00a2e + u16 vendor_id; // 1c = 0x045e = Microsoft Corp. + u16 product_id; // 1e = 0x0c1a + u32 revision; // 20 = 0x00000001 + u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) + u32 command; // 28 = 0x00000000 + u32 fw_mode; // 2c = 0x00000000 (for fw update?) + u32 _unknown_30; // 30 = 0x00000000 + u8 eds_minor_ver; // 34 = 0x5e + u8 eds_major_ver; // 35 = 0x03 + u8 interface_rev; // 36 = 0x04 + u8 eu_kernel_ver; // 37 = 0x04 + u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) + u32 _unknown_3c; // 3c = 0x00000002 +}; +static_assert(sizeof(struct ithc_device_config) == 64); + +#define RX_CODE_INPUT_REPORT 3 +#define RX_CODE_FEATURE_REPORT 4 +#define RX_CODE_REPORT_DESCRIPTOR 5 +#define RX_CODE_RESET 7 + +#define TX_CODE_SET_FEATURE 3 +#define TX_CODE_GET_FEATURE 4 +#define TX_CODE_OUTPUT_REPORT 5 +#define TX_CODE_GET_REPORT_DESCRIPTOR 7 + +static int ithc_set_device_enabled(struct ithc *ithc, bool enable) +{ + u32 x = ithc->legacy_touch_cfg = + (ithc->legacy_touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | + DEVCFG_TOUCH_HID_REPORT_ENABLE | + (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_POWER_STATE(3) : 0); + return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, + offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); +} + +int ithc_legacy_init(struct ithc *ithc) +{ + // Since we don't yet know which SPI config the device wants, use default speed and mode + // initially for reading config data. + CHECK(ithc_set_spi_config, ithc, 2, true, SPI_MODE_SINGLE, SPI_MODE_SINGLE); + + // Setting the following bit seems to make reading the config more reliable. + bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); + + // Setting this bit may be necessary on some ADL devices. + switch (ithc->pci->device) { + case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1: + case PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2: + case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1: + case PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2: + bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_5); + break; + } + + // Take the touch device out of reset. + bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); + CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); + for (int retries = 0; ; retries++) { + ithc_log_regs(ithc); + bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); + if (!waitl(ithc, &ithc->regs->irq_cause, 0xf, 2)) + break; + if (retries > 5) { + pci_err(ithc->pci, "failed to reset device, irq_cause = 0x%08x\n", + readl(&ithc->regs->irq_cause)); + return -ETIMEDOUT; + } + pci_warn(ithc->pci, "invalid irq_cause, retrying reset\n"); + bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); + if (msleep_interruptible(1000)) + return -EINTR; + } + ithc_log_regs(ithc); + + CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); + + // Read configuration data. + u32 spi_cfg; + for (int retries = 0; ; retries++) { + ithc_log_regs(ithc); + struct ithc_device_config config = { 0 }; + CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(config), &config); + u32 *p = (void *)&config; + pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); + if (config.device_id == DEVCFG_DEVICE_ID_TIC) { + spi_cfg = config.spi_config; + ithc->vendor_id = config.vendor_id; + ithc->product_id = config.product_id; + ithc->product_rev = config.revision; + ithc->max_rx_size = DEVCFG_DMA_RX_SIZE(config.dma_buf_sizes); + ithc->max_tx_size = DEVCFG_DMA_TX_SIZE(config.dma_buf_sizes); + ithc->legacy_touch_cfg = config.touch_cfg; + ithc->have_config = true; + break; + } + if (retries > 10) { + pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", + config.device_id); + return -EIO; + } + pci_warn(ithc->pci, "failed to read config, retrying\n"); + if (msleep_interruptible(100)) + return -EINTR; + } + ithc_log_regs(ithc); + + // Apply SPI config and enable touch device. + CHECK_RET(ithc_set_spi_config, ithc, + DEVCFG_SPI_CLKDIV(spi_cfg), (spi_cfg & DEVCFG_SPI_CLKDIV_8) != 0, + spi_cfg & DEVCFG_SPI_SUPPORTS_QUAD ? SPI_MODE_QUAD : + spi_cfg & DEVCFG_SPI_SUPPORTS_DUAL ? SPI_MODE_DUAL : + SPI_MODE_SINGLE, + SPI_MODE_SINGLE); + CHECK_RET(ithc_set_device_enabled, ithc, true); + ithc_log_regs(ithc); + return 0; +} + +void ithc_legacy_exit(struct ithc *ithc) +{ + CHECK(ithc_set_device_enabled, ithc, false); +} + +int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) +{ + const struct { + u32 code; + u32 data_size; + u32 _unknown[14]; + } *hdr = src; + + if (len < sizeof(*hdr)) + return -ENODATA; + // Note: RX data is not padded, even though TX data must be padded. + if (len != sizeof(*hdr) + hdr->data_size) + return -EMSGSIZE; + + dest->data = hdr + 1; + dest->size = hdr->data_size; + + switch (hdr->code) { + case RX_CODE_RESET: + // The THC sends a reset request when we need to reinitialize the device. + // This usually only happens if we send an invalid command or put the device + // in a bad state. + dest->type = ITHC_DATA_ERROR; + return 0; + case RX_CODE_REPORT_DESCRIPTOR: + // The descriptor is preceded by 8 nul bytes. + if (hdr->data_size < 8) + return -ENODATA; + dest->type = ITHC_DATA_REPORT_DESCRIPTOR; + dest->data = (char *)(hdr + 1) + 8; + dest->size = hdr->data_size - 8; + return 0; + case RX_CODE_INPUT_REPORT: + dest->type = ITHC_DATA_INPUT_REPORT; + return 0; + case RX_CODE_FEATURE_REPORT: + dest->type = ITHC_DATA_GET_FEATURE; + return 0; + default: + return -EINVAL; + } +} + +ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, + size_t maxlen) +{ + struct { + u32 code; + u32 data_size; + } *hdr = dest; + + size_t src_size = src->size; + const void *src_data = src->data; + const u64 get_report_desc_data = 0; + u32 code; + + switch (src->type) { + case ITHC_DATA_SET_FEATURE: + code = TX_CODE_SET_FEATURE; + break; + case ITHC_DATA_GET_FEATURE: + code = TX_CODE_GET_FEATURE; + break; + case ITHC_DATA_OUTPUT_REPORT: + code = TX_CODE_OUTPUT_REPORT; + break; + case ITHC_DATA_REPORT_DESCRIPTOR: + code = TX_CODE_GET_REPORT_DESCRIPTOR; + src_size = sizeof(get_report_desc_data); + src_data = &get_report_desc_data; + break; + default: + return -EINVAL; + } + + // Data must be padded to next 4-byte boundary. + size_t padded = round_up(src_size, 4); + if (sizeof(*hdr) + padded > maxlen) + return -EOVERFLOW; + + // Fill the TX buffer with header and data. + hdr->code = code; + hdr->data_size = src_size; + memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); + + return sizeof(*hdr) + padded; +} + diff --git a/drivers/hid/ithc/ithc-legacy.h b/drivers/hid/ithc/ithc-legacy.h new file mode 100644 index 0000000000000..28d6924620722 --- /dev/null +++ b/drivers/hid/ithc/ithc-legacy.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +int ithc_legacy_init(struct ithc *ithc); +void ithc_legacy_exit(struct ithc *ithc); +int ithc_legacy_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); +ssize_t ithc_legacy_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, + size_t maxlen); + diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c index 87ed4aa70fda0..2acf02e41d40f 100644 --- a/drivers/hid/ithc/ithc-main.c +++ b/drivers/hid/ithc/ithc-main.c @@ -5,28 +5,6 @@ MODULE_DESCRIPTION("Intel Touch Host Controller driver"); MODULE_LICENSE("Dual BSD/GPL"); -// Lakefield -#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 -#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 -// Tiger Lake -#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 -#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 -#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 -#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 -// Alder Lake -#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 -#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 -#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 -#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 -#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 -#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 -// Raptor Lake -#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 -#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 -// Meteor Lake -#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT1 0x7e48 -#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT2 0x7e4a - static const struct pci_device_id ithc_pci_tbl[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT1) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_LKF_PORT2) }, @@ -66,15 +44,13 @@ static bool ithc_use_rx1 = true; module_param_named(rx1, ithc_use_rx1, bool, 0); MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); -// Values below 250 seem to work well on the SP7+. If this is set too high, you may observe cursor stuttering. -static int ithc_dma_latency_us = 200; -module_param_named(dma_latency_us, ithc_dma_latency_us, int, 0); -MODULE_PARM_DESC(dma_latency_us, "Determines the CPU latency QoS value for DMA transfers (in microseconds), -1 to disable latency QoS"); +static int ithc_active_ltr_us = -1; +module_param_named(activeltr, ithc_active_ltr_us, int, 0); +MODULE_PARM_DESC(activeltr, "Active LTR value override (in microseconds)"); -// Values above 1700 seem to work well on the SP7+. If this is set too low, you may observe cursor stuttering. -static unsigned int ithc_dma_early_us = 2000; -module_param_named(dma_early_us, ithc_dma_early_us, uint, 0); -MODULE_PARM_DESC(dma_early_us, "Determines how early the CPU latency QoS value is applied before the next expected IRQ (in microseconds)"); +static int ithc_idle_ltr_us = -1; +module_param_named(idleltr, ithc_idle_ltr_us, int, 0); +MODULE_PARM_DESC(idleltr, "Idle LTR value override (in microseconds)"); static bool ithc_log_regs_enabled = false; module_param_named(logregs, ithc_log_regs_enabled, bool, 0); @@ -82,44 +58,30 @@ MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); // Sysfs attributes -static bool ithc_is_config_valid(struct ithc *ithc) -{ - return ithc->config.device_id == DEVCFG_DEVICE_ID_TIC; -} - static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ithc *ithc = dev_get_drvdata(dev); - if (!ithc || !ithc_is_config_valid(ithc)) + if (!ithc || !ithc->have_config) return -ENODEV; - return sprintf(buf, "0x%04x", ithc->config.vendor_id); + return sprintf(buf, "0x%04x", ithc->vendor_id); } static DEVICE_ATTR_RO(vendor); static ssize_t product_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ithc *ithc = dev_get_drvdata(dev); - if (!ithc || !ithc_is_config_valid(ithc)) + if (!ithc || !ithc->have_config) return -ENODEV; - return sprintf(buf, "0x%04x", ithc->config.product_id); + return sprintf(buf, "0x%04x", ithc->product_id); } static DEVICE_ATTR_RO(product); static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ithc *ithc = dev_get_drvdata(dev); - if (!ithc || !ithc_is_config_valid(ithc)) + if (!ithc || !ithc->have_config) return -ENODEV; - return sprintf(buf, "%u", ithc->config.revision); + return sprintf(buf, "%u", ithc->product_rev); } static DEVICE_ATTR_RO(revision); -static ssize_t fw_version_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct ithc *ithc = dev_get_drvdata(dev); - if (!ithc || !ithc_is_config_valid(ithc)) - return -ENODEV; - u32 v = ithc->config.fw_version; - return sprintf(buf, "%i.%i.%i.%i", v >> 24, v >> 16 & 0xff, v >> 8 & 0xff, v & 0xff); -} -static DEVICE_ATTR_RO(fw_version); static const struct attribute_group *ithc_attribute_groups[] = { &(const struct attribute_group){ @@ -128,185 +90,26 @@ static const struct attribute_group *ithc_attribute_groups[] = { &dev_attr_vendor.attr, &dev_attr_product.attr, &dev_attr_revision.attr, - &dev_attr_fw_version.attr, NULL }, }, NULL }; -// HID setup - -static int ithc_hid_start(struct hid_device *hdev) { return 0; } -static void ithc_hid_stop(struct hid_device *hdev) { } -static int ithc_hid_open(struct hid_device *hdev) { return 0; } -static void ithc_hid_close(struct hid_device *hdev) { } - -static int ithc_hid_parse(struct hid_device *hdev) -{ - struct ithc *ithc = hdev->driver_data; - u64 val = 0; - WRITE_ONCE(ithc->hid_parse_done, false); - for (int retries = 0; ; retries++) { - CHECK_RET(ithc_dma_tx, ithc, DMA_TX_CODE_GET_REPORT_DESCRIPTOR, sizeof(val), &val); - if (wait_event_timeout(ithc->wait_hid_parse, READ_ONCE(ithc->hid_parse_done), - msecs_to_jiffies(200))) - return 0; - if (retries > 5) { - pci_err(ithc->pci, "failed to read report descriptor\n"); - return -ETIMEDOUT; - } - pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); - } -} - -static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, - size_t len, unsigned char rtype, int reqtype) -{ - struct ithc *ithc = hdev->driver_data; - if (!buf || !len) - return -EINVAL; - u32 code; - if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { - code = DMA_TX_CODE_OUTPUT_REPORT; - } else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { - code = DMA_TX_CODE_SET_FEATURE; - } else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { - code = DMA_TX_CODE_GET_FEATURE; - } else { - pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", - rtype, reqtype, reportnum); - return -EINVAL; - } - buf[0] = reportnum; - - if (reqtype == HID_REQ_GET_REPORT) { - // Prepare for response. - mutex_lock(&ithc->hid_get_feature_mutex); - ithc->hid_get_feature_buf = buf; - ithc->hid_get_feature_size = len; - mutex_unlock(&ithc->hid_get_feature_mutex); - - // Transmit 'get feature' request. - int r = CHECK(ithc_dma_tx, ithc, code, 1, buf); - if (!r) { - r = wait_event_interruptible_timeout(ithc->wait_hid_get_feature, - !ithc->hid_get_feature_buf, msecs_to_jiffies(1000)); - if (!r) - r = -ETIMEDOUT; - else if (r < 0) - r = -EINTR; - else - r = 0; - } - - // If everything went ok, the buffer has been filled with the response data. - // Return the response size. - mutex_lock(&ithc->hid_get_feature_mutex); - ithc->hid_get_feature_buf = NULL; - if (!r) - r = ithc->hid_get_feature_size; - mutex_unlock(&ithc->hid_get_feature_mutex); - return r; - } - - // 'Set feature', or 'output report'. These don't have a response. - CHECK_RET(ithc_dma_tx, ithc, code, len, buf); - return 0; -} - -static struct hid_ll_driver ithc_ll_driver = { - .start = ithc_hid_start, - .stop = ithc_hid_stop, - .open = ithc_hid_open, - .close = ithc_hid_close, - .parse = ithc_hid_parse, - .raw_request = ithc_hid_raw_request, -}; - -static void ithc_hid_devres_release(struct device *dev, void *res) -{ - struct hid_device **hidm = res; - if (*hidm) - hid_destroy_device(*hidm); -} - -static int ithc_hid_init(struct ithc *ithc) -{ - struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); - if (!hidm) - return -ENOMEM; - devres_add(&ithc->pci->dev, hidm); - struct hid_device *hid = hid_allocate_device(); - if (IS_ERR(hid)) - return PTR_ERR(hid); - *hidm = hid; - - strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); - strscpy(hid->phys, ithc->phys, sizeof(hid->phys)); - hid->ll_driver = &ithc_ll_driver; - hid->bus = BUS_PCI; - hid->vendor = ithc->config.vendor_id; - hid->product = ithc->config.product_id; - hid->version = 0x100; - hid->dev.parent = &ithc->pci->dev; - hid->driver_data = ithc; - - ithc->hid = hid; - return 0; -} - // Interrupts/polling -static enum hrtimer_restart ithc_activity_start_timer_callback(struct hrtimer *t) -{ - struct ithc *ithc = container_of(t, struct ithc, activity_start_timer); - ithc_set_active(ithc, ithc_dma_early_us * 2 + USEC_PER_MSEC); - return HRTIMER_NORESTART; -} - -static enum hrtimer_restart ithc_activity_end_timer_callback(struct hrtimer *t) -{ - struct ithc *ithc = container_of(t, struct ithc, activity_end_timer); - cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); - return HRTIMER_NORESTART; -} - -void ithc_set_active(struct ithc *ithc, unsigned int duration_us) -{ - if (ithc_dma_latency_us < 0) - return; - // When CPU usage is very low, the CPU can enter various low power states (C2-C10). - // This disrupts DMA, causing truncated DMA messages. ERROR_FLAG_DMA_RX_TIMEOUT will be - // set when this happens. The amount of truncated messages can become very high, resulting - // in user-visible effects (laggy/stuttering cursor). To avoid this, we use a CPU latency - // QoS request to prevent the CPU from entering low power states during touch interactions. - cpu_latency_qos_update_request(&ithc->activity_qos, ithc_dma_latency_us); - hrtimer_start_range_ns(&ithc->activity_end_timer, - ns_to_ktime(duration_us * NSEC_PER_USEC), duration_us * NSEC_PER_USEC, HRTIMER_MODE_REL); -} - -static int ithc_set_device_enabled(struct ithc *ithc, bool enable) -{ - u32 x = ithc->config.touch_cfg = - (ithc->config.touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | DEVCFG_TOUCH_UNKNOWN_2 | - (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_UNKNOWN_3 | DEVCFG_TOUCH_UNKNOWN_4 : 0); - return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, - offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); -} - static void ithc_disable_interrupts(struct ithc *ithc) { writel(0, &ithc->regs->error_control); bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); - bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); - bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); + bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); + bitsb(&ithc->regs->dma_rx[1].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_READY | DMA_RX_CONTROL_IRQ_DATA, 0); bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); } static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) { - writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_UNKNOWN_4 | DMA_RX_STATUS_HAVE_DATA, + writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_READY | DMA_RX_STATUS_HAVE_DATA, &ithc->regs->dma_rx[channel].status); } @@ -325,39 +128,22 @@ static void ithc_process(struct ithc *ithc) { ithc_log_regs(ithc); + // The THC automatically transitions from LTR idle to active at the start of a DMA transfer. + // It does not appear to automatically go back to idle, so we switch it back here, since + // the DMA transfer should be complete. + ithc_set_ltr_idle(ithc); + bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; - // Track time between DMA rx transfers, so we can try to predict when we need to enable CPU latency QoS for the next transfer - ktime_t t = ktime_get(); - ktime_t dt = ktime_sub(t, ithc->last_rx_time); - if (rx0 || rx1) { - ithc->last_rx_time = t; - if (dt > ms_to_ktime(100)) { - ithc->cur_rx_seq_count = 0; - ithc->cur_rx_seq_errors = 0; - } - ithc->cur_rx_seq_count++; - if (!ithc_use_polling && ithc_dma_latency_us >= 0) { - // Disable QoS, since the DMA transfer has completed (we re-enable it after a delay below) - cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); - hrtimer_try_to_cancel(&ithc->activity_end_timer); - } - } - // Read and clear error bits u32 err = readl(&ithc->regs->error_flags); if (err) { writel(err, &ithc->regs->error_flags); if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) pci_err(ithc->pci, "error flags: 0x%08x\n", err); - if (err & ERROR_FLAG_DMA_RX_TIMEOUT) { - // Only log an error if we see a significant number of these errors. - ithc->cur_rx_seq_errors++; - if (ithc->cur_rx_seq_errors && ithc->cur_rx_seq_errors % 50 == 0 && ithc->cur_rx_seq_errors > ithc->cur_rx_seq_count / 10) - pci_err(ithc->pci, "High number of DMA RX timeouts/errors (%u/%u, dt=%lldus). Try adjusting dma_early_us and/or dma_latency_us.\n", - ithc->cur_rx_seq_errors, ithc->cur_rx_seq_count, ktime_to_us(dt)); - } + if (err & ERROR_FLAG_DMA_RX_TIMEOUT) + pci_err(ithc->pci, "DMA RX timeout/error (try decreasing activeltr/idleltr if this happens frequently)\n"); } // Process DMA rx @@ -372,12 +158,6 @@ static void ithc_process(struct ithc *ithc) ithc_dma_rx(ithc, 1); } - // Start timer to re-enable QoS for next rx, but only if we've seen an ERROR_FLAG_DMA_RX_TIMEOUT - if ((rx0 || rx1) && !ithc_use_polling && ithc_dma_latency_us >= 0 && ithc->cur_rx_seq_errors > 0) { - ktime_t expires = ktime_add(t, ktime_sub_us(dt, ithc_dma_early_us)); - hrtimer_start_range_ns(&ithc->activity_start_timer, expires, 10 * NSEC_PER_USEC, HRTIMER_MODE_ABS); - } - ithc_log_regs(ithc); } @@ -403,12 +183,8 @@ static int ithc_poll_thread(void *arg) ithc_process(ithc); // Decrease polling interval to 20ms if we received data, otherwise slowly // increase it up to 200ms. - if (n != ithc->dma_rx[1].num_received) { - ithc_set_active(ithc, 100 * USEC_PER_MSEC); - sleep = 20; - } else { - sleep = min(200u, sleep + (sleep >> 4) + 1); - } + sleep = n != ithc->dma_rx[1].num_received ? 20 + : min(200u, sleep + (sleep >> 4) + 1); msleep_interruptible(sleep); } return 0; @@ -431,73 +207,44 @@ static void ithc_disable(struct ithc *ithc) static int ithc_init_device(struct ithc *ithc) { + // Read ACPI config for QuickSPI mode + struct ithc_acpi_config cfg = { 0 }; + CHECK_RET(ithc_read_acpi_config, ithc, &cfg); + if (!cfg.has_config) + pci_info(ithc->pci, "no ACPI config, using legacy mode\n"); + else + ithc_print_acpi_config(ithc, &cfg); + ithc->use_quickspi = cfg.has_config; + + // Shut down device ithc_log_regs(ithc); bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; ithc_disable(ithc); CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); - - // Since we don't yet know which SPI config the device wants, use default speed and mode - // initially for reading config data. - ithc_set_spi_config(ithc, 10, 0); - - // Setting the following bit seems to make reading the config more reliable. - bitsl_set(&ithc->regs->dma_rx[0].unknown_init_bits, 0x80000000); + ithc_log_regs(ithc); // If the device was previously enabled, wait a bit to make sure it's fully shut down. if (was_enabled) if (msleep_interruptible(100)) return -EINTR; - // Take the touch device out of reset. - bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); - CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); - for (int retries = 0; ; retries++) { - ithc_log_regs(ithc); - bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); - if (!waitl(ithc, &ithc->regs->state, 0xf, 2)) - break; - if (retries > 5) { - pci_err(ithc->pci, "failed to reset device, state = 0x%08x\n", readl(&ithc->regs->state)); - return -ETIMEDOUT; - } - pci_warn(ithc->pci, "invalid state, retrying reset\n"); - bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); - if (msleep_interruptible(1000)) - return -EINTR; - } - ithc_log_regs(ithc); + // Set Latency Tolerance Reporting config. The device will automatically + // apply these values depending on whether it is active or idle. + // If active value is too high, DMA buffer data can become truncated. + // By default, we set the active LTR value to 100us, and idle to 100ms. + u64 active_ltr_ns = ithc_active_ltr_us >= 0 ? (u64)ithc_active_ltr_us * 1000 + : cfg.has_config && cfg.has_active_ltr ? (u64)cfg.active_ltr << 10 + : 100 * 1000; + u64 idle_ltr_ns = ithc_idle_ltr_us >= 0 ? (u64)ithc_idle_ltr_us * 1000 + : cfg.has_config && cfg.has_idle_ltr ? (u64)cfg.idle_ltr << 10 + : 100 * 1000 * 1000; + ithc_set_ltr_config(ithc, active_ltr_ns, idle_ltr_ns); + + if (ithc->use_quickspi) + CHECK_RET(ithc_quickspi_init, ithc, &cfg); + else + CHECK_RET(ithc_legacy_init, ithc); - // Waiting for the following status bit makes reading config much more reliable, - // however the official driver does not seem to do this... - CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_UNKNOWN_4, DMA_RX_STATUS_UNKNOWN_4); - - // Read configuration data. - for (int retries = 0; ; retries++) { - ithc_log_regs(ithc); - memset(&ithc->config, 0, sizeof(ithc->config)); - CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(ithc->config), &ithc->config); - u32 *p = (void *)&ithc->config; - pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", - p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); - if (ithc_is_config_valid(ithc)) - break; - if (retries > 10) { - pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", - ithc->config.device_id); - return -EIO; - } - pci_warn(ithc->pci, "failed to read config, retrying\n"); - if (msleep_interruptible(100)) - return -EINTR; - } - ithc_log_regs(ithc); - - // Apply SPI config and enable touch device. - CHECK_RET(ithc_set_spi_config, ithc, - DEVCFG_SPI_MAX_FREQ(ithc->config.spi_config), - DEVCFG_SPI_MODE(ithc->config.spi_config)); - CHECK_RET(ithc_set_device_enabled, ithc, true); - ithc_log_regs(ithc); return 0; } @@ -527,11 +274,11 @@ static void ithc_stop(void *res) CHECK(kthread_stop, ithc->poll_thread); if (ithc->irq >= 0) disable_irq(ithc->irq); - CHECK(ithc_set_device_enabled, ithc, false); + if (ithc->use_quickspi) + ithc_quickspi_exit(ithc); + else + ithc_legacy_exit(ithc); ithc_disable(ithc); - hrtimer_cancel(&ithc->activity_start_timer); - hrtimer_cancel(&ithc->activity_end_timer); - cpu_latency_qos_remove_request(&ithc->activity_qos); // Clear DMA config. for (unsigned int i = 0; i < 2; i++) { @@ -570,9 +317,6 @@ static int ithc_start(struct pci_dev *pci) ithc->irq = -1; ithc->pci = pci; snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); - init_waitqueue_head(&ithc->wait_hid_parse); - init_waitqueue_head(&ithc->wait_hid_get_feature); - mutex_init(&ithc->hid_get_feature_mutex); pci_set_drvdata(pci, ithc); CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); if (ithc_log_regs_enabled) @@ -596,6 +340,9 @@ static int ithc_start(struct pci_dev *pci) // Initialize THC and touch device. CHECK_RET(ithc_init_device, ithc); + + // Initialize HID and DMA. + CHECK_RET(ithc_hid_init, ithc); CHECK(devm_device_add_groups, &pci->dev, ithc_attribute_groups); if (ithc_use_rx0) CHECK_RET(ithc_dma_rx_init, ithc, 0); @@ -603,18 +350,10 @@ static int ithc_start(struct pci_dev *pci) CHECK_RET(ithc_dma_rx_init, ithc, 1); CHECK_RET(ithc_dma_tx_init, ithc); - cpu_latency_qos_add_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); - hrtimer_init(&ithc->activity_start_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); - ithc->activity_start_timer.function = ithc_activity_start_timer_callback; - hrtimer_init(&ithc->activity_end_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - ithc->activity_end_timer.function = ithc_activity_end_timer_callback; - // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are // disabled BEFORE the buffers are freed. CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); - CHECK_RET(ithc_hid_init, ithc); - // Start polling/IRQ. if (ithc_use_polling) { pci_info(pci, "using polling instead of irq\n"); @@ -637,9 +376,11 @@ static int ithc_start(struct pci_dev *pci) // hid_add_device() can only be called after irq/polling is started and DMA is enabled, // because it calls ithc_hid_parse() which reads the report descriptor via DMA. - CHECK_RET(hid_add_device, ithc->hid); + CHECK_RET(hid_add_device, ithc->hid.dev); + + CHECK(ithc_debug_init_device, ithc); - CHECK(ithc_debug_init, ithc); + ithc_set_ltr_idle(ithc); pci_dbg(pci, "started\n"); return 0; @@ -710,17 +451,20 @@ static struct pci_driver ithc_driver = { .thaw = ithc_thaw, .restore = ithc_restore, }, + .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, //.dev_groups = ithc_attribute_groups, // could use this (since 5.14), however the attributes won't have valid values until config has been read anyway }; static int __init ithc_init(void) { + ithc_debug_init_module(); return pci_register_driver(&ithc_driver); } static void __exit ithc_exit(void) { pci_unregister_driver(&ithc_driver); + ithc_debug_exit_module(); } module_init(ithc_init); diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c new file mode 100644 index 0000000000000..760e55ead0788 --- /dev/null +++ b/drivers/hid/ithc/ithc-quickspi.c @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause + +// Some public THC/QuickSPI documentation can be found in: +// - Intel Firmware Support Package repo: https://github.com/intel/FSP +// - HID over SPI (HIDSPI) spec: https://www.microsoft.com/en-us/download/details.aspx?id=103325 + +#include "ithc.h" + +static const guid_t guid_hidspi = + GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a, 0x22, 0x0d, 0xcf, 0xab); +static const guid_t guid_thc_quickspi = + GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4, 0xda, 0xfd, 0x0a, 0xfe); +static const guid_t guid_thc_ltr = + GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38); + +// TODO The HIDSPI spec says revision should be 3. Should we try both? +#define DSM_REV 2 + +struct hidspi_header { + u8 type; + u16 len; + u8 id; +} __packed; +static_assert(sizeof(struct hidspi_header) == 4); + +#define HIDSPI_INPUT_TYPE_DATA 1 +#define HIDSPI_INPUT_TYPE_RESET_RESPONSE 3 +#define HIDSPI_INPUT_TYPE_COMMAND_RESPONSE 4 +#define HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE 5 +#define HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR 7 +#define HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR 8 +#define HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE 9 +#define HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE 10 +#define HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE 11 + +#define HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST 1 +#define HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST 2 +#define HIDSPI_OUTPUT_TYPE_SET_FEATURE 3 +#define HIDSPI_OUTPUT_TYPE_GET_FEATURE 4 +#define HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT 5 +#define HIDSPI_OUTPUT_TYPE_INPUT_REPORT_REQUEST 6 +#define HIDSPI_OUTPUT_TYPE_COMMAND 7 + +struct hidspi_device_descriptor { + u16 wDeviceDescLength; + u16 bcdVersion; + u16 wReportDescLength; + u16 wMaxInputLength; + u16 wMaxOutputLength; + u16 wMaxFragmentLength; + u16 wVendorID; + u16 wProductID; + u16 wVersionID; + u16 wFlags; + u32 dwReserved; +}; +static_assert(sizeof(struct hidspi_device_descriptor) == 24); + +static int read_acpi_u32(struct ithc *ithc, const guid_t *guid, u32 func, u32 *dest) +{ + acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); + union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); + if (!o) + return 0; + if (o->type != ACPI_TYPE_INTEGER) { + pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of integer\n", + guid, func, o->type); + ACPI_FREE(o); + return -1; + } + pci_dbg(ithc->pci, "DSM %pUl %u = 0x%08x\n", guid, func, (u32)o->integer.value); + *dest = (u32)o->integer.value; + ACPI_FREE(o); + return 1; +} + +static int read_acpi_buf(struct ithc *ithc, const guid_t *guid, u32 func, size_t len, u8 *dest) +{ + acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); + union acpi_object *o = acpi_evaluate_dsm(handle, guid, DSM_REV, func, NULL); + if (!o) + return 0; + if (o->type != ACPI_TYPE_BUFFER) { + pci_err(ithc->pci, "DSM %pUl %u returned type %i instead of buffer\n", + guid, func, o->type); + ACPI_FREE(o); + return -1; + } + if (o->buffer.length != len) { + pci_err(ithc->pci, "DSM %pUl %u returned len %u instead of %zu\n", + guid, func, o->buffer.length, len); + ACPI_FREE(o); + return -1; + } + memcpy(dest, o->buffer.pointer, len); + pci_dbg(ithc->pci, "DSM %pUl %u = 0x%02x\n", guid, func, dest[0]); + ACPI_FREE(o); + return 1; +} + +int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg) +{ + int r; + acpi_handle handle = ACPI_HANDLE(&ithc->pci->dev); + + cfg->has_config = acpi_check_dsm(handle, &guid_hidspi, DSM_REV, BIT(0)); + if (!cfg->has_config) + return 0; + + // HIDSPI settings + + r = read_acpi_u32(ithc, &guid_hidspi, 1, &cfg->input_report_header_address); + if (r < 0) + return r; + cfg->has_input_report_header_address = r > 0; + if (r > 0 && cfg->input_report_header_address > 0xffffff) { + pci_err(ithc->pci, "Invalid input report header address 0x%x\n", + cfg->input_report_header_address); + return -1; + } + + r = read_acpi_u32(ithc, &guid_hidspi, 2, &cfg->input_report_body_address); + if (r < 0) + return r; + cfg->has_input_report_body_address = r > 0; + if (r > 0 && cfg->input_report_body_address > 0xffffff) { + pci_err(ithc->pci, "Invalid input report body address 0x%x\n", + cfg->input_report_body_address); + return -1; + } + + r = read_acpi_u32(ithc, &guid_hidspi, 3, &cfg->output_report_body_address); + if (r < 0) + return r; + cfg->has_output_report_body_address = r > 0; + if (r > 0 && cfg->output_report_body_address > 0xffffff) { + pci_err(ithc->pci, "Invalid output report body address 0x%x\n", + cfg->output_report_body_address); + return -1; + } + + r = read_acpi_buf(ithc, &guid_hidspi, 4, sizeof(cfg->read_opcode), &cfg->read_opcode); + if (r < 0) + return r; + cfg->has_read_opcode = r > 0; + + r = read_acpi_buf(ithc, &guid_hidspi, 5, sizeof(cfg->write_opcode), &cfg->write_opcode); + if (r < 0) + return r; + cfg->has_write_opcode = r > 0; + + u32 flags; + r = read_acpi_u32(ithc, &guid_hidspi, 6, &flags); + if (r < 0) + return r; + cfg->has_read_mode = cfg->has_write_mode = r > 0; + if (r > 0) { + cfg->read_mode = (flags >> 14) & 3; + cfg->write_mode = flags & BIT(13) ? cfg->read_mode : SPI_MODE_SINGLE; + } + + // Quick SPI settings + + r = read_acpi_u32(ithc, &guid_thc_quickspi, 1, &cfg->spi_frequency); + if (r < 0) + return r; + cfg->has_spi_frequency = r > 0; + + r = read_acpi_u32(ithc, &guid_thc_quickspi, 2, &cfg->limit_packet_size); + if (r < 0) + return r; + cfg->has_limit_packet_size = r > 0; + + r = read_acpi_u32(ithc, &guid_thc_quickspi, 3, &cfg->tx_delay); + if (r < 0) + return r; + cfg->has_tx_delay = r > 0; + if (r > 0) + cfg->tx_delay &= 0xffff; + + // LTR settings + + r = read_acpi_u32(ithc, &guid_thc_ltr, 1, &cfg->active_ltr); + if (r < 0) + return r; + cfg->has_active_ltr = r > 0; + if (r > 0 && (!cfg->active_ltr || cfg->active_ltr > 0x3ff)) { + if (cfg->active_ltr != 0xffffffff) + pci_warn(ithc->pci, "Ignoring invalid active LTR value 0x%x\n", + cfg->active_ltr); + cfg->active_ltr = 500; + } + + r = read_acpi_u32(ithc, &guid_thc_ltr, 2, &cfg->idle_ltr); + if (r < 0) + return r; + cfg->has_idle_ltr = r > 0; + if (r > 0 && (!cfg->idle_ltr || cfg->idle_ltr > 0x3ff)) { + if (cfg->idle_ltr != 0xffffffff) + pci_warn(ithc->pci, "Ignoring invalid idle LTR value 0x%x\n", + cfg->idle_ltr); + cfg->idle_ltr = 500; + if (cfg->has_active_ltr && cfg->active_ltr > cfg->idle_ltr) + cfg->idle_ltr = cfg->active_ltr; + } + + return 0; +} + +void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg) +{ + if (!cfg->has_config) { + pci_info(ithc->pci, "No ACPI config"); + return; + } + + char input_report_header_address[16] = "-"; + if (cfg->has_input_report_header_address) + sprintf(input_report_header_address, "0x%x", cfg->input_report_header_address); + char input_report_body_address[16] = "-"; + if (cfg->has_input_report_body_address) + sprintf(input_report_body_address, "0x%x", cfg->input_report_body_address); + char output_report_body_address[16] = "-"; + if (cfg->has_output_report_body_address) + sprintf(output_report_body_address, "0x%x", cfg->output_report_body_address); + char read_opcode[16] = "-"; + if (cfg->has_read_opcode) + sprintf(read_opcode, "0x%02x", cfg->read_opcode); + char write_opcode[16] = "-"; + if (cfg->has_write_opcode) + sprintf(write_opcode, "0x%02x", cfg->write_opcode); + char read_mode[16] = "-"; + if (cfg->has_read_mode) + sprintf(read_mode, "%i", cfg->read_mode); + char write_mode[16] = "-"; + if (cfg->has_write_mode) + sprintf(write_mode, "%i", cfg->write_mode); + char spi_frequency[16] = "-"; + if (cfg->has_spi_frequency) + sprintf(spi_frequency, "%u", cfg->spi_frequency); + char limit_packet_size[16] = "-"; + if (cfg->has_limit_packet_size) + sprintf(limit_packet_size, "%u", cfg->limit_packet_size); + char tx_delay[16] = "-"; + if (cfg->has_tx_delay) + sprintf(tx_delay, "%u", cfg->tx_delay); + char active_ltr[16] = "-"; + if (cfg->has_active_ltr) + sprintf(active_ltr, "%u", cfg->active_ltr); + char idle_ltr[16] = "-"; + if (cfg->has_idle_ltr) + sprintf(idle_ltr, "%u", cfg->idle_ltr); + + pci_info(ithc->pci, "ACPI config: InputHeaderAddr=%s InputBodyAddr=%s OutputBodyAddr=%s ReadOpcode=%s WriteOpcode=%s ReadMode=%s WriteMode=%s Frequency=%s LimitPacketSize=%s TxDelay=%s ActiveLTR=%s IdleLTR=%s\n", + input_report_header_address, input_report_body_address, output_report_body_address, + read_opcode, write_opcode, read_mode, write_mode, + spi_frequency, limit_packet_size, tx_delay, active_ltr, idle_ltr); +} + +static int ithc_quickspi_init_regs(struct ithc *ithc, const struct ithc_acpi_config *cfg) +{ + pci_dbg(ithc->pci, "initializing QuickSPI registers\n"); + + // SPI frequency and mode + if (!cfg->has_spi_frequency || !cfg->spi_frequency) { + pci_err(ithc->pci, "Missing SPI frequency in configuration\n"); + return -EINVAL; + } + unsigned int clkdiv = DIV_ROUND_UP(SPI_CLK_FREQ_BASE, cfg->spi_frequency); + bool clkdiv8 = clkdiv > 7; + if (clkdiv8) + clkdiv = min(7u, DIV_ROUND_UP(clkdiv, 8u)); + if (!clkdiv) + clkdiv = 1; + CHECK_RET(ithc_set_spi_config, ithc, clkdiv, clkdiv8, + cfg->has_read_mode ? cfg->read_mode : SPI_MODE_SINGLE, + cfg->has_write_mode ? cfg->write_mode : SPI_MODE_SINGLE); + + // SPI addresses and opcodes + if (cfg->has_input_report_header_address) + writel(cfg->input_report_header_address, &ithc->regs->spi_header_addr); + if (cfg->has_input_report_body_address) + writel(cfg->input_report_body_address, &ithc->regs->dma_rx[0].spi_addr); + if (cfg->has_output_report_body_address) + writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); + + if (cfg->has_read_opcode) { + writeb(cfg->read_opcode, &ithc->regs->read_opcode); + writeb(cfg->read_opcode, &ithc->regs->read_opcode_single); + writeb(cfg->read_opcode, &ithc->regs->read_opcode_dual); + writeb(cfg->read_opcode, &ithc->regs->read_opcode_quad); + } + if (cfg->has_write_opcode) { + writeb(cfg->write_opcode, &ithc->regs->write_opcode); + writeb(cfg->write_opcode, &ithc->regs->write_opcode_single); + writeb(cfg->write_opcode, &ithc->regs->write_opcode_dual); + writeb(cfg->write_opcode, &ithc->regs->write_opcode_quad); + } + ithc_log_regs(ithc); + + // The rest... + bitsl(&ithc->regs->quickspi_config1, + QUICKSPI_CONFIG1_UNKNOWN_0(0xff) | QUICKSPI_CONFIG1_UNKNOWN_5(0xff) | + QUICKSPI_CONFIG1_UNKNOWN_10(0xff) | QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), + QUICKSPI_CONFIG1_UNKNOWN_0(4) | QUICKSPI_CONFIG1_UNKNOWN_5(4) | + QUICKSPI_CONFIG1_UNKNOWN_10(22) | QUICKSPI_CONFIG1_UNKNOWN_16(2)); + + bitsl(&ithc->regs->quickspi_config2, + QUICKSPI_CONFIG2_UNKNOWN_0(0xff) | QUICKSPI_CONFIG2_UNKNOWN_5(0xff) | + QUICKSPI_CONFIG2_UNKNOWN_12(0xff), + QUICKSPI_CONFIG2_UNKNOWN_0(8) | QUICKSPI_CONFIG2_UNKNOWN_5(14) | + QUICKSPI_CONFIG2_UNKNOWN_12(2)); + + u32 pktsize = cfg->has_limit_packet_size && cfg->limit_packet_size == 1 ? 4 : 0x80; + bitsl(&ithc->regs->spi_config, + SPI_CONFIG_READ_PACKET_SIZE(0xfff) | SPI_CONFIG_WRITE_PACKET_SIZE(0xfff), + SPI_CONFIG_READ_PACKET_SIZE(pktsize) | SPI_CONFIG_WRITE_PACKET_SIZE(pktsize)); + + bitsl_set(&ithc->regs->quickspi_config2, + QUICKSPI_CONFIG2_UNKNOWN_16 | QUICKSPI_CONFIG2_UNKNOWN_17); + bitsl(&ithc->regs->quickspi_config2, + QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT | + QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT | + QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE, 0); + + return 0; +} + +static int wait_for_report(struct ithc *ithc) +{ + CHECK_RET(waitl, ithc, &ithc->regs->dma_rx[0].status, + DMA_RX_STATUS_READY, DMA_RX_STATUS_READY); + writel(DMA_RX_STATUS_READY, &ithc->regs->dma_rx[0].status); + + u32 h = readl(&ithc->regs->input_header); + ithc_log_regs(ithc); + if (INPUT_HEADER_SYNC(h) != INPUT_HEADER_SYNC_VALUE + || INPUT_HEADER_VERSION(h) != INPUT_HEADER_VERSION_VALUE) { + pci_err(ithc->pci, "invalid input report frame header 0x%08x\n", h); + return -ENODATA; + } + return INPUT_HEADER_REPORT_LENGTH(h) * 4; +} + +static int ithc_quickspi_init_hidspi(struct ithc *ithc, const struct ithc_acpi_config *cfg) +{ + pci_dbg(ithc->pci, "initializing HIDSPI\n"); + + // HIDSPI initialization sequence: + // "1. The host shall invoke the ACPI reset method to clear the device state." + acpi_status s = acpi_evaluate_object(ACPI_HANDLE(&ithc->pci->dev), "_RST", NULL, NULL); + if (ACPI_FAILURE(s)) { + pci_err(ithc->pci, "ACPI reset failed\n"); + return -EIO; + } + + bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); + + // "2. Within 1 second, the device shall signal an interrupt and make available to the host + // an input report containing a device reset response." + int size = wait_for_report(ithc); + if (size < 0) + return size; + if (size < sizeof(struct hidspi_header)) { + pci_err(ithc->pci, "SPI data size too small for reset response (%u)\n", size); + return -EMSGSIZE; + } + + // "3. The host shall read the reset response from the device at the Input Report addresses + // specified in ACPI." + u32 in_addr = cfg->has_input_report_body_address ? cfg->input_report_body_address : 0x1000; + struct { + struct hidspi_header header; + union { + struct hidspi_device_descriptor device_desc; + u32 data[16]; + }; + } resp = { 0 }; + if (size > sizeof(resp)) { + pci_err(ithc->pci, "SPI data size for reset response too big (%u)\n", size); + return -EMSGSIZE; + } + CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); + if (resp.header.type != HIDSPI_INPUT_TYPE_RESET_RESPONSE) { + pci_err(ithc->pci, "received type %i instead of reset response\n", resp.header.type); + return -ENOMSG; + } + + // "4. The host shall then write an Output Report to the device at the Output Report Address + // specified in ACPI, requesting the Device Descriptor from the device." + u32 out_addr = cfg->has_output_report_body_address ? cfg->output_report_body_address : 0x1000; + struct hidspi_header req = { .type = HIDSPI_OUTPUT_TYPE_DEVICE_DESCRIPTOR_REQUEST }; + CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_WRITE, out_addr, sizeof(req), &req); + + // "5. Within 1 second, the device shall signal an interrupt and make available to the host + // an input report containing the Device Descriptor." + size = wait_for_report(ithc); + if (size < 0) + return size; + if (size < sizeof(resp.header) + sizeof(resp.device_desc)) { + pci_err(ithc->pci, "SPI data size too small for device descriptor (%u)\n", size); + return -EMSGSIZE; + } + + // "6. The host shall read the Device Descriptor from the Input Report addresses specified + // in ACPI." + if (size > sizeof(resp)) { + pci_err(ithc->pci, "SPI data size for device descriptor too big (%u)\n", size); + return -EMSGSIZE; + } + memset(&resp, 0, sizeof(resp)); + CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, in_addr, size, &resp); + if (resp.header.type != HIDSPI_INPUT_TYPE_DEVICE_DESCRIPTOR) { + pci_err(ithc->pci, "received type %i instead of device descriptor\n", + resp.header.type); + return -ENOMSG; + } + struct hidspi_device_descriptor *d = &resp.device_desc; + if (resp.header.len < sizeof(*d)) { + pci_err(ithc->pci, "response too small for device descriptor (%u)\n", + resp.header.len); + return -EMSGSIZE; + } + if (d->wDeviceDescLength != sizeof(*d)) { + pci_err(ithc->pci, "invalid device descriptor length (%u)\n", + d->wDeviceDescLength); + return -EMSGSIZE; + } + + pci_info(ithc->pci, "Device descriptor: bcdVersion=0x%04x wReportDescLength=%u wMaxInputLength=%u wMaxOutputLength=%u wMaxFragmentLength=%u wVendorID=0x%04x wProductID=0x%04x wVersionID=0x%04x wFlags=0x%04x dwReserved=0x%08x\n", + d->bcdVersion, d->wReportDescLength, + d->wMaxInputLength, d->wMaxOutputLength, d->wMaxFragmentLength, + d->wVendorID, d->wProductID, d->wVersionID, + d->wFlags, d->dwReserved); + + ithc->vendor_id = d->wVendorID; + ithc->product_id = d->wProductID; + ithc->product_rev = d->wVersionID; + ithc->max_rx_size = max_t(u32, d->wMaxInputLength, + d->wReportDescLength + sizeof(struct hidspi_header)); + ithc->max_tx_size = d->wMaxOutputLength; + ithc->have_config = true; + + // "7. The device and host shall then enter their "Ready" states - where the device may + // begin sending Input Reports, and the device shall be prepared for Output Reports from + // the host." + + return 0; +} + +int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg) +{ + bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); + CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); + + ithc_log_regs(ithc); + CHECK_RET(ithc_quickspi_init_regs, ithc, cfg); + ithc_log_regs(ithc); + CHECK_RET(ithc_quickspi_init_hidspi, ithc, cfg); + ithc_log_regs(ithc); + + // This value is set to 2 in ithc_quickspi_init_regs(). It needs to be set to 1 here, + // otherwise DMA will not work. Maybe selects between DMA and PIO mode? + bitsl(&ithc->regs->quickspi_config1, + QUICKSPI_CONFIG1_UNKNOWN_16(0xffff), QUICKSPI_CONFIG1_UNKNOWN_16(1)); + + // TODO Do we need to set any of the following bits here? + //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_4); + //bitsb_set(&ithc->regs->dma_rx[0].control2, DMA_RX_CONTROL2_UNKNOWN_5); + //bitsb_set(&ithc->regs->dma_rx[1].control2, DMA_RX_CONTROL2_UNKNOWN_5); + //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_3); + //bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); + + ithc_log_regs(ithc); + + return 0; +} + +void ithc_quickspi_exit(struct ithc *ithc) +{ + // TODO Should we send HIDSPI 'power off' command? + //struct hidspi_header h = { .type = HIDSPI_OUTPUT_TYPE_COMMAND, .id = 3, }; + //struct ithc_data d = { .type = ITHC_DATA_RAW, .data = &h, .size = sizeof(h) }; + //CHECK(ithc_dma_tx, ithc, &d); // or ithc_spi_command() +} + +int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest) +{ + const struct hidspi_header *hdr = src; + + if (len < sizeof(*hdr)) + return -ENODATA; + // TODO Do we need to handle HIDSPI packet fragmentation? + if (len < sizeof(*hdr) + hdr->len) + return -EMSGSIZE; + if (len > round_up(sizeof(*hdr) + hdr->len, 4)) + return -EMSGSIZE; + + switch (hdr->type) { + case HIDSPI_INPUT_TYPE_RESET_RESPONSE: + // TODO "When the device detects an error condition, it may interrupt and make + // available to the host an Input Report containing an unsolicited Reset Response. + // After receiving an unsolicited Reset Response, the host shall initiate the + // request procedure from step (4) in the [HIDSPI initialization] process." + dest->type = ITHC_DATA_ERROR; + return 0; + case HIDSPI_INPUT_TYPE_REPORT_DESCRIPTOR: + dest->type = ITHC_DATA_REPORT_DESCRIPTOR; + dest->data = hdr + 1; + dest->size = hdr->len; + return 0; + case HIDSPI_INPUT_TYPE_DATA: + case HIDSPI_INPUT_TYPE_GET_INPUT_REPORT_RESPONSE: + dest->type = ITHC_DATA_INPUT_REPORT; + dest->data = &hdr->id; + dest->size = hdr->len + 1; + return 0; + case HIDSPI_INPUT_TYPE_GET_FEATURE_RESPONSE: + dest->type = ITHC_DATA_GET_FEATURE; + dest->data = &hdr->id; + dest->size = hdr->len + 1; + return 0; + case HIDSPI_INPUT_TYPE_SET_FEATURE_RESPONSE: + case HIDSPI_INPUT_TYPE_OUTPUT_REPORT_RESPONSE: + dest->type = ITHC_DATA_IGNORE; + return 0; + default: + return -EINVAL; + } +} + +ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, + size_t maxlen) +{ + struct hidspi_header *hdr = dest; + + size_t src_size = src->size; + const u8 *src_data = src->data; + u8 type; + + switch (src->type) { + case ITHC_DATA_SET_FEATURE: + type = HIDSPI_OUTPUT_TYPE_SET_FEATURE; + break; + case ITHC_DATA_GET_FEATURE: + type = HIDSPI_OUTPUT_TYPE_GET_FEATURE; + break; + case ITHC_DATA_OUTPUT_REPORT: + type = HIDSPI_OUTPUT_TYPE_OUTPUT_REPORT; + break; + case ITHC_DATA_REPORT_DESCRIPTOR: + type = HIDSPI_OUTPUT_TYPE_REPORT_DESCRIPTOR_REQUEST; + src_size = 0; + break; + default: + return -EINVAL; + } + + u8 id = 0; + if (src_size) { + id = *src_data++; + src_size--; + } + + // Data must be padded to next 4-byte boundary. + size_t padded = round_up(src_size, 4); + if (sizeof(*hdr) + padded > maxlen) + return -EOVERFLOW; + + // Fill the TX buffer with header and data. + hdr->type = type; + hdr->len = (u16)src_size; + hdr->id = id; + memcpy_and_pad(hdr + 1, padded, src_data, src_size, 0); + + return sizeof(*hdr) + padded; +} + diff --git a/drivers/hid/ithc/ithc-quickspi.h b/drivers/hid/ithc/ithc-quickspi.h new file mode 100644 index 0000000000000..74d882f6b2f0a --- /dev/null +++ b/drivers/hid/ithc/ithc-quickspi.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +struct ithc_acpi_config { + bool has_config: 1; + bool has_input_report_header_address: 1; + bool has_input_report_body_address: 1; + bool has_output_report_body_address: 1; + bool has_read_opcode: 1; + bool has_write_opcode: 1; + bool has_read_mode: 1; + bool has_write_mode: 1; + bool has_spi_frequency: 1; + bool has_limit_packet_size: 1; + bool has_tx_delay: 1; + bool has_active_ltr: 1; + bool has_idle_ltr: 1; + u32 input_report_header_address; + u32 input_report_body_address; + u32 output_report_body_address; + u8 read_opcode; + u8 write_opcode; + u8 read_mode; + u8 write_mode; + u32 spi_frequency; + u32 limit_packet_size; + u32 tx_delay; // us/10 // TODO use? + u32 active_ltr; // ns/1024 + u32 idle_ltr; // ns/1024 +}; + +int ithc_read_acpi_config(struct ithc *ithc, struct ithc_acpi_config *cfg); +void ithc_print_acpi_config(struct ithc *ithc, const struct ithc_acpi_config *cfg); + +int ithc_quickspi_init(struct ithc *ithc, const struct ithc_acpi_config *cfg); +void ithc_quickspi_exit(struct ithc *ithc); +int ithc_quickspi_decode_rx(struct ithc *ithc, const void *src, size_t len, struct ithc_data *dest); +ssize_t ithc_quickspi_encode_tx(struct ithc *ithc, const struct ithc_data *src, void *dest, + size_t maxlen); + diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c index e058721886e37..c0f13506af205 100644 --- a/drivers/hid/ithc/ithc-regs.c +++ b/drivers/hid/ithc/ithc-regs.c @@ -22,46 +22,104 @@ void bitsb(__iomem u8 *reg, u8 mask, u8 val) int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) { + ithc_log_regs(ithc); pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", reg_num(reg), mask, val); u32 x; if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { + ithc_log_regs(ithc); pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", reg_num(reg), mask, val); return -ETIMEDOUT; } + ithc_log_regs(ithc); pci_dbg(ithc->pci, "done waiting\n"); return 0; } int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) { + ithc_log_regs(ithc); pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", reg_num(reg), mask, val); u8 x; if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { + ithc_log_regs(ithc); pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", reg_num(reg), mask, val); return -ETIMEDOUT; } + ithc_log_regs(ithc); pci_dbg(ithc->pci, "done waiting\n"); return 0; } -int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode) +static void calc_ltr(u64 *ns, unsigned int *val, unsigned int *scale) { - pci_dbg(ithc->pci, "setting SPI speed to %i, mode %i\n", speed, mode); - if (mode == 3) - mode = 2; + unsigned int s = 0; + u64 v = *ns; + while (v > 0x3ff) { + s++; + v >>= 5; + } + if (s > 5) { + s = 5; + v = 0x3ff; + } + *val = v; + *scale = s; + *ns = v << (5 * s); +} + +void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns) +{ + unsigned int active_val, active_scale, idle_val, idle_scale; + calc_ltr(&active_ltr_ns, &active_val, &active_scale); + calc_ltr(&idle_ltr_ns, &idle_val, &idle_scale); + pci_dbg(ithc->pci, "setting active LTR value to %llu ns, idle LTR value to %llu ns\n", + active_ltr_ns, idle_ltr_ns); + writel(LTR_CONFIG_ENABLE_ACTIVE | LTR_CONFIG_ENABLE_IDLE | LTR_CONFIG_APPLY | + LTR_CONFIG_ACTIVE_LTR_SCALE(active_scale) | LTR_CONFIG_ACTIVE_LTR_VALUE(active_val) | + LTR_CONFIG_IDLE_LTR_SCALE(idle_scale) | LTR_CONFIG_IDLE_LTR_VALUE(idle_val), + &ithc->regs->ltr_config); +} + +void ithc_set_ltr_idle(struct ithc *ithc) +{ + u32 ltr = readl(&ithc->regs->ltr_config); + switch (ltr & (LTR_CONFIG_STATUS_ACTIVE | LTR_CONFIG_STATUS_IDLE)) { + case LTR_CONFIG_STATUS_IDLE: + break; + case LTR_CONFIG_STATUS_ACTIVE: + writel(ltr | LTR_CONFIG_TOGGLE | LTR_CONFIG_APPLY, &ithc->regs->ltr_config); + break; + default: + pci_err(ithc->pci, "invalid LTR state 0x%08x\n", ltr); + break; + } +} + +int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode) +{ + if (clkdiv == 0 || clkdiv > 7 || read_mode > SPI_MODE_QUAD || write_mode > SPI_MODE_QUAD) + return -EINVAL; + static const char * const modes[] = { "single", "dual", "quad" }; + pci_dbg(ithc->pci, "setting SPI frequency to %i Hz, %s read, %s write\n", + SPI_CLK_FREQ_BASE / (clkdiv * (clkdiv8 ? 8 : 1)), + modes[read_mode], modes[write_mode]); bitsl(&ithc->regs->spi_config, - SPI_CONFIG_MODE(0xff) | SPI_CONFIG_SPEED(0xff) | SPI_CONFIG_UNKNOWN_18(0xff) | SPI_CONFIG_SPEED2(0xff), - SPI_CONFIG_MODE(mode) | SPI_CONFIG_SPEED(speed) | SPI_CONFIG_UNKNOWN_18(0) | SPI_CONFIG_SPEED2(speed)); + SPI_CONFIG_READ_MODE(0xff) | SPI_CONFIG_READ_CLKDIV(0xff) | + SPI_CONFIG_WRITE_MODE(0xff) | SPI_CONFIG_WRITE_CLKDIV(0xff) | + SPI_CONFIG_CLKDIV_8, + SPI_CONFIG_READ_MODE(read_mode) | SPI_CONFIG_READ_CLKDIV(clkdiv) | + SPI_CONFIG_WRITE_MODE(write_mode) | SPI_CONFIG_WRITE_CLKDIV(clkdiv) | + (clkdiv8 ? SPI_CONFIG_CLKDIV_8 : 0)); return 0; } int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) { - pci_dbg(ithc->pci, "SPI command %u, size %u, offset %u\n", command, size, offset); + pci_dbg(ithc->pci, "SPI command %u, size %u, offset 0x%x\n", command, size, offset); if (size > sizeof(ithc->regs->spi_cmd.data)) return -EINVAL; diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h index d4007d9e2bacc..a9d2364546442 100644 --- a/drivers/hid/ithc/ithc-regs.h +++ b/drivers/hid/ithc/ithc-regs.h @@ -1,14 +1,34 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ +#define LTR_CONFIG_ENABLE_ACTIVE BIT(0) +#define LTR_CONFIG_TOGGLE BIT(1) +#define LTR_CONFIG_ENABLE_IDLE BIT(2) +#define LTR_CONFIG_APPLY BIT(3) +#define LTR_CONFIG_IDLE_LTR_SCALE(x) (((x) & 7) << 4) +#define LTR_CONFIG_IDLE_LTR_VALUE(x) (((x) & 0x3ff) << 7) +#define LTR_CONFIG_ACTIVE_LTR_SCALE(x) (((x) & 7) << 17) +#define LTR_CONFIG_ACTIVE_LTR_VALUE(x) (((x) & 0x3ff) << 20) +#define LTR_CONFIG_STATUS_ACTIVE BIT(30) +#define LTR_CONFIG_STATUS_IDLE BIT(31) + #define CONTROL_QUIESCE BIT(1) #define CONTROL_IS_QUIESCED BIT(2) #define CONTROL_NRESET BIT(3) +#define CONTROL_UNKNOWN_24(x) (((x) & 3) << 24) #define CONTROL_READY BIT(29) -#define SPI_CONFIG_MODE(x) (((x) & 3) << 2) -#define SPI_CONFIG_SPEED(x) (((x) & 7) << 4) -#define SPI_CONFIG_UNKNOWN_18(x) (((x) & 3) << 18) -#define SPI_CONFIG_SPEED2(x) (((x) & 0xf) << 20) // high bit = high speed mode? +#define SPI_CONFIG_READ_MODE(x) (((x) & 3) << 2) +#define SPI_CONFIG_READ_CLKDIV(x) (((x) & 7) << 4) +#define SPI_CONFIG_READ_PACKET_SIZE(x) (((x) & 0x1ff) << 7) +#define SPI_CONFIG_WRITE_MODE(x) (((x) & 3) << 18) +#define SPI_CONFIG_WRITE_CLKDIV(x) (((x) & 7) << 20) +#define SPI_CONFIG_CLKDIV_8 BIT(23) // additionally divide clk by 8, for both read and write +#define SPI_CONFIG_WRITE_PACKET_SIZE(x) (((x) & 0xff) << 24) + +#define SPI_CLK_FREQ_BASE 125000000 +#define SPI_MODE_SINGLE 0 +#define SPI_MODE_DUAL 1 +#define SPI_MODE_QUAD 2 #define ERROR_CONTROL_UNKNOWN_0 BIT(0) #define ERROR_CONTROL_DISABLE_DMA BIT(1) // clears DMA_RX_CONTROL_ENABLE when a DMA error occurs @@ -53,33 +73,71 @@ #define DMA_TX_STATUS_UNKNOWN_2 BIT(2) #define DMA_TX_STATUS_UNKNOWN_3 BIT(3) // busy? +#define INPUT_HEADER_VERSION(x) ((x) & 0xf) +#define INPUT_HEADER_REPORT_LENGTH(x) (((x) >> 8) & 0x3fff) +#define INPUT_HEADER_SYNC(x) ((x) >> 24) +#define INPUT_HEADER_VERSION_VALUE 3 +#define INPUT_HEADER_SYNC_VALUE 0x5a + +#define QUICKSPI_CONFIG1_UNKNOWN_0(x) (((x) & 0x1f) << 0) +#define QUICKSPI_CONFIG1_UNKNOWN_5(x) (((x) & 0x1f) << 5) +#define QUICKSPI_CONFIG1_UNKNOWN_10(x) (((x) & 0x1f) << 10) +#define QUICKSPI_CONFIG1_UNKNOWN_16(x) (((x) & 0xffff) << 16) + +#define QUICKSPI_CONFIG2_UNKNOWN_0(x) (((x) & 0x1f) << 0) +#define QUICKSPI_CONFIG2_UNKNOWN_5(x) (((x) & 0x1f) << 5) +#define QUICKSPI_CONFIG2_UNKNOWN_12(x) (((x) & 0xf) << 12) +#define QUICKSPI_CONFIG2_UNKNOWN_16 BIT(16) +#define QUICKSPI_CONFIG2_UNKNOWN_17 BIT(17) +#define QUICKSPI_CONFIG2_DISABLE_READ_ADDRESS_INCREMENT BIT(24) +#define QUICKSPI_CONFIG2_DISABLE_WRITE_ADDRESS_INCREMENT BIT(25) +#define QUICKSPI_CONFIG2_ENABLE_WRITE_STREAMING_MODE BIT(27) +#define QUICKSPI_CONFIG2_IRQ_POLARITY BIT(28) + #define DMA_RX_CONTROL_ENABLE BIT(0) #define DMA_RX_CONTROL_IRQ_UNKNOWN_1 BIT(1) // rx1 only? #define DMA_RX_CONTROL_IRQ_ERROR BIT(3) // rx1 only? -#define DMA_RX_CONTROL_IRQ_UNKNOWN_4 BIT(4) // rx0 only? +#define DMA_RX_CONTROL_IRQ_READY BIT(4) // rx0 only #define DMA_RX_CONTROL_IRQ_DATA BIT(5) +#define DMA_RX_CONTROL2_UNKNOWN_4 BIT(4) // rx1 only? #define DMA_RX_CONTROL2_UNKNOWN_5 BIT(5) // rx0 only? #define DMA_RX_CONTROL2_RESET BIT(7) // resets ringbuffer indices #define DMA_RX_WRAP_FLAG BIT(7) #define DMA_RX_STATUS_ERROR BIT(3) -#define DMA_RX_STATUS_UNKNOWN_4 BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) +#define DMA_RX_STATUS_READY BIT(4) // set in rx0 after using CONTROL_NRESET when it becomes possible to read config (can take >100ms) #define DMA_RX_STATUS_HAVE_DATA BIT(5) #define DMA_RX_STATUS_ENABLED BIT(8) +#define INIT_UNKNOWN_GUC_2 BIT(2) +#define INIT_UNKNOWN_3 BIT(3) +#define INIT_UNKNOWN_GUC_4 BIT(4) +#define INIT_UNKNOWN_5 BIT(5) +#define INIT_UNKNOWN_31 BIT(31) + // COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. #define COUNTER_RESET BIT(31) struct ithc_registers { - /* 0000 */ u32 _unknown_0000[1024]; + /* 0000 */ u32 _unknown_0000[5]; + /* 0014 */ u32 ltr_config; + /* 0018 */ u32 _unknown_0018[1018]; /* 1000 */ u32 _unknown_1000; /* 1004 */ u32 _unknown_1004; /* 1008 */ u32 control_bits; /* 100c */ u32 _unknown_100c; /* 1010 */ u32 spi_config; - /* 1014 */ u32 _unknown_1014[3]; + /* 1014 */ u8 read_opcode; // maybe for header? + /* 1015 */ u8 read_opcode_quad; + /* 1016 */ u8 read_opcode_dual; + /* 1017 */ u8 read_opcode_single; + /* 1018 */ u8 write_opcode; // not used? + /* 1019 */ u8 write_opcode_quad; + /* 101a */ u8 write_opcode_dual; + /* 101b */ u8 write_opcode_single; + /* 101c */ u32 _unknown_101c; /* 1020 */ u32 error_control; /* 1024 */ u32 error_status; // write to clear /* 1028 */ u32 error_flags; // write to clear @@ -100,12 +158,19 @@ struct ithc_registers { /* 109a */ u8 _unknown_109a; /* 109b */ u8 num_prds; /* 109c */ u32 status; // write to clear + /* 10a0 */ u32 _unknown_10a0[5]; + /* 10b4 */ u32 spi_addr; } dma_tx; - /* 10a0 */ u32 _unknown_10a0[7]; - /* 10bc */ u32 state; // is 0xe0000402 (dev config val 0) after CONTROL_NRESET, 0xe0000461 after first touch, 0xe0000401 after DMA_RX_CODE_RESET + /* 10b8 */ u32 spi_header_addr; + union { + /* 10bc */ u32 irq_cause; // in legacy THC mode + /* 10bc */ u32 input_header; // in QuickSPI mode (see HIDSPI spec) + }; /* 10c0 */ u32 _unknown_10c0[8]; /* 10e0 */ u32 _unknown_10e0_counters[3]; - /* 10ec */ u32 _unknown_10ec[5]; + /* 10ec */ u32 quickspi_config1; + /* 10f0 */ u32 quickspi_config2; + /* 10f4 */ u32 _unknown_10f4[3]; struct { /* 1100/1200 */ u64 addr; // cannot be written with writeq(), must use lo_hi_writeq() /* 1108/1208 */ u8 num_bufs; @@ -120,70 +185,30 @@ struct ithc_registers { /* 1118/1218 */ u64 _unknown_1118_guc_addr; /* 1120/1220 */ u32 _unknown_1120_guc; /* 1124/1224 */ u32 _unknown_1124_guc; - /* 1128/1228 */ u32 unknown_init_bits; // bit 2 = guc related, bit 3 = rx1 related, bit 4 = guc related + /* 1128/1228 */ u32 init_unknown; /* 112c/122c */ u32 _unknown_112c; /* 1130/1230 */ u64 _unknown_1130_guc_addr; /* 1138/1238 */ u32 _unknown_1138_guc; /* 113c/123c */ u32 _unknown_113c; /* 1140/1240 */ u32 _unknown_1140_guc; - /* 1144/1244 */ u32 _unknown_1144[23]; + /* 1144/1244 */ u32 _unknown_1144[11]; + /* 1170/1270 */ u32 spi_addr; + /* 1174/1274 */ u32 _unknown_1174[11]; /* 11a0/12a0 */ u32 _unknown_11a0_counters[6]; /* 11b8/12b8 */ u32 _unknown_11b8[18]; } dma_rx[2]; }; static_assert(sizeof(struct ithc_registers) == 0x1300); -#define DEVCFG_DMA_RX_SIZE(x) ((((x) & 0x3fff) + 1) << 6) -#define DEVCFG_DMA_TX_SIZE(x) (((((x) >> 14) & 0x3ff) + 1) << 6) - -#define DEVCFG_TOUCH_MASK 0x3f -#define DEVCFG_TOUCH_ENABLE BIT(0) -#define DEVCFG_TOUCH_UNKNOWN_1 BIT(1) -#define DEVCFG_TOUCH_UNKNOWN_2 BIT(2) -#define DEVCFG_TOUCH_UNKNOWN_3 BIT(3) -#define DEVCFG_TOUCH_UNKNOWN_4 BIT(4) -#define DEVCFG_TOUCH_UNKNOWN_5 BIT(5) -#define DEVCFG_TOUCH_UNKNOWN_6 BIT(6) - -#define DEVCFG_DEVICE_ID_TIC 0x43495424 // "$TIC" - -#define DEVCFG_SPI_MAX_FREQ(x) (((x) >> 1) & 0xf) // high bit = use high speed mode? -#define DEVCFG_SPI_MODE(x) (((x) >> 6) & 3) -#define DEVCFG_SPI_UNKNOWN_8(x) (((x) >> 8) & 0x3f) -#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat -#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) -#define DEVCFG_SPI_UNKNOWN_25 BIT(25) -#define DEVCFG_SPI_UNKNOWN_26 BIT(26) -#define DEVCFG_SPI_UNKNOWN_27 BIT(27) -#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this -#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? - -struct ithc_device_config { // (Example values are from an SP7+.) - u32 _unknown_00; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) - u32 _unknown_04; // 04 = 0x00000000 - u32 dma_buf_sizes; // 08 = 0x000a00ff - u32 touch_cfg; // 0c = 0x0000001c - u32 _unknown_10; // 10 = 0x0000001c - u32 device_id; // 14 = 0x43495424 = "$TIC" - u32 spi_config; // 18 = 0xfda00a2e - u16 vendor_id; // 1c = 0x045e = Microsoft Corp. - u16 product_id; // 1e = 0x0c1a - u32 revision; // 20 = 0x00000001 - u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) - u32 _unknown_28; // 28 = 0x00000000 - u32 fw_mode; // 2c = 0x00000000 (for fw update?) - u32 _unknown_30; // 30 = 0x00000000 - u32 _unknown_34; // 34 = 0x0404035e (u8,u8,u8,u8 = version?) - u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) - u32 _unknown_3c; // 3c = 0x00000002 -}; - void bitsl(__iomem u32 *reg, u32 mask, u32 val); void bitsb(__iomem u8 *reg, u8 mask, u8 val); #define bitsl_set(reg, x) bitsl(reg, x, x) #define bitsb_set(reg, x) bitsb(reg, x, x) int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val); int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val); -int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode); + +void ithc_set_ltr_config(struct ithc *ithc, u64 active_ltr_ns, u64 idle_ltr_ns); +void ithc_set_ltr_idle(struct ithc *ithc); +int ithc_set_spi_config(struct ithc *ithc, u8 clkdiv, bool clkdiv8, u8 read_mode, u8 write_mode); int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h index 028e55a4ec53e..e90c380444325 100644 --- a/drivers/hid/ithc/ithc.h +++ b/drivers/hid/ithc/ithc.h @@ -1,20 +1,19 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ -#include -#include -#include +#include +#include +#include #include +#include #include -#include +#include #include #include -#include #include #include -#include +#include +#include #include -#include -#include #define DEVNAME "ithc" #define DEVFULLNAME "Intel Touch Host Controller" @@ -27,10 +26,37 @@ #define NUM_RX_BUF 16 +// PCI device IDs: +// Lakefield +#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT1 0x98d0 +#define PCI_DEVICE_ID_INTEL_THC_LKF_PORT2 0x98d1 +// Tiger Lake +#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT1 0xa0d0 +#define PCI_DEVICE_ID_INTEL_THC_TGL_LP_PORT2 0xa0d1 +#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT1 0x43d0 +#define PCI_DEVICE_ID_INTEL_THC_TGL_H_PORT2 0x43d1 +// Alder Lake +#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1 0x7ad8 +#define PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2 0x7ad9 +#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT1 0x51d0 +#define PCI_DEVICE_ID_INTEL_THC_ADL_P_PORT2 0x51d1 +#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT1 0x54d0 +#define PCI_DEVICE_ID_INTEL_THC_ADL_M_PORT2 0x54d1 +// Raptor Lake +#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT1 0x7a58 +#define PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2 0x7a59 +// Meteor Lake +#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT1 0x7e48 +#define PCI_DEVICE_ID_INTEL_THC_MTL_PORT2 0x7e4a + struct ithc; #include "ithc-regs.h" +#include "ithc-hid.h" #include "ithc-dma.h" +#include "ithc-legacy.h" +#include "ithc-quickspi.h" +#include "ithc-debug.h" struct ithc { char phys[32]; @@ -38,30 +64,21 @@ struct ithc { int irq; struct task_struct *poll_thread; - struct pm_qos_request activity_qos; - struct hrtimer activity_start_timer; - struct hrtimer activity_end_timer; - ktime_t last_rx_time; - unsigned int cur_rx_seq_count; - unsigned int cur_rx_seq_errors; - - struct hid_device *hid; - bool hid_parse_done; - wait_queue_head_t wait_hid_parse; - wait_queue_head_t wait_hid_get_feature; - struct mutex hid_get_feature_mutex; - void *hid_get_feature_buf; - size_t hid_get_feature_size; - struct ithc_registers __iomem *regs; struct ithc_registers *prev_regs; // for debugging - struct ithc_device_config config; struct ithc_dma_rx dma_rx[2]; struct ithc_dma_tx dma_tx; + struct ithc_hid hid; + + bool use_quickspi; + bool have_config; + u16 vendor_id; + u16 product_id; + u32 product_rev; + u32 max_rx_size; + u32 max_tx_size; + u32 legacy_touch_cfg; }; int ithc_reset(struct ithc *ithc); -void ithc_set_active(struct ithc *ithc, unsigned int duration_us); -int ithc_debug_init(struct ithc *ithc); -void ithc_log_regs(struct ithc *ithc); -- 2.45.1 From b4563a0da733d5759de36d9e555ce81324dca286 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 9 May 2024 16:15:49 +0200 Subject: [PATCH] serial: Clear UPF_DEAD before calling tty_port_register_device_attr_serdev() If a serdev_device_driver is already loaded for a serdev_tty_port when it gets registered by tty_port_register_device_attr_serdev() then that driver's probe() method will be called immediately. The serdev_device_driver's probe() method should then be able to call serdev_device_open() successfully, but because UPF_DEAD is still dead serdev_device_open() will fail with -ENXIO in this scenario: serdev_device_open() ctrl->ops->open() /* this callback being ttyport_open() */ tty->ops->open() /* this callback being uart_open() */ tty_port_open() port->ops->activate() /* this callback being uart_port_activate() */ Find bit UPF_DEAD is set in uport->flags and fail with errno -ENXIO. Fix this be clearing UPF_DEAD before tty_port_register_device_attr_serdev() note this only moves up the UPD_DEAD clearing a small bit, before: tty_port_register_device_attr_serdev(); mutex_unlock(&tty_port.mutex); uart_port.flags &= ~UPF_DEAD; mutex_unlock(&port_mutex); after: uart_port.flags &= ~UPF_DEAD; tty_port_register_device_attr_serdev(); mutex_unlock(&tty_port.mutex); mutex_unlock(&port_mutex); Reported-by: Weifeng Liu Closes: https://lore.kernel.org/platform-driver-x86/20240505130800.2546640-1-weifeng.liu.z@gmail.com/ Tested-by: Weifeng Liu Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240509141549.63704-1-hdegoede@redhat.com Signed-off-by: Greg Kroah-Hartman Patchset: surface-sam --- drivers/tty/serial/serial_core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index c476d884356db..b47a277978a0b 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -3211,6 +3211,9 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u if (uport->attr_group) uport->tty_groups[1] = uport->attr_group; + /* Ensure serdev drivers can call serdev_device_open() right away */ + uport->flags &= ~UPF_DEAD; + /* * Register the port whether it's detected or not. This allows * setserial to be used to alter this port's parameters. @@ -3221,6 +3224,7 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u if (!IS_ERR(tty_dev)) { device_set_wakeup_capable(tty_dev, 1); } else { + uport->flags |= UPF_DEAD; dev_err(uport->dev, "Cannot register tty device on line %d\n", uport->line); } @@ -3426,8 +3430,6 @@ int serial_core_register_port(struct uart_driver *drv, struct uart_port *port) if (ret) goto err_unregister_port_dev; - port->flags &= ~UPF_DEAD; - mutex_unlock(&port_mutex); return 0; -- 2.45.1 From 0864ded554efe90dd9603b61e82c604481ee5125 Mon Sep 17 00:00:00 2001 From: Weifeng Liu Date: Sun, 5 May 2024 21:07:50 +0800 Subject: [PATCH] platform/surface: aggregator: Log critical errors during SAM probing Emits messages upon errors during probing of SAM. Hopefully this could provide useful context to user for the purpose of diagnosis when something miserable happen. Reviewed-by: Maximilian Luz Reviewed-by: Andy Shevchenko Signed-off-by: Weifeng Liu Link: https://lore.kernel.org/r/20240505130800.2546640-3-weifeng.liu.z@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Patchset: surface-sam --- drivers/platform/surface/aggregator/core.c | 42 ++++++++++++++-------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c index ba550eaa06fcf..797d0645bd77f 100644 --- a/drivers/platform/surface/aggregator/core.c +++ b/drivers/platform/surface/aggregator/core.c @@ -618,15 +618,17 @@ static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { static int ssam_serial_hub_probe(struct serdev_device *serdev) { - struct acpi_device *ssh = ACPI_COMPANION(&serdev->dev); + struct device *dev = &serdev->dev; + struct acpi_device *ssh = ACPI_COMPANION(dev); struct ssam_controller *ctrl; acpi_status astatus; int status; - if (gpiod_count(&serdev->dev, NULL) < 0) - return -ENODEV; + status = gpiod_count(dev, NULL); + if (status < 0) + return dev_err_probe(dev, status, "no GPIO found\n"); - status = devm_acpi_dev_add_driver_gpios(&serdev->dev, ssam_acpi_gpios); + status = devm_acpi_dev_add_driver_gpios(dev, ssam_acpi_gpios); if (status) return status; @@ -637,8 +639,10 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) /* Initialize controller. */ status = ssam_controller_init(ctrl, serdev); - if (status) + if (status) { + dev_err_probe(dev, status, "failed to initialize ssam controller\n"); goto err_ctrl_init; + } ssam_controller_lock(ctrl); @@ -646,12 +650,14 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) serdev_device_set_drvdata(serdev, ctrl); serdev_device_set_client_ops(serdev, &ssam_serdev_ops); status = serdev_device_open(serdev); - if (status) + if (status) { + dev_err_probe(dev, status, "failed to open serdev device\n"); goto err_devopen; + } astatus = ssam_serdev_setup_via_acpi(ssh->handle, serdev); if (ACPI_FAILURE(astatus)) { - status = -ENXIO; + status = dev_err_probe(dev, -ENXIO, "failed to setup serdev\n"); goto err_devinit; } @@ -667,25 +673,33 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) * states. */ status = ssam_log_firmware_version(ctrl); - if (status) + if (status) { + dev_err_probe(dev, status, "failed to get firmware version\n"); goto err_initrq; + } status = ssam_ctrl_notif_d0_entry(ctrl); - if (status) + if (status) { + dev_err_probe(dev, status, "D0-entry notification failed\n"); goto err_initrq; + } status = ssam_ctrl_notif_display_on(ctrl); - if (status) + if (status) { + dev_err_probe(dev, status, "display-on notification failed\n"); goto err_initrq; + } - status = sysfs_create_group(&serdev->dev.kobj, &ssam_sam_group); + status = sysfs_create_group(&dev->kobj, &ssam_sam_group); if (status) goto err_initrq; /* Set up IRQ. */ status = ssam_irq_setup(ctrl); - if (status) + if (status) { + dev_err_probe(dev, status, "failed to setup IRQ\n"); goto err_irq; + } /* Finally, set main controller reference. */ status = ssam_try_set_controller(ctrl); @@ -702,7 +716,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) * resumed. In short, this causes some spurious unwanted wake-ups. * For now let's thus default power/wakeup to false. */ - device_set_wakeup_capable(&serdev->dev, true); + device_set_wakeup_capable(dev, true); acpi_dev_clear_dependencies(ssh); return 0; @@ -710,7 +724,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) err_mainref: ssam_irq_free(ctrl); err_irq: - sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); + sysfs_remove_group(&dev->kobj, &ssam_sam_group); err_initrq: ssam_controller_lock(ctrl); ssam_controller_shutdown(ctrl); -- 2.45.1 From d44985653441ba783a830bd5efde2fcf3a3ea271 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 19 Apr 2024 20:41:47 +0200 Subject: [PATCH] platform/surface: aggregator: Fix warning when controller is destroyed in probe There is a small window in ssam_serial_hub_probe() where the controller is initialized but has not been started yet. Specifically, between ssam_controller_init() and ssam_controller_start(). Any failure in this window, for example caused by a failure of serdev_device_open(), currently results in an incorrect warning being emitted. In particular, any failure in this window results in the controller being destroyed via ssam_controller_destroy(). This function checks the state of the controller and, in an attempt to validate that the controller has been cleanly shut down before we try and deallocate any resources, emits a warning if that state is not SSAM_CONTROLLER_STOPPED. However, since we have only just initialized the controller and have not yet started it, its state is SSAM_CONTROLLER_INITIALIZED. Note that this is the only point at which the controller has this state, as it will change after we start the controller with ssam_controller_start() and never revert back. Further, at this point no communication has taken place and the sender and receiver threads have not been started yet (and we may not even have an open serdev device either). Therefore, it is perfectly safe to call ssam_controller_destroy() with a state of SSAM_CONTROLLER_INITIALIZED. This, however, means that the warning currently being emitted is incorrect. Fix it by extending the check. Fixes: c167b9c7e3d6 ("platform/surface: Add Surface Aggregator subsystem") Signed-off-by: Maximilian Luz Patchset: surface-sam --- drivers/platform/surface/aggregator/controller.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index 7fc602e01487d..7e89f547999b2 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -1354,7 +1354,8 @@ void ssam_controller_destroy(struct ssam_controller *ctrl) if (ctrl->state == SSAM_CONTROLLER_UNINITIALIZED) return; - WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED); + WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED && + ctrl->state != SSAM_CONTROLLER_INITIALIZED); /* * Note: New events could still have been received after the previous -- 2.45.1 From 6b6f860bbef0ba3f10f8dc151ac4e27d0a34c223 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 22 Oct 2023 14:57:11 +0200 Subject: [PATCH] platform/surface: aggregator_registry: Add support for Surface Laptop Go 3 Add SAM client device nodes for the Surface Laptop Go 3. It seems to use the same SAM client devices as the Surface Laptop Go 1 and 2, so re-use their node group. Signed-off-by: Maximilian Luz Patchset: surface-sam --- drivers/platform/surface/surface_aggregator_registry.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index 035d6b4105cd6..74688a2ed4b2e 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -374,6 +374,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { /* Surface Laptop Go 2 */ { "MSHW0290", (unsigned long)ssam_node_group_slg1 }, + /* Surface Laptop Go 3 */ + { "MSHW0440", (unsigned long)ssam_node_group_slg1 }, + /* Surface Laptop Studio */ { "MSHW0123", (unsigned long)ssam_node_group_sls }, -- 2.45.1 From 31b312c25822404e52a81de2525da5c7bae12864 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 20 Nov 2023 19:47:00 +0100 Subject: [PATCH] platform/surface: aggregator_registry: Add support for Surface Laptop Studio 2 Add SAM client device nodes for the Surface Laptop Studio 2 (SLS2). The SLS2 is quite similar to the SLS1, but it does not provide the touchpad as a SAM-HID device. Therefore, add a new node group for the SLS2 and update the comments accordingly Signed-off-by: Maximilian Luz Patchset: surface-sam --- .../surface/surface_aggregator_registry.c | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index 74688a2ed4b2e..f02a933160ff2 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -253,8 +253,8 @@ static const struct software_node *ssam_node_group_sl5[] = { NULL, }; -/* Devices for Surface Laptop Studio. */ -static const struct software_node *ssam_node_group_sls[] = { +/* Devices for Surface Laptop Studio 1. */ +static const struct software_node *ssam_node_group_sls1[] = { &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, @@ -269,6 +269,20 @@ static const struct software_node *ssam_node_group_sls[] = { NULL, }; +/* Devices for Surface Laptop Studio 2. */ +static const struct software_node *ssam_node_group_sls2[] = { + &ssam_node_root, + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_tmp_pprof, + &ssam_node_pos_tablet_switch, + &ssam_node_hid_sam_keyboard, + &ssam_node_hid_sam_penstash, + &ssam_node_hid_sam_sensors, + &ssam_node_hid_sam_ucm_ucsi, + NULL, +}; + /* Devices for Surface Laptop Go. */ static const struct software_node *ssam_node_group_slg1[] = { &ssam_node_root, @@ -377,8 +391,11 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { /* Surface Laptop Go 3 */ { "MSHW0440", (unsigned long)ssam_node_group_slg1 }, - /* Surface Laptop Studio */ - { "MSHW0123", (unsigned long)ssam_node_group_sls }, + /* Surface Laptop Studio 1 */ + { "MSHW0123", (unsigned long)ssam_node_group_sls1 }, + + /* Surface Laptop Studio 2 */ + { "MSHW0360", (unsigned long)ssam_node_group_sls2 }, { }, }; -- 2.45.1 From 42f6d14bda5e69c2b5a8d27cfcbd063a5922f876 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 9 Jun 2024 20:05:57 +0200 Subject: [PATCH] platform/surface: aggregator_registry: Add support for Surface Laptop 6 Add SAM client device nodes for the Surface Laptop Studio 6 (SL6). The SL6 is similar to the SL5, with the typical battery/AC, platform profile, and HID nodes. It also has support for the newly supported fan interface. Signed-off-by: Maximilian Luz Patchset: surface-sam --- .../surface/surface_aggregator_registry.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index f02a933160ff2..34df1bdad83bd 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -253,6 +253,22 @@ static const struct software_node *ssam_node_group_sl5[] = { NULL, }; +/* Devices for Surface Laptop 6. */ +static const struct software_node *ssam_node_group_sl6[] = { + &ssam_node_root, + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_tmp_perf_profile_with_fan, + &ssam_node_tmp_sensors, + &ssam_node_fan_speed, + &ssam_node_hid_main_keyboard, + &ssam_node_hid_main_touchpad, + &ssam_node_hid_main_iid5, + &ssam_node_hid_sam_sensors, + &ssam_node_hid_sam_ucm_ucsi, + NULL, +}; + /* Devices for Surface Laptop Studio 1. */ static const struct software_node *ssam_node_group_sls1[] = { &ssam_node_root, @@ -382,6 +398,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { /* Surface Laptop 5 */ { "MSHW0350", (unsigned long)ssam_node_group_sl5 }, + /* Surface Laptop 6 */ + { "MSHW0530", (unsigned long)ssam_node_group_sl5 }, + /* Surface Laptop Go 1 */ { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, -- 2.45.1 From 88fda328aea3bb7077cd39f854148276dcffcea3 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 30 Dec 2023 18:07:54 +0100 Subject: [PATCH] hwmon: Add thermal sensor driver for Surface Aggregator Module Some of the newer Microsoft Surface devices (such as the Surface Book 3 and Pro 9) have thermal sensors connected via the Surface Aggregator Module (the embedded controller on those devices). Add a basic driver to read out the temperature values of those sensors. Link: https://github.com/linux-surface/surface-aggregator-module/issues/59 Signed-off-by: Maximilian Luz Patchset: surface-sam --- drivers/hwmon/Kconfig | 10 +++ drivers/hwmon/Makefile | 1 + drivers/hwmon/surface_temp.c | 165 +++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 drivers/hwmon/surface_temp.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 83945397b6eb1..338ef73c96a3a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2070,6 +2070,16 @@ config SENSORS_SURFACE_FAN Select M or Y here, if you want to be able to read the fan's speed. +config SENSORS_SURFACE_TEMP + tristate "Microsoft Surface Thermal Sensor Driver" + depends on SURFACE_AGGREGATOR + help + Driver for monitoring thermal sensors connected via the Surface + Aggregator Module (embedded controller) on Microsoft Surface devices. + + This driver can also be built as a module. If so, the module + will be called surface_temp. + config SENSORS_ADC128D818 tristate "Texas Instruments ADC128D818" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 5c31808f6378d..de8bc99719e63 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -208,6 +208,7 @@ obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o obj-$(CONFIG_SENSORS_STTS751) += stts751.o obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o +obj-$(CONFIG_SENSORS_SURFACE_TEMP)+= surface_temp.o obj-$(CONFIG_SENSORS_SY7636A) += sy7636a-hwmon.o obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o obj-$(CONFIG_SENSORS_TC74) += tc74.o diff --git a/drivers/hwmon/surface_temp.c b/drivers/hwmon/surface_temp.c new file mode 100644 index 0000000000000..48c3e826713f6 --- /dev/null +++ b/drivers/hwmon/surface_temp.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Thermal sensor subsystem driver for Surface System Aggregator Module (SSAM). + * + * Copyright (C) 2022-2023 Maximilian Luz + */ + +#include +#include +#include +#include +#include + +#include +#include + + +/* -- SAM interface. -------------------------------------------------------- */ + +SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_get_available_sensors, __le16, { + .target_category = SSAM_SSH_TC_TMP, + .command_id = 0x04, +}); + +SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_temperature, __le16, { + .target_category = SSAM_SSH_TC_TMP, + .command_id = 0x01, +}); + +static int ssam_tmp_get_available_sensors(struct ssam_device *sdev, s16 *sensors) +{ + __le16 sensors_le; + int status; + + status = __ssam_tmp_get_available_sensors(sdev, &sensors_le); + if (status) + return status; + + *sensors = le16_to_cpu(sensors_le); + return 0; +} + +static int ssam_tmp_get_temperature(struct ssam_device *sdev, u8 iid, long *temperature) +{ + __le16 temp_le; + int status; + + status = __ssam_tmp_get_temperature(sdev->ctrl, sdev->uid.target, iid, &temp_le); + if (status) + return status; + + /* Convert 1/10 °K to 1/1000 °C */ + *temperature = (le16_to_cpu(temp_le) - 2731) * 100L; + return 0; +} + + +/* -- Driver.---------------------------------------------------------------- */ + +struct ssam_temp { + struct ssam_device *sdev; + s16 sensors; +}; + +static umode_t ssam_temp_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct ssam_temp *ssam_temp = data; + + if (!(ssam_temp->sensors & BIT(channel))) + return 0; + + return 0444; +} + +static int ssam_temp_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + const struct ssam_temp *ssam_temp = dev_get_drvdata(dev); + + return ssam_tmp_get_temperature(ssam_temp->sdev, channel + 1, value); +} + +static const struct hwmon_channel_info * const ssam_temp_hwmon_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ), + /* We have at most 16 thermal sensor channels. */ + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT), + NULL +}; + +static const struct hwmon_ops ssam_temp_hwmon_ops = { + .is_visible = ssam_temp_hwmon_is_visible, + .read = ssam_temp_hwmon_read, +}; + +static const struct hwmon_chip_info ssam_temp_hwmon_chip_info = { + .ops = &ssam_temp_hwmon_ops, + .info = ssam_temp_hwmon_info, +}; + +static int ssam_temp_probe(struct ssam_device *sdev) +{ + struct ssam_temp *ssam_temp; + struct device *hwmon_dev; + s16 sensors; + int status; + + status = ssam_tmp_get_available_sensors(sdev, &sensors); + if (status) + return status; + + ssam_temp = devm_kzalloc(&sdev->dev, sizeof(*ssam_temp), GFP_KERNEL); + if (!ssam_temp) + return -ENOMEM; + + ssam_temp->sdev = sdev; + ssam_temp->sensors = sensors; + + hwmon_dev = devm_hwmon_device_register_with_info(&sdev->dev, + "surface_thermal", ssam_temp, &ssam_temp_hwmon_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + return 0; +} + +static const struct ssam_device_id ssam_temp_match[] = { + { SSAM_SDEV(TMP, SAM, 0x00, 0x02) }, + { }, +}; +MODULE_DEVICE_TABLE(ssam, ssam_temp_match); + +static struct ssam_device_driver ssam_temp = { + .probe = ssam_temp_probe, + .match_table = ssam_temp_match, + .driver = { + .name = "surface_temp", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_ssam_device_driver(ssam_temp); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Thermal sensor subsystem driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); -- 2.45.1 From 17f0ec6ef1bc95e152af3a9f2b05ea669c75d24a Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 30 Dec 2023 18:12:23 +0100 Subject: [PATCH] hwmon: surface_temp: Add support for sensor names The thermal subsystem of the Surface Aggregator Module allows us to query the names of the respective thermal sensors. Forward those to userspace. Signed-off-by: Ivor Wanders Co-Developed-by: Maximilian Luz Signed-off-by: Maximilian Luz Patchset: surface-sam --- drivers/hwmon/surface_temp.c | 113 +++++++++++++++++++++++++++++------ 1 file changed, 96 insertions(+), 17 deletions(-) diff --git a/drivers/hwmon/surface_temp.c b/drivers/hwmon/surface_temp.c index 48c3e826713f6..4c08926139dbf 100644 --- a/drivers/hwmon/surface_temp.c +++ b/drivers/hwmon/surface_temp.c @@ -17,6 +17,27 @@ /* -- SAM interface. -------------------------------------------------------- */ +/* + * Available sensors are indicated by a 16-bit bitfield, where a 1 marks the + * presence of a sensor. So we have at most 16 possible sensors/channels. + */ +#define SSAM_TMP_SENSOR_MAX_COUNT 16 + +/* + * All names observed so far are 6 characters long, but there's only + * zeros after the name, so perhaps they can be longer. This number reflects + * the maximum zero-padded space observed in the returned buffer. + */ +#define SSAM_TMP_SENSOR_NAME_LENGTH 18 + +struct ssam_tmp_get_name_rsp { + __le16 unknown1; + char unknown2; + char name[SSAM_TMP_SENSOR_NAME_LENGTH]; +} __packed; + +static_assert(sizeof(struct ssam_tmp_get_name_rsp) == 21); + SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_get_available_sensors, __le16, { .target_category = SSAM_SSH_TC_TMP, .command_id = 0x04, @@ -27,6 +48,11 @@ SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_temperature, __le16, { .command_id = 0x01, }); +SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_name, struct ssam_tmp_get_name_rsp, { + .target_category = SSAM_SSH_TC_TMP, + .command_id = 0x0e, +}); + static int ssam_tmp_get_available_sensors(struct ssam_device *sdev, s16 *sensors) { __le16 sensors_le; @@ -54,12 +80,37 @@ static int ssam_tmp_get_temperature(struct ssam_device *sdev, u8 iid, long *temp return 0; } +static int ssam_tmp_get_name(struct ssam_device *sdev, u8 iid, char *buf, size_t buf_len) +{ + struct ssam_tmp_get_name_rsp name_rsp; + int status; + + status = __ssam_tmp_get_name(sdev->ctrl, sdev->uid.target, iid, &name_rsp); + if (status) + return status; + + /* + * This should not fail unless the name in the returned struct is not + * null-terminated or someone changed something in the struct + * definitions above, since our buffer and struct have the same + * capacity by design. So if this fails blow this up with a warning. + * Since the more likely cause is that the returned string isn't + * null-terminated, we might have received garbage (as opposed to just + * an incomplete string), so also fail the function. + */ + status = strscpy(buf, name_rsp.name, buf_len); + WARN_ON(status < 0); + + return status < 0 ? status : 0; +} + /* -- Driver.---------------------------------------------------------------- */ struct ssam_temp { struct ssam_device *sdev; s16 sensors; + char names[SSAM_TMP_SENSOR_MAX_COUNT][SSAM_TMP_SENSOR_NAME_LENGTH]; }; static umode_t ssam_temp_hwmon_is_visible(const void *data, @@ -83,33 +134,47 @@ static int ssam_temp_hwmon_read(struct device *dev, return ssam_tmp_get_temperature(ssam_temp->sdev, channel + 1, value); } +static int ssam_temp_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + const struct ssam_temp *ssam_temp = dev_get_drvdata(dev); + + *str = ssam_temp->names[channel]; + return 0; +} + static const struct hwmon_channel_info * const ssam_temp_hwmon_info[] = { HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), - /* We have at most 16 thermal sensor channels. */ + /* + * We have at most SSAM_TMP_SENSOR_MAX_COUNT = 16 thermal sensor + * channels. + */ HWMON_CHANNEL_INFO(temp, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT, - HWMON_T_INPUT), + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), NULL }; static const struct hwmon_ops ssam_temp_hwmon_ops = { .is_visible = ssam_temp_hwmon_is_visible, .read = ssam_temp_hwmon_read, + .read_string = ssam_temp_hwmon_read_string, }; static const struct hwmon_chip_info ssam_temp_hwmon_chip_info = { @@ -122,6 +187,7 @@ static int ssam_temp_probe(struct ssam_device *sdev) struct ssam_temp *ssam_temp; struct device *hwmon_dev; s16 sensors; + int channel; int status; status = ssam_tmp_get_available_sensors(sdev, &sensors); @@ -135,6 +201,19 @@ static int ssam_temp_probe(struct ssam_device *sdev) ssam_temp->sdev = sdev; ssam_temp->sensors = sensors; + /* Retrieve the name for each available sensor. */ + for (channel = 0; channel < SSAM_TMP_SENSOR_MAX_COUNT; channel++) + { + if (!(sensors & BIT(channel))) + continue; + + status = ssam_tmp_get_name(sdev, channel + 1, + ssam_temp->names[channel], + SSAM_TMP_SENSOR_NAME_LENGTH); + if (status) + return status; + } + hwmon_dev = devm_hwmon_device_register_with_info(&sdev->dev, "surface_thermal", ssam_temp, &ssam_temp_hwmon_chip_info, NULL); -- 2.45.1 From 54bfa02fe865b9f22d79b112a5244ce81e4961f1 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 30 Dec 2023 18:21:12 +0100 Subject: [PATCH] platform/surface: aggregator_registry: Add support for thermal sensors on the Surface Pro 9 The Surface Pro 9 has thermal sensors connected via the Surface Aggregator Module. Add a device node to support those. Signed-off-by: Maximilian Luz 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 34df1bdad83bd..c0bf0cadcd258 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -74,6 +74,12 @@ static const struct software_node ssam_node_tmp_pprof = { .parent = &ssam_node_root, }; +/* Thermal sensors. */ +static const struct software_node ssam_node_tmp_sensors = { + .name = "ssam:01:03:01:00:02", + .parent = &ssam_node_root, +}; + /* Fan speed function. */ static const struct software_node ssam_node_fan_speed = { .name = "ssam:01:05:01:01:01", @@ -341,6 +347,7 @@ static const struct software_node *ssam_node_group_sp9[] = { &ssam_node_bat_ac, &ssam_node_bat_main, &ssam_node_tmp_pprof, + &ssam_node_tmp_sensors, &ssam_node_fan_speed, &ssam_node_pos_tablet_switch, &ssam_node_hid_kip_keyboard, -- 2.45.1 From 06c4b5ac6b6357227e45c53643729140d794b48d Mon Sep 17 00:00:00 2001 From: Ivor Wanders Date: Sat, 16 Dec 2023 15:56:39 -0500 Subject: [PATCH] platform/surface: platform_profile: add fan profile switching Change naming from tmp to platform profile to clarify the module may interact with both the TMP and FAN subystems. Add functionality that switches the fan profile when the platform profile is changed. Signed-off-by: Ivor Wanders Patchset: surface-sam --- .../surface/surface_aggregator_registry.c | 38 +++++--- .../surface/surface_platform_profile.c | 86 ++++++++++++++++--- 2 files changed, 100 insertions(+), 24 deletions(-) diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index c0bf0cadcd258..07a4c4e1120d3 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -68,8 +68,8 @@ 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 = { +/* Platform profile / performance-mode device without a fan. */ +static const struct software_node ssam_node_tmp_perf_profile = { .name = "ssam:01:03:01:00:01", .parent = &ssam_node_root, }; @@ -86,6 +86,20 @@ static const struct software_node ssam_node_fan_speed = { .parent = &ssam_node_root, }; +/* Platform profile / performance-mode device with a fan, such that + * the fan controller profile can also be switched. + */ +static const struct property_entry ssam_node_tmp_perf_profile_has_fan[] = { + PROPERTY_ENTRY_BOOL("has_fan"), + { } +}; + +static const struct software_node ssam_node_tmp_perf_profile_with_fan = { + .name = "ssam:01:03:01:00:01", + .parent = &ssam_node_root, + .properties = ssam_node_tmp_perf_profile_has_fan, +}; + /* Tablet-mode switch via KIP subsystem. */ static const struct software_node ssam_node_kip_tablet_switch = { .name = "ssam:01:0e:01:00:01", @@ -214,7 +228,7 @@ static const struct software_node ssam_node_pos_tablet_switch = { */ static const struct software_node *ssam_node_group_gen5[] = { &ssam_node_root, - &ssam_node_tmp_pprof, + &ssam_node_tmp_perf_profile, NULL, }; @@ -225,7 +239,7 @@ 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, + &ssam_node_tmp_perf_profile, &ssam_node_bas_dtx, &ssam_node_hid_base_keyboard, &ssam_node_hid_base_touchpad, @@ -239,7 +253,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, + &ssam_node_tmp_perf_profile, &ssam_node_hid_main_keyboard, &ssam_node_hid_main_touchpad, &ssam_node_hid_main_iid5, @@ -251,7 +265,7 @@ static const struct software_node *ssam_node_group_sl5[] = { &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, - &ssam_node_tmp_pprof, + &ssam_node_tmp_perf_profile, &ssam_node_hid_main_keyboard, &ssam_node_hid_main_touchpad, &ssam_node_hid_main_iid5, @@ -280,7 +294,7 @@ static const struct software_node *ssam_node_group_sls1[] = { &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, - &ssam_node_tmp_pprof, + &ssam_node_tmp_perf_profile, &ssam_node_pos_tablet_switch, &ssam_node_hid_sam_keyboard, &ssam_node_hid_sam_penstash, @@ -296,7 +310,7 @@ static const struct software_node *ssam_node_group_sls2[] = { &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, - &ssam_node_tmp_pprof, + &ssam_node_tmp_perf_profile, &ssam_node_pos_tablet_switch, &ssam_node_hid_sam_keyboard, &ssam_node_hid_sam_penstash, @@ -310,7 +324,7 @@ static const struct software_node *ssam_node_group_slg1[] = { &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, - &ssam_node_tmp_pprof, + &ssam_node_tmp_perf_profile, NULL, }; @@ -319,7 +333,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, + &ssam_node_tmp_perf_profile, NULL, }; @@ -329,7 +343,7 @@ static const struct software_node *ssam_node_group_sp8[] = { &ssam_node_hub_kip, &ssam_node_bat_ac, &ssam_node_bat_main, - &ssam_node_tmp_pprof, + &ssam_node_tmp_perf_profile, &ssam_node_kip_tablet_switch, &ssam_node_hid_kip_keyboard, &ssam_node_hid_kip_penstash, @@ -346,7 +360,7 @@ static const struct software_node *ssam_node_group_sp9[] = { &ssam_node_hub_kip, &ssam_node_bat_ac, &ssam_node_bat_main, - &ssam_node_tmp_pprof, + &ssam_node_tmp_perf_profile_with_fan, &ssam_node_tmp_sensors, &ssam_node_fan_speed, &ssam_node_pos_tablet_switch, diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c index a5a3941b3f43a..e54d0a8f7daa5 100644 --- a/drivers/platform/surface/surface_platform_profile.c +++ b/drivers/platform/surface/surface_platform_profile.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0+ /* * Surface Platform Profile / Performance Mode driver for Surface System - * Aggregator Module (thermal subsystem). + * Aggregator Module (thermal and fan subsystem). * * Copyright (C) 2021-2022 Maximilian Luz */ @@ -14,6 +14,7 @@ #include +// Enum for the platform performance profile sent to the TMP module. enum ssam_tmp_profile { SSAM_TMP_PROFILE_NORMAL = 1, SSAM_TMP_PROFILE_BATTERY_SAVER = 2, @@ -21,15 +22,26 @@ enum ssam_tmp_profile { SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4, }; +// Enum for the fan profile sent to the FAN module. This fan profile is +// only sent to the EC if the 'has_fan' property is set. The integers are +// not a typo, they differ from the performance profile indices. +enum ssam_fan_profile { + SSAM_FAN_PROFILE_NORMAL = 2, + SSAM_FAN_PROFILE_BATTERY_SAVER = 1, + SSAM_FAN_PROFILE_BETTER_PERFORMANCE = 3, + SSAM_FAN_PROFILE_BEST_PERFORMANCE = 4, +}; + struct ssam_tmp_profile_info { __le32 profile; __le16 unknown1; __le16 unknown2; } __packed; -struct ssam_tmp_profile_device { +struct ssam_platform_profile_device { struct ssam_device *sdev; struct platform_profile_handler handler; + bool has_fan; }; SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, { @@ -42,6 +54,13 @@ SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, { .command_id = 0x03, }); +SSAM_DEFINE_SYNC_REQUEST_W(__ssam_fan_profile_set, char, { + .target_category = SSAM_SSH_TC_FAN, + .target_id = SSAM_SSH_TID_SAM, + .command_id = 0x0e, + .instance_id = 0x01, +}); + static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p) { struct ssam_tmp_profile_info info; @@ -62,7 +81,14 @@ static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile 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) +static int ssam_fan_profile_set(struct ssam_device *sdev, enum ssam_fan_profile p) +{ + char profile = p; + + return ssam_retry(__ssam_fan_profile_set, sdev->ctrl, &profile); +} + +static int convert_ssam_tmp_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p) { switch (p) { case SSAM_TMP_PROFILE_NORMAL: @@ -83,7 +109,8 @@ static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profi } } -static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p) + +static int convert_profile_to_ssam_tmp(struct ssam_device *sdev, enum platform_profile_option p) { switch (p) { case PLATFORM_PROFILE_LOW_POWER: @@ -105,20 +132,42 @@ static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profi } } +static int convert_profile_to_ssam_fan(struct ssam_device *sdev, enum platform_profile_option p) +{ + switch (p) { + case PLATFORM_PROFILE_LOW_POWER: + return SSAM_FAN_PROFILE_BATTERY_SAVER; + + case PLATFORM_PROFILE_BALANCED: + return SSAM_FAN_PROFILE_NORMAL; + + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: + return SSAM_FAN_PROFILE_BETTER_PERFORMANCE; + + case PLATFORM_PROFILE_PERFORMANCE: + return SSAM_FAN_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; + struct ssam_platform_profile_device *tpd; enum ssam_tmp_profile tp; int status; - tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); + tpd = container_of(pprof, struct ssam_platform_profile_device, handler); status = ssam_tmp_profile_get(tpd->sdev, &tp); if (status) return status; - status = convert_ssam_to_profile(tpd->sdev, tp); + status = convert_ssam_tmp_to_profile(tpd->sdev, tp); if (status < 0) return status; @@ -129,21 +178,32 @@ static int ssam_platform_profile_get(struct platform_profile_handler *pprof, static int ssam_platform_profile_set(struct platform_profile_handler *pprof, enum platform_profile_option profile) { - struct ssam_tmp_profile_device *tpd; + struct ssam_platform_profile_device *tpd; int tp; - tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); + tpd = container_of(pprof, struct ssam_platform_profile_device, handler); + + tp = convert_profile_to_ssam_tmp(tpd->sdev, profile); + if (tp < 0) + return tp; - tp = convert_profile_to_ssam(tpd->sdev, profile); + tp = ssam_tmp_profile_set(tpd->sdev, tp); if (tp < 0) return tp; - return ssam_tmp_profile_set(tpd->sdev, tp); + if (tpd->has_fan) { + tp = convert_profile_to_ssam_fan(tpd->sdev, profile); + if (tp < 0) + return tp; + tp = ssam_fan_profile_set(tpd->sdev, tp); + } + + return tp; } static int surface_platform_profile_probe(struct ssam_device *sdev) { - struct ssam_tmp_profile_device *tpd; + struct ssam_platform_profile_device *tpd; tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL); if (!tpd) @@ -154,6 +214,8 @@ static int surface_platform_profile_probe(struct ssam_device *sdev) tpd->handler.profile_get = ssam_platform_profile_get; tpd->handler.profile_set = ssam_platform_profile_set; + tpd->has_fan = device_property_read_bool(&sdev->dev, "has_fan"); + 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); -- 2.45.1 From bf55987e82f9ae913b51a6a269fc1a397930f049 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 d6037a3286690..a290ebc77aea2 100644 --- a/drivers/i2c/i2c-core-acpi.c +++ b/drivers/i2c/i2c-core-acpi.c @@ -628,6 +628,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, @@ -729,6 +751,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.45.1 From ebfda7ad73dd90971be10e9d6f59c51c781accbb 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 b629e82af97c0..68656e8f309ed 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH Select M or Y here, if you want to provide tablet-mode switch input events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. +config SURFACE_BOOK1_DGPU_SWITCH + tristate "Surface Book 1 dGPU Switch Driver" + depends on SYSFS + help + This driver provides a sysfs switch to set the power-state of the + discrete GPU found on the Microsoft Surface Book 1. + config SURFACE_DTX tristate "Surface DTX (Detachment System) Driver" depends on SURFACE_AGGREGATOR diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 53344330939bf..7efcd0cdb5329 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o +obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c new file mode 100644 index 0000000000000..8b816ed8f35c6 --- /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.45.1 From 1e315f586b0b2bc375b96bb538a3be4c0b09d1ea Mon Sep 17 00:00:00 2001 From: Sachi King Date: Tue, 5 Oct 2021 00:05:09 +1100 Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices The power button on the AMD variant of the Surface Laptop uses the same MSHW0040 device ID as the 5th and later generation of Surface devices, however they report 0 for their OEM platform revision. As the _DSM does not exist on the devices requiring special casing, check for the existance of the _DSM to determine if soc_button_array should be loaded. Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") Co-developed-by: Maximilian Luz Signed-off-by: Sachi King Patchset: surface-button --- drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c index f6d060377d189..b8603f74eb286 100644 --- a/drivers/input/misc/soc_button_array.c +++ b/drivers/input/misc/soc_button_array.c @@ -540,8 +540,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned * devices use MSHW0040 for power and volume buttons, however the way they * have to be addressed differs. Make sure that we only load this drivers - * for the correct devices by checking the OEM Platform Revision provided by - * the _DSM method. + * for the correct devices by checking if the OEM Platform Revision DSM call + * exists. */ #define MSHW0040_DSM_REVISION 0x01 #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision @@ -552,31 +552,14 @@ static const guid_t MSHW0040_DSM_UUID = static int soc_device_check_MSHW0040(struct device *dev) { acpi_handle handle = ACPI_HANDLE(dev); - union acpi_object *result; - u64 oem_platform_rev = 0; // valid revisions are nonzero - - // get OEM platform revision - result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, - MSHW0040_DSM_REVISION, - MSHW0040_DSM_GET_OMPR, NULL, - ACPI_TYPE_INTEGER); - - if (result) { - oem_platform_rev = result->integer.value; - ACPI_FREE(result); - } - - /* - * If the revision is zero here, the _DSM evaluation has failed. This - * indicates that we have a Pro 4 or Book 1 and this driver should not - * be used. - */ - if (oem_platform_rev == 0) - return -ENODEV; + bool exists; - dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); + // check if OEM platform revision DSM call exists + exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, + MSHW0040_DSM_REVISION, + BIT(MSHW0040_DSM_GET_OMPR)); - return 0; + return exists ? 0 : -ENODEV; } /* -- 2.45.1 From ac551644781bce2145c901b16779114b273c4d49 Mon Sep 17 00:00:00 2001 From: Sachi King Date: Tue, 5 Oct 2021 00:22:57 +1100 Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd variant The AMD variant of the Surface Laptop report 0 for their OEM platform revision. The Surface devices that require the surfacepro3_button driver do not have the _DSM that gets the OEM platform revision. If the method does not exist, load surfacepro3_button. Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") Co-developed-by: Maximilian Luz Signed-off-by: Sachi King Patchset: surface-button --- drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c index 2755601f979cd..4240c98ca2265 100644 --- a/drivers/platform/surface/surfacepro3_button.c +++ b/drivers/platform/surface/surfacepro3_button.c @@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) /* * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device * ID (MSHW0040) for the power/volume buttons. Make sure this is the right - * device by checking for the _DSM method and OEM Platform Revision. + * device by checking for the _DSM method and OEM Platform Revision DSM + * function. * * Returns true if the driver should bind to this device, i.e. the device is * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. @@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) static bool surface_button_check_MSHW0040(struct acpi_device *dev) { acpi_handle handle = dev->handle; - union acpi_object *result; - u64 oem_platform_rev = 0; // valid revisions are nonzero - - // get OEM platform revision - result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, - MSHW0040_DSM_REVISION, - MSHW0040_DSM_GET_OMPR, - NULL, ACPI_TYPE_INTEGER); - - /* - * If evaluating the _DSM fails, the method is not present. This means - * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we - * should use this driver. We use revision 0 indicating it is - * unavailable. - */ - - if (result) { - oem_platform_rev = result->integer.value; - ACPI_FREE(result); - } - - dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); - return oem_platform_rev == 0; + // make sure that OEM platform revision DSM call does not exist + return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, + MSHW0040_DSM_REVISION, + BIT(MSHW0040_DSM_GET_OMPR)); } -- 2.45.1 From 9e0b83c9668c3d0e8e5ce9c254697056940a205d Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 19 Feb 2023 22:12:24 +0100 Subject: [PATCH] PCI: Add quirk to prevent calling shutdown mehtod Work around buggy EFI firmware: On some Microsoft Surface devices (Surface Pro 9 and Surface Laptop 5) the EFI ResetSystem call with EFI_RESET_SHUTDOWN doesn't function properly. Instead of shutting the system down, it returns and the system stays on. It turns out that this only happens after PCI shutdown callbacks ran for specific devices. Excluding those devices from the shutdown process makes the ResetSystem call work as expected. TODO: Maybe we can find a better way or the root cause of this? Not-Signed-off-by: Maximilian Luz Patchset: surface-shutdown --- drivers/pci/pci-driver.c | 3 +++ drivers/pci/quirks.c | 36 ++++++++++++++++++++++++++++++++++++ include/linux/pci.h | 1 + 3 files changed, 40 insertions(+) diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index af2996d0d17ff..3ce0fb61257dc 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -505,6 +505,9 @@ static void pci_device_shutdown(struct device *dev) struct pci_dev *pci_dev = to_pci_dev(dev); struct pci_driver *drv = pci_dev->driver; + if (pci_dev->no_shutdown) + return; + pm_runtime_resume(dev); if (drv && drv->shutdown) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index eff7f5df08e27..d1cb4ff3ebc57 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -6253,3 +6253,39 @@ static void pci_fixup_d3cold_delay_1sec(struct pci_dev *pdev) pdev->d3cold_delay = 1000; } DECLARE_PCI_FIXUP_FINAL(0x5555, 0x0004, pci_fixup_d3cold_delay_1sec); + +static const struct dmi_system_id no_shutdown_dmi_table[] = { + /* + * Systems on which some devices should not be touched during shutdown. + */ + { + .ident = "Microsoft Surface Pro 9", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), + }, + }, + { + .ident = "Microsoft Surface Laptop 5", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), + }, + }, + {} +}; + +static void quirk_no_shutdown(struct pci_dev *dev) +{ + if (!dmi_check_system(no_shutdown_dmi_table)) + return; + + dev->no_shutdown = 1; + pci_info(dev, "disabling shutdown ops for [%04x:%04x]\n", + dev->vendor, dev->device); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461e, quirk_no_shutdown); // Thunderbolt 4 USB Controller +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU diff --git a/include/linux/pci.h b/include/linux/pci.h index 16493426a04ff..0eb821624056d 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -465,6 +465,7 @@ struct pci_dev { unsigned int no_command_memory:1; /* No PCI_COMMAND_MEMORY */ unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ + unsigned int no_shutdown:1; /* Do not touch device on shutdown */ pci_dev_flags_t dev_flags; atomic_t enable_cnt; /* pci_enable_device has been called */ -- 2.45.1 From 739ab84095d8ea8ec8fe05447706c6eb2ffa3f35 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 12 Mar 2023 01:41:57 +0100 Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 9 Add the lid GPE used by the Surface Pro 9. Signed-off-by: Maximilian Luz Patchset: surface-gpe --- drivers/platform/surface/surface_gpe.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c index 62fd4004db31a..103fc4468262a 100644 --- a/drivers/platform/surface/surface_gpe.c +++ b/drivers/platform/surface/surface_gpe.c @@ -41,6 +41,11 @@ static const struct property_entry lid_device_props_l4F[] = { {}, }; +static const struct property_entry lid_device_props_l52[] = { + PROPERTY_ENTRY_U32("gpe", 0x52), + {}, +}; + static const struct property_entry lid_device_props_l57[] = { PROPERTY_ENTRY_U32("gpe", 0x57), {}, @@ -107,6 +112,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { }, .driver_data = (void *)lid_device_props_l4B, }, + { + /* + * We match for SKU here due to product name clash with the ARM + * version. + */ + .ident = "Surface Pro 9", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_9_2038"), + }, + .driver_data = (void *)lid_device_props_l52, + }, { .ident = "Surface Book 1", .matches = { -- 2.45.1 From 44ed44fe421e484bcf2de223d7e8077302635772 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 10 Oct 2021 20:56:57 +0200 Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an INT3472 device The clk and regulator frameworks expect clk/regulator consumer-devices to have info about the consumed clks/regulators described in the device's fw_node. To work around cases where this info is not present in the firmware tables, which is often the case on x86/ACPI devices, both frameworks allow the provider-driver to attach info about consumers to the clks/regulators when registering these. This causes problems with the probe ordering wrt drivers for consumers of these clks/regulators. Since the lookups are only registered when the provider-driver binds, trying to get these clks/regulators before then results in a -ENOENT error for clks and a dummy regulator for regulators. One case where we hit this issue is camera sensors such as e.g. the OV8865 sensor found on the Microsoft Surface Go. The sensor uses clks, regulators and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 ACPI device. There is special platform code handling this and setting platform_data with the necessary consumer info on the MFD cells instantiated for the PMIC under: drivers/platform/x86/intel/int3472. For this to work properly the ov8865 driver must not bind to the I2C-client for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and clk MFD cells have all been fully setup. The OV8865 on the Microsoft Surface Go is just one example, all X86 devices using the Intel IPU3 camera block found on recent Intel SoCs have similar issues where there is an INT3472 HID ACPI-device, which describes the clks and regulators, and the driver for this INT3472 device must be fully initialized before the sensor driver (any sensor driver) binds for things to work properly. On these devices the ACPI nodes describing the sensors all have a _DEP dependency on the matching INT3472 ACPI device (there is one per sensor). This allows solving the probe-ordering problem by delaying the enumeration (instantiation of the I2C-client in the ov8865 example) of ACPI-devices which have a _DEP dependency on an INT3472 device. The new acpi_dev_ready_for_enumeration() helper used for this is also exported because for devices, which have the enumeration_by_parent flag set, the parent-driver will do its own scan of child ACPI devices and it will try to enumerate those during its probe(). Code doing this such as e.g. the i2c-core-acpi.c code must call this new helper to ensure that it too delays the enumeration until all the _DEP dependencies are met on devices which have the new honor_deps flag set. Signed-off-by: Hans de Goede Patchset: cameras --- drivers/acpi/scan.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index d1464324de951..5d865a34dd9db 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -2181,6 +2181,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, static void acpi_default_enumeration(struct acpi_device *device) { + if (!acpi_dev_ready_for_enumeration(device)) + return; + /* * Do not enumerate devices with enumeration_by_parent flag set as * they will be enumerated by their respective parents. -- 2.45.1 From d9d5afcea9880a957d6a8d975a122b4f54ec28c2 Mon Sep 17 00:00:00 2001 From: zouxiaoh Date: Fri, 25 Jun 2021 08:52:59 +0800 Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, The IPU driver allocates its own page table that is not mapped via the DMA, and thus the Intel IOMMU driver blocks access giving this error: DMAR: DRHD: handling fault status reg 3 DMAR: [DMA Read] Request device [00:05.0] PASID ffffffff fault addr 76406000 [fault reason 06] PTE Read access is not set As IPU is not an external facing device which is not risky, so use IOMMU passthrough mode for Intel IPUs. Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 Tracked-On: #JIITL8-411 Signed-off-by: Bingbu Cao Signed-off-by: zouxiaoh Signed-off-by: Xu Chongyang Patchset: cameras --- drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index 61bc54299a591..a61af0f4e9fce 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -44,6 +44,13 @@ ((pdev)->vendor == PCI_VENDOR_ID_INTEL && (pdev)->device == 0x34E4) \ ) +#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ + ((pdev)->device == 0x9a19 || \ + (pdev)->device == 0x9a39 || \ + (pdev)->device == 0x4e19 || \ + (pdev)->device == 0x465d || \ + (pdev)->device == 0x1919)) + #define IOAPIC_RANGE_START (0xfee00000) #define IOAPIC_RANGE_END (0xfeefffff) #define IOVA_START_ADDR (0x1000) @@ -227,12 +234,14 @@ int intel_iommu_enabled = 0; EXPORT_SYMBOL_GPL(intel_iommu_enabled); static int dmar_map_ipts = 1; +static int dmar_map_ipu = 1; static int intel_iommu_superpage = 1; static int iommu_identity_mapping; static int iommu_skip_te_disable; static int disable_igfx_iommu; #define IDENTMAP_AZALIA 4 +#define IDENTMAP_IPU 8 #define IDENTMAP_IPTS 16 const struct iommu_ops intel_iommu_ops; @@ -2409,6 +2418,9 @@ static int device_def_domain_type(struct device *dev) if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) return IOMMU_DOMAIN_IDENTITY; + if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) + return IOMMU_DOMAIN_IDENTITY; + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) return IOMMU_DOMAIN_IDENTITY; } @@ -2711,6 +2723,9 @@ static int __init init_dmars(void) iommu_set_root_entry(iommu); } + if (!dmar_map_ipu) + iommu_identity_mapping |= IDENTMAP_IPU; + if (!dmar_map_ipts) iommu_identity_mapping |= IDENTMAP_IPTS; @@ -4884,6 +4899,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) disable_igfx_iommu = 1; } +static void quirk_iommu_ipu(struct pci_dev *dev) +{ + if (!IS_INTEL_IPU(dev)) + return; + + if (risky_device(dev)) + return; + + pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); + dmar_map_ipu = 0; +} + static void quirk_iommu_ipts(struct pci_dev *dev) { if (!IS_IPTS(dev)) @@ -4931,6 +4958,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); +/* disable IPU dmar support */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); + /* disable IPTS dmar support */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x34E4, quirk_iommu_ipts); -- 2.45.1 From bbf2fb14fae6c6bda12e156ff9d027913ab7860f Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sun, 10 Oct 2021 20:57:02 +0200 Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic can be forwarded to a device connected to the PMIC as though it were connected directly to the system bus. Enable this mode when the chip is initialised. Signed-off-by: Daniel Scally Patchset: cameras --- drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c index 1e107fd49f828..e3e1696e7f0ee 100644 --- a/drivers/platform/x86/intel/int3472/tps68470.c +++ b/drivers/platform/x86/intel/int3472/tps68470.c @@ -46,6 +46,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) return ret; } + /* Enable I2C daisy chain */ + ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); + if (ret) { + dev_err(dev, "Failed to enable i2c daisy chain\n"); + return ret; + } + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); return 0; -- 2.45.1 From bc2732708fc2a71f7fe3808aa84cc6eabbdd1285 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 2 Mar 2023 12:59:39 +0000 Subject: [PATCH] platform/x86: int3472: Remap reset GPIO for INT347E ACPI _HID INT347E represents the OmniVision 7251 camera sensor. The driver for this sensor expects a single pin named "enable", but on some Microsoft Surface platforms the sensor is assigned a single GPIO who's type flag is INT3472_GPIO_TYPE_RESET. Remap the GPIO pin's function from "reset" to "enable". This is done outside of the existing remap table since it is a more widespread discrepancy than that method is designed for. Additionally swap the polarity of the pin to match the driver's expectation. Signed-off-by: Daniel Scally Patchset: cameras --- drivers/platform/x86/intel/int3472/discrete.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 07b302e093407..1d3097bc7e487 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -83,12 +83,26 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 const char *func, u32 polarity) { int ret; + const struct acpi_device_id ov7251_ids[] = { + { "INT347E" }, + }; if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { dev_warn(int3472->dev, "Too many GPIOs mapped\n"); return -EINVAL; } + /* + * In addition to the function remap table we need to bulk remap the + * "reset" GPIO for the OmniVision 7251 sensor, as the driver for that + * expects its only GPIO pin to be called "enable" (and to have the + * opposite polarity). + */ + if (!strcmp(func, "reset") && !acpi_match_device_ids(int3472->sensor, ov7251_ids)) { + func = "enable"; + polarity = GPIO_ACTIVE_HIGH; + } + ret = skl_int3472_fill_gpiod_lookup(&int3472->gpios.table[int3472->n_sensor_gpios], agpio, func, polarity); if (ret) -- 2.45.1 From 0180b848e145642574d2a91175f92050dcec1ec7 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 21 Mar 2023 13:45:26 +0000 Subject: [PATCH] media: i2c: Clarify that gain is Analogue gain in OV7251 Update the control ID for the gain control in the ov7251 driver to V4L2_CID_ANALOGUE_GAIN. Signed-off-by: Daniel Scally Patchset: cameras --- drivers/media/i2c/ov7251.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c index 30f61e04ecaf5..9c1292ca85522 100644 --- a/drivers/media/i2c/ov7251.c +++ b/drivers/media/i2c/ov7251.c @@ -1051,7 +1051,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_EXPOSURE: ret = ov7251_set_exposure(ov7251, ctrl->val); break; - case V4L2_CID_GAIN: + case V4L2_CID_ANALOGUE_GAIN: ret = ov7251_set_gain(ov7251, ctrl->val); break; case V4L2_CID_TEST_PATTERN: @@ -1572,7 +1572,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, V4L2_CID_EXPOSURE, 1, 32, 1, 32); ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, - V4L2_CID_GAIN, 16, 1023, 1, 16); + V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, V4L2_CID_TEST_PATTERN, ARRAY_SIZE(ov7251_test_pattern_menu) - 1, -- 2.45.1 From 67553f37af4ea73f824108a08666b537c68972e5 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 22 Mar 2023 11:01:42 +0000 Subject: [PATCH] media: v4l2-core: Acquire privacy led in v4l2_async_register_subdev() The current call to v4l2_subdev_get_privacy_led() is contained in v4l2_async_register_subdev_sensor(), but that function isn't used by all the sensor drivers. Move the acquisition of the privacy led to v4l2_async_register_subdev() instead. Signed-off-by: Daniel Scally Patchset: cameras --- drivers/media/v4l2-core/v4l2-async.c | 4 ++++ drivers/media/v4l2-core/v4l2-fwnode.c | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c index 3ec323bd528b1..b55570a0142cb 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c @@ -796,6 +796,10 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd) INIT_LIST_HEAD(&sd->asc_list); + ret = v4l2_subdev_get_privacy_led(sd); + if (ret < 0) + return ret; + /* * No reference taken. The reference is held by the device (struct * v4l2_subdev.dev), and async sub-device does not exist independently diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c index 89c7192148dfb..44eca113e7727 100644 --- a/drivers/media/v4l2-core/v4l2-fwnode.c +++ b/drivers/media/v4l2-core/v4l2-fwnode.c @@ -1219,10 +1219,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) v4l2_async_subdev_nf_init(notifier, sd); - ret = v4l2_subdev_get_privacy_led(sd); - if (ret < 0) - goto out_cleanup; - ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); if (ret < 0) goto out_cleanup; -- 2.45.1 From 82dff76b0fc0e6d8fec20339edcb52fcb3eb1fac Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Tue, 21 Mar 2023 23:37:16 +0800 Subject: [PATCH] platform: x86: int3472: Add MFD cell for tps68470 LED Add MFD cell for tps68470-led. Reviewed-by: Daniel Scally Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Patchset: cameras --- drivers/platform/x86/intel/int3472/tps68470.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c index e3e1696e7f0ee..423dc555093f7 100644 --- a/drivers/platform/x86/intel/int3472/tps68470.c +++ b/drivers/platform/x86/intel/int3472/tps68470.c @@ -17,7 +17,7 @@ #define DESIGNED_FOR_CHROMEOS 1 #define DESIGNED_FOR_WINDOWS 2 -#define TPS68470_WIN_MFD_CELL_COUNT 3 +#define TPS68470_WIN_MFD_CELL_COUNT 4 static const struct mfd_cell tps68470_cros[] = { { .name = "tps68470-gpio" }, @@ -200,7 +200,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) cells[1].name = "tps68470-regulator"; cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); - cells[2].name = "tps68470-gpio"; + cells[2].name = "tps68470-led"; + cells[3].name = "tps68470-gpio"; for (i = 0; i < board_data->n_gpiod_lookups; i++) gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); -- 2.45.1 From ac5b449b3e7da53dfff6ab59dc32d9714eb2f106 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Tue, 21 Mar 2023 23:37:17 +0800 Subject: [PATCH] include: mfd: tps68470: Add masks for LEDA and LEDB Add flags for both LEDA(TPS68470_ILEDCTL_ENA), LEDB (TPS68470_ILEDCTL_ENB), and current control mask for LEDB (TPS68470_ILEDCTL_CTRLB) Reviewed-by: Daniel Scally Reviewed-by: Hans de Goede Signed-off-by: Kate Hsuan Patchset: cameras --- include/linux/mfd/tps68470.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h index 7807fa329db00..2d2abb25b944f 100644 --- a/include/linux/mfd/tps68470.h +++ b/include/linux/mfd/tps68470.h @@ -34,6 +34,7 @@ #define TPS68470_REG_SGPO 0x22 #define TPS68470_REG_GPDI 0x26 #define TPS68470_REG_GPDO 0x27 +#define TPS68470_REG_ILEDCTL 0x28 #define TPS68470_REG_VCMVAL 0x3C #define TPS68470_REG_VAUX1VAL 0x3D #define TPS68470_REG_VAUX2VAL 0x3E @@ -94,4 +95,8 @@ #define TPS68470_GPIO_MODE_OUT_CMOS 2 #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 +#define TPS68470_ILEDCTL_ENA BIT(2) +#define TPS68470_ILEDCTL_ENB BIT(6) +#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) + #endif /* __LINUX_MFD_TPS68470_H */ -- 2.45.1 From 3bdaa0c3e0b7dfa3b441e7d19d3f08ff3708e354 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Tue, 21 Mar 2023 23:37:18 +0800 Subject: [PATCH] leds: tps68470: Add LED control for tps68470 There are two LED controllers, LEDA indicator LED and LEDB flash LED for tps68470. LEDA can be enabled by setting TPS68470_ILEDCTL_ENA. Moreover, tps68470 provides four levels of power status for LEDB. If the properties called "ti,ledb-current" can be found, the current will be set according to the property values. These two LEDs can be controlled through the LED class of sysfs (tps68470-leda and tps68470-ledb). Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Patchset: cameras --- drivers/leds/Kconfig | 12 +++ drivers/leds/Makefile | 1 + drivers/leds/leds-tps68470.c | 185 +++++++++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 drivers/leds/leds-tps68470.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 05e6af88b88cd..c120eb0b5aa8e 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -909,6 +909,18 @@ config LEDS_TPS6105X It is a single boost converter primarily for white LEDs and audio amplifiers. +config LEDS_TPS68470 + tristate "LED support for TI TPS68470" + depends on LEDS_CLASS + depends on INTEL_SKL_INT3472 + help + This driver supports TPS68470 PMIC with LED chip. + It provides two LED controllers, with the ability to drive 2 + indicator LEDs and 2 flash LEDs. + + To compile this driver as a module, choose M and it will be + called leds-tps68470 + config LEDS_IP30 tristate "LED support for SGI Octane machines" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index effdfc6f1e951..6ce609b2cdac6 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -86,6 +86,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o +obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c new file mode 100644 index 0000000000000..35aeb5db89c8f --- /dev/null +++ b/drivers/leds/leds-tps68470.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LED driver for TPS68470 PMIC + * + * Copyright (C) 2023 Red Hat + * + * Authors: + * Kate Hsuan + */ + +#include +#include +#include +#include +#include +#include + + +#define lcdev_to_led(led_cdev) \ + container_of(led_cdev, struct tps68470_led, lcdev) + +#define led_to_tps68470(led, index) \ + container_of(led, struct tps68470_device, leds[index]) + +enum tps68470_led_ids { + TPS68470_ILED_A, + TPS68470_ILED_B, + TPS68470_NUM_LEDS +}; + +static const char *tps68470_led_names[] = { + [TPS68470_ILED_A] = "tps68470-iled_a", + [TPS68470_ILED_B] = "tps68470-iled_b", +}; + +struct tps68470_led { + unsigned int led_id; + struct led_classdev lcdev; +}; + +struct tps68470_device { + struct device *dev; + struct regmap *regmap; + struct tps68470_led leds[TPS68470_NUM_LEDS]; +}; + +enum ctrlb_current { + CTRLB_2MA = 0, + CTRLB_4MA = 1, + CTRLB_8MA = 2, + CTRLB_16MA = 3, +}; + +static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) +{ + struct tps68470_led *led = lcdev_to_led(led_cdev); + struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); + struct regmap *regmap = tps68470->regmap; + + switch (led->led_id) { + case TPS68470_ILED_A: + return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, + brightness ? TPS68470_ILEDCTL_ENA : 0); + case TPS68470_ILED_B: + return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, + brightness ? TPS68470_ILEDCTL_ENB : 0); + } + return -EINVAL; +} + +static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) +{ + struct tps68470_led *led = lcdev_to_led(led_cdev); + struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); + struct regmap *regmap = tps68470->regmap; + int ret = 0; + int value = 0; + + ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); + if (ret) + return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); + + switch (led->led_id) { + case TPS68470_ILED_A: + value = value & TPS68470_ILEDCTL_ENA; + break; + case TPS68470_ILED_B: + value = value & TPS68470_ILEDCTL_ENB; + break; + } + + return value ? LED_ON : LED_OFF; +} + + +static int tps68470_ledb_current_init(struct platform_device *pdev, + struct tps68470_device *tps68470) +{ + int ret = 0; + unsigned int curr; + + /* configure LEDB current if the properties can be got */ + if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { + if (curr > CTRLB_16MA) { + dev_err(&pdev->dev, + "Invalid LEDB current value: %d\n", + curr); + return -EINVAL; + } + ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, + TPS68470_ILEDCTL_CTRLB, curr); + } + return ret; +} + +static int tps68470_leds_probe(struct platform_device *pdev) +{ + int i = 0; + int ret = 0; + struct tps68470_device *tps68470; + struct tps68470_led *led; + struct led_classdev *lcdev; + + tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), + GFP_KERNEL); + if (!tps68470) + return -ENOMEM; + + tps68470->dev = &pdev->dev; + tps68470->regmap = dev_get_drvdata(pdev->dev.parent); + + for (i = 0; i < TPS68470_NUM_LEDS; i++) { + led = &tps68470->leds[i]; + lcdev = &led->lcdev; + + led->led_id = i; + + lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", + tps68470_led_names[i], LED_FUNCTION_INDICATOR); + if (!lcdev->name) + return -ENOMEM; + + lcdev->max_brightness = 1; + lcdev->brightness = 0; + lcdev->brightness_set_blocking = tps68470_brightness_set; + lcdev->brightness_get = tps68470_brightness_get; + lcdev->dev = &pdev->dev; + + ret = devm_led_classdev_register(tps68470->dev, lcdev); + if (ret) { + dev_err_probe(tps68470->dev, ret, + "error registering led\n"); + goto err_exit; + } + + if (i == TPS68470_ILED_B) { + ret = tps68470_ledb_current_init(pdev, tps68470); + if (ret) + goto err_exit; + } + } + +err_exit: + if (ret) { + for (i = 0; i < TPS68470_NUM_LEDS; i++) { + if (tps68470->leds[i].lcdev.name) + devm_led_classdev_unregister(&pdev->dev, + &tps68470->leds[i].lcdev); + } + } + + return ret; +} +static struct platform_driver tps68470_led_driver = { + .driver = { + .name = "tps68470-led", + }, + .probe = tps68470_leds_probe, +}; + +module_platform_driver(tps68470_led_driver); + +MODULE_ALIAS("platform:tps68470-led"); +MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); +MODULE_LICENSE("GPL v2"); -- 2.45.1 From 05501de0dab9bc531918dcf2b8aa7e679760fd7b Mon Sep 17 00:00:00 2001 From: mojyack Date: Sat, 3 Feb 2024 12:59:53 +0900 Subject: [PATCH] media: staging: ipu3-imgu: Fix multiple calls of s_stream on stream stop Adapt to 009905e "media: v4l2-subdev: Document and enforce .s_stream() requirements" Patchset: cameras --- drivers/staging/media/ipu3/ipu3-v4l2.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/staging/media/ipu3/ipu3-v4l2.c b/drivers/staging/media/ipu3/ipu3-v4l2.c index 3df58eb3e8822..81aff2d5d8988 100644 --- a/drivers/staging/media/ipu3/ipu3-v4l2.c +++ b/drivers/staging/media/ipu3/ipu3-v4l2.c @@ -538,18 +538,18 @@ static void imgu_vb2_stop_streaming(struct vb2_queue *vq) WARN_ON(!node->enabled); - pipe = node->pipe; - dev_dbg(dev, "Try to stream off node [%u][%u]", pipe, node->id); - imgu_pipe = &imgu->imgu_pipe[pipe]; - r = v4l2_subdev_call(&imgu_pipe->imgu_sd.subdev, video, s_stream, 0); - if (r) - dev_err(&imgu->pci_dev->dev, - "failed to stop subdev streaming\n"); - mutex_lock(&imgu->streaming_lock); /* Was this the first node with streaming disabled? */ if (imgu->streaming && imgu_all_nodes_streaming(imgu, node)) { /* Yes, really stop streaming now */ + pipe = node->pipe; + dev_dbg(dev, "Try to stream off node [%u][%u]", pipe, node->id); + imgu_pipe = &imgu->imgu_pipe[pipe]; + r = v4l2_subdev_call(&imgu_pipe->imgu_sd.subdev, video, s_stream, 0); + if (r) + dev_err(&imgu->pci_dev->dev, + "failed to stop subdev streaming\n"); + dev_dbg(dev, "IMGU streaming is ready to stop"); r = imgu_s_stream(imgu, false); if (!r) -- 2.45.1 From c85328d7df3de31a574e42f66192c6a944f48bde Mon Sep 17 00:00:00 2001 From: mojyack Date: Tue, 26 Mar 2024 05:55:44 +0900 Subject: [PATCH] media: i2c: dw9719: fix probe error on surface go 2 On surface go 2, sometimes probing dw9719 fails with "dw9719: probe of i2c-INT347A:00-VCM failed with error -121". The -121(-EREMOTEIO) is came from drivers/i2c/busses/i2c-designware-common.c:575, and indicates the initialize occurs too early. So just add some delay. There is no exact reason for this 10000us, but 100us failed. Patchset: cameras --- drivers/media/i2c/dw9719.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c index c626ed845928c..0094cfda57ea8 100644 --- a/drivers/media/i2c/dw9719.c +++ b/drivers/media/i2c/dw9719.c @@ -82,6 +82,9 @@ static int dw9719_power_up(struct dw9719_device *dw9719) if (ret) return ret; + /* Wait for device to be acknowledged */ + fsleep(10000); + /* Jiggle SCL pin to wake up device */ cci_write(dw9719->regmap, DW9719_CONTROL, 1, &ret); -- 2.45.1 From e0b584d3054d7c69d2e1b4f2ca54e42b79ee5446 Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sat, 29 May 2021 17:47:38 +1000 Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 override This patch is the work of Thomas Gleixner and is copied from: https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin setup that is missing in the laptops ACPI table. This patch was used for validation of the issue, and is not a proper fix, but is probably a better temporary hack than continuing to probe the Legacy PIC and run with the PIC in an unknown state. Patchset: amd-gpio --- arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index 4bf82dbd2a6b5..7a8cb090c6568 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -1216,6 +1217,17 @@ static void __init mp_config_acpi_legacy_irqs(void) } } +static const struct dmi_system_id surface_quirk[] __initconst = { + { + .ident = "Microsoft Surface Laptop 4 (AMD)", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, + {} +}; + /* * Parse IOAPIC related entries in MADT * returns 0 on success, < 0 on error @@ -1271,6 +1283,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, acpi_gbl_FADT.sci_interrupt); + if (dmi_check_system(surface_quirk)) { + pr_warn("Surface hack: Override irq 7\n"); + mp_override_legacy_irq(7, 3, 3, 7); + } + /* Fill in identity legacy mappings where no override */ mp_config_acpi_legacy_irqs(); -- 2.45.1 From dafa7b45eecb1e02dc857158566d2fb3087fa71f Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Thu, 3 Jun 2021 14:04:26 +0200 Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override quirk The 13" version of the Surface Laptop 4 has the same problem as the 15" version, but uses a different SKU. Add that SKU to the quirk as well. Patchset: amd-gpio --- arch/x86/kernel/acpi/boot.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index 7a8cb090c6568..0faafc323e673 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -1219,12 +1219,19 @@ static void __init mp_config_acpi_legacy_irqs(void) static const struct dmi_system_id surface_quirk[] __initconst = { { - .ident = "Microsoft Surface Laptop 4 (AMD)", + .ident = "Microsoft Surface Laptop 4 (AMD 15\")", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") }, }, + { + .ident = "Microsoft Surface Laptop 4 (AMD 13\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") + }, + }, {} }; -- 2.45.1 From e3e62473f885786f1b57421f5fed86ac5ba402ac Mon Sep 17 00:00:00 2001 From: "Bart Groeneveld | GPX Solutions B.V" Date: Mon, 5 Dec 2022 16:08:46 +0100 Subject: [PATCH] acpi: allow usage of acpi_tad on HW-reduced platforms The specification [1] allows so-called HW-reduced platforms, which do not implement everything, especially the wakeup related stuff. In that case, it is still usable as a RTC. This is helpful for [2] and [3], which is about a device with no other working RTC, but it does have an HW-reduced TAD, which can be used as a RTC instead. [1]: https://uefi.org/specs/ACPI/6.5/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#time-and-alarm-device [2]: https://bugzilla.kernel.org/show_bug.cgi?id=212313 [3]: https://github.com/linux-surface/linux-surface/issues/415 Signed-off-by: Bart Groeneveld | GPX Solutions B.V. Patchset: rtc --- drivers/acpi/acpi_tad.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 1d670dbe4d1dd..71c9e375ca1ca 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -432,6 +432,14 @@ static ssize_t caps_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RO(caps); +static struct attribute *acpi_tad_attrs[] = { + &dev_attr_caps.attr, + NULL, +}; +static const struct attribute_group acpi_tad_attr_group = { + .attrs = acpi_tad_attrs, +}; + static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -480,15 +488,14 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(ac_status); -static struct attribute *acpi_tad_attrs[] = { - &dev_attr_caps.attr, +static struct attribute *acpi_tad_ac_attrs[] = { &dev_attr_ac_alarm.attr, &dev_attr_ac_policy.attr, &dev_attr_ac_status.attr, NULL, }; -static const struct attribute_group acpi_tad_attr_group = { - .attrs = acpi_tad_attrs, +static const struct attribute_group acpi_tad_ac_attr_group = { + .attrs = acpi_tad_ac_attrs, }; static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, @@ -564,13 +571,18 @@ static void acpi_tad_remove(struct platform_device *pdev) pm_runtime_get_sync(dev); + if (dd->capabilities & ACPI_TAD_AC_WAKE) + sysfs_remove_group(&dev->kobj, &acpi_tad_ac_attr_group); + if (dd->capabilities & ACPI_TAD_DC_WAKE) sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); - acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); - acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); + if (dd->capabilities & ACPI_TAD_AC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); + } if (dd->capabilities & ACPI_TAD_DC_WAKE) { acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); @@ -612,12 +624,6 @@ static int acpi_tad_probe(struct platform_device *pdev) goto remove_handler; } - if (!acpi_has_method(handle, "_PRW")) { - dev_info(dev, "Missing _PRW\n"); - ret = -ENODEV; - goto remove_handler; - } - dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); if (!dd) { ret = -ENOMEM; @@ -648,6 +654,12 @@ static int acpi_tad_probe(struct platform_device *pdev) if (ret) goto fail; + if (caps & ACPI_TAD_AC_WAKE) { + ret = sysfs_create_group(&dev->kobj, &acpi_tad_ac_attr_group); + if (ret) + goto fail; + } + if (caps & ACPI_TAD_DC_WAKE) { ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); if (ret) -- 2.45.1