From 11f4a85c1156171c22e03216d13384bec651541e 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 9bea5a1e2c52..25848f886ad6 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_DLLCHARACTERISTICS_NX_COMPAT # DllCharacteristics +#else + .word 0 # DllCharacteristics +#endif #ifdef CONFIG_X86_32 .long 0 # SizeOfStackReserve .long 0 # SizeOfStackCommit -- 2.53.0 From 1e091139669d3d03f3abf3106ed2fabda98082bb Mon Sep 17 00:00:00 2001 From: "J. Eduardo" Date: Sun, 25 Aug 2024 14:17:45 +0200 Subject: [PATCH] PM: hibernate: Add a lockdown_hibernate parameter This allows the user to tell the kernel that they know better (namely, they secured their swap properly), and that it can enable hibernation. Signed-off-by: Kelvie Wong Link: https://github.com/linux-surface/kernel/pull/158 Link: https://gist.github.com/brknkfr/95d1925ccdbb7a2d18947c168dfabbee Patchset: secureboot --- Documentation/admin-guide/kernel-parameters.txt | 5 +++++ kernel/power/hibernate.c | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index aa0031108bc1..ca92d0e45220 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -3500,6 +3500,11 @@ Kernel parameters to extract confidential information from the kernel are also disabled. + lockdown_hibernate [HIBERNATION] + Enable hibernation even if lockdown is enabled. Enable this only if + your swap is encrypted and secured properly, as an attacker can + modify the kernel offline during hibernation. + locktorture.acq_writer_lim= [KNL] Set the time limit in jiffies for a lock acquisition. Acquisitions exceeding this limit diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index af8d07bafe02..9e151554d5a0 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -38,6 +38,7 @@ #include "power.h" +static int lockdown_hibernate; static int nocompress; static int noresume; static int nohibernate; @@ -109,7 +110,7 @@ bool hibernation_in_progress(void) bool hibernation_available(void) { return nohibernate == 0 && - !security_locked_down(LOCKDOWN_HIBERNATION) && + (lockdown_hibernate || !security_locked_down(LOCKDOWN_HIBERNATION)) && !secretmem_active() && !cxl_mem_active(); } @@ -1493,6 +1494,12 @@ static int __init nohibernate_setup(char *str) return 1; } +static int __init lockdown_hibernate_setup(char *str) +{ + lockdown_hibernate = 1; + return 1; +} + static const char * const comp_alg_enabled[] = { #if IS_ENABLED(CONFIG_CRYPTO_LZO) COMPRESSION_ALGO_LZO, @@ -1550,3 +1557,4 @@ __setup("hibernate=", hibernate_setup); __setup("resumewait", resumewait_setup); __setup("resumedelay=", resumedelay_setup); __setup("nohibernate", nohibernate_setup); +__setup("lockdown_hibernate", lockdown_hibernate_setup); -- 2.53.0 From 15ab9b53834ba0ab25433ecfc02ee1659b2ff95b 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 --- 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 6c8fb7a4dde4..22797a53f4d8 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 f7701b8d0d3c..0708c2fe8f61 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3793,6 +3793,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 e4c3492a0c28..0b930c91bccb 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.53.0 From c881590fdf25571ca9576ddb5fb1672092cd458a Mon Sep 17 00:00:00 2001 From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> Date: Fri, 6 Dec 2019 23:10:30 +0900 Subject: [PATCH] surface3-spi: workaround: disable DMA mode to avoid crash by default On Arch Linux kernel at least after 4.19, touch input is broken after suspend (s2idle). kern :err : [ +0.203408] Surface3-spi spi-MSHW0037:00: SPI transfer timed out On recent stable Arch Linux kernel (at least after 5.1), touch input will crash after the first touch. kern :err : [ +0.203592] Surface3-spi spi-MSHW0037:00: SPI transfer timed out kern :err : [ +0.000173] spi_master spi1: failed to transfer one message from queue I found on an affected system (Arch Linux kernel, etc.), the touchscreen driver uses DMA mode by default. Then, we found some kernels with different kernel config (5.1 kernel config from Jakeday [1] or Chromium OS kernel chromeos-4.19 [2]) will use PIO mode by default and no such issues there. So, this commit disables DMA mode on the touchscreen driver side as a quick workaround to avoid touch input crash. We may need to properly set up DMA mode to use the touchscreen driver with DMA mode. You can still switch DMA/PIO mode if you want: switch to DMA mode (maybe broken) echo 1 | sudo tee /sys/module/surface3_spi/parameters/use_dma back to PIO mode echo 0 | sudo tee /sys/module/surface3_spi/parameters/use_dma Link to issue: https://github.com/jakeday/linux-surface/issues/596 References: [1] https://github.com/jakeday/linux-surface/blob/master/configs/5.1/config [2] https://chromium.googlesource.com/chromiumos/third_party/kernel/+/refs/heads/chromeos-4.19 Tested on Arch Linux 5.4.1 with Surface 3, which will use DMA by default. This commit made the driver use PIO by default and no touch input crash. Also tested on chromeos-4.19 4.19.90 with Surface 3, which will use PIO by default even without this commit. After this commit, it still uses PIO and confirmed no functional changes regarding touch input. More details: We can confirm which mode the touchscreen driver uses; first, enable debug output: echo "file drivers/spi/spi-pxa2xx.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control echo "file drivers/input/touchscreen/surface3_spi.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control Then, try to make a touch input and see dmesg log (On Arch Linux kernel, uses DMA) kern :debug : [ +0.006383] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, DMA kern :debug : [ +0.000495] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 18 00 e4 01 00 04 1a 04 1a e3 0c e3 0c b0 00 c5 00 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff (On the kernels I referenced above, uses PIO) kern :debug : [ +0.009260] Surface3-spi spi-MSHW0037:00: 7692307 Hz actual, PIO kern :debug : [ +0.001105] Surface3-spi spi-MSHW0037:00: surface3_spi_irq_handler received -> ff ff ff ff a5 5a e7 7e 01 d2 00 80 01 03 03 24 00 e4 01 00 58 0b 58 0b 83 12 83 12 26 01 95 01 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff Note (2025-03-08): This patch was originally dropped due to the comments in [3]. However, according to the commments in [4] it is still required. [3]: https://github.com/linux-surface/kernel/commit/a3421c12bed0e46c28518bcb8c6b22f237c6dc7a [4]: https://github.com/linux-surface/linux-surface/issues/1184 Patchset: surface3 --- drivers/input/touchscreen/surface3_spi.c | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/input/touchscreen/surface3_spi.c b/drivers/input/touchscreen/surface3_spi.c index 6074b7730e86..6aa3e1d6f160 100644 --- a/drivers/input/touchscreen/surface3_spi.c +++ b/drivers/input/touchscreen/surface3_spi.c @@ -25,6 +25,12 @@ #define SURFACE3_REPORT_TOUCH 0xd2 #define SURFACE3_REPORT_PEN 0x16 +bool use_dma = false; +module_param(use_dma, bool, 0644); +MODULE_PARM_DESC(use_dma, + "Disable DMA mode if you encounter touch input crash. " + "(default: false, disabled to avoid crash)"); + struct surface3_ts_data { struct spi_device *spi; struct gpio_desc *gpiod_rst[2]; @@ -317,6 +323,13 @@ static int surface3_spi_create_pen_input(struct surface3_ts_data *data) return 0; } +static bool surface3_spi_can_dma(struct spi_controller *ctlr, + struct spi_device *spi, + struct spi_transfer *tfr) +{ + return use_dma; +} + static int surface3_spi_probe(struct spi_device *spi) { struct surface3_ts_data *data; @@ -359,6 +372,19 @@ static int surface3_spi_probe(struct spi_device *spi) if (error) return error; + /* + * Set up DMA + * + * TODO: Currently, touch input with DMA seems to be broken. + * On 4.19 LTS, touch input will crash after suspend. + * On recent stable kernel (at least after 5.1), touch input will crash after + * the first touch. No problem with PIO on those kernels. + * Maybe we need to configure DMA here. + * + * Link to issue: https://github.com/jakeday/linux-surface/issues/596 + */ + spi->controller->can_dma = surface3_spi_can_dma; + return 0; } -- 2.53.0 From 5c32be551d5e372447cc2f4faae21d20a07ae94e 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 a760de191fce..db9b203226a8 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 dd6d21f1dbfd..f46b06f8d643 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 d6ff964aec5b..5d30ae39d65e 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.53.0 From e4b75eaf26d9ffaa19c85875a8b6604783500e38 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 db9b203226a8..ffd0c1fe9223 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 f46b06f8d643..99b024ecbade 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 5d30ae39d65e..c14eb56eb911 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.53.0 From a15b4cd04c9904f36e0124d3175fa4daeeb1269b 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 80ccfa8fd982..093e17983eb7 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -67,6 +67,7 @@ static struct usb_driver btusb_driver; #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) #define BTUSB_ACTIONS_SEMI BIT(27) #define BTUSB_BARROT BIT(28) +#define BTUSB_LOWER_LESCAN_INTERVAL BIT(29) static const struct usb_device_id btusb_table[] = { /* Generic Bluetooth USB device */ @@ -472,6 +473,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 }, @@ -4229,6 +4231,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.53.0 From e5e36c49ec4ebd249eb220fa0028f57f1db3dc25 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 | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c index 7c2939cbde5f..eb402a3a8c5f 100644 --- a/drivers/net/wireless/ath/ath10k/core.c +++ b/drivers/net/wireless/ath/ath10k/core.c @@ -41,6 +41,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); @@ -53,6 +56,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"); @@ -62,6 +68,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, @@ -932,6 +941,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) @@ -946,6 +991,18 @@ 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); + if (ar->board_name) { snprintf(filename, sizeof(filename), "%s/%s/%s", dir, ar->board_name, file); -- 2.53.0 From 6235e3f2891f0162cf07e78fd9a2d11f072d29e3 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 fa30899a5fa2..a1864dcb713d 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 2a6e569558b9..c19dfba542c1 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.53.0 From 36f2f146cc18cc0185c65637aa7f0d1689bcf4ee 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 134302fbcd92..09d031bc873f 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) @@ -202,12 +207,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; @@ -1401,6 +1408,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; @@ -1691,6 +1701,9 @@ static int __init init_dmars(void) iommu_set_root_entry(iommu); } + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + check_tylersburg_isoch(); /* @@ -3936,6 +3949,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); @@ -3974,6 +3999,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.53.0 From af52eedf010166a262fae4b5fdd962226a5f0672 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 | 104 +++++++ drivers/hid/ipts/eds1.h | 35 +++ drivers/hid/ipts/eds2.c | 145 ++++++++++ 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 | 251 +++++++++++++++++ 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, 2853 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 920a64b66b25..314e3b0479be 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1442,6 +1442,8 @@ source "drivers/hid/surface-hid/Kconfig" source "drivers/hid/intel-thc-hid/Kconfig" +source "drivers/hid/ipts/Kconfig" + endif # HID # USB support may be used with HID disabled diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 361a7daedeb8..b73a1bbd3a1d 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -187,4 +187,6 @@ obj-$(CONFIG_HID_SURFACE) += hid-surface.o obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ +obj-$(CONFIG_HID_IPTS) += ipts/ + obj-$(CONFIG_HID_MSI_CLAW) += hid-msi-claw.o diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig new file mode 100644 index 000000000000..297401bd388d --- /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 000000000000..883896f68e6a --- /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 000000000000..63a4934bbc5f --- /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 000000000000..2b4079075b64 --- /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 000000000000..ba33259f1f7c --- /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 000000000000..5360842d260b --- /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 000000000000..26629c5144ed --- /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 000000000000..307438c7c80c --- /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 000000000000..7b9f54388a9f --- /dev/null +++ b/drivers/hid/ipts/eds1.c @@ -0,0 +1,104 @@ +// 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 "eds1.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 000000000000..eeeb6575e3e8 --- /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 000000000000..639940794615 --- /dev/null +++ b/drivers/hid/ipts/eds2.c @@ -0,0 +1,145 @@ +// 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 "eds2.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 000000000000..064e3716907a --- /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 000000000000..e34a1a4f9fa7 --- /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 000000000000..1ebe77447903 --- /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 000000000000..fb5b5c13ee3e --- /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 000000000000..1e0395ceae4a --- /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 000000000000..973bade6b0fd --- /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 000000000000..977724c728c3 --- /dev/null +++ b/drivers/hid/ipts/receiver.c @@ -0,0 +1,251 @@ +// 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 "receiver.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 000000000000..3de7da62d40c --- /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 000000000000..cc14653b2a9f --- /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 000000000000..2068e13285f0 --- /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 000000000000..e8dd98895a7e --- /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 000000000000..41845f9d9025 --- /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 000000000000..5a58d4a0a610 --- /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 000000000000..355e92bea26f --- /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 000000000000..1f966b8b32c4 --- /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.53.0 From 703eeaea6536d65c01c092e5f844a784f2d18df5 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 8bcbfe3d9c72..c78806d35f2b 100644 --- a/drivers/iommu/intel/irq_remapping.c +++ b/drivers/iommu/intel/irq_remapping.c @@ -381,6 +381,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.53.0 From 063f50f47ef7f0a0d4466f70b3450dc8335f5f24 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@34539af4726d. Signed-off-by: Maximilian 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 | 149 ++++++++ drivers/hid/ithc/ithc-debug.h | 7 + drivers/hid/ithc/ithc-dma.c | 312 ++++++++++++++++ drivers/hid/ithc/ithc-dma.h | 47 +++ drivers/hid/ithc/ithc-hid.c | 207 +++++++++++ drivers/hid/ithc/ithc-hid.h | 32 ++ drivers/hid/ithc/ithc-legacy.c | 254 +++++++++++++ drivers/hid/ithc/ithc-legacy.h | 8 + drivers/hid/ithc/ithc-main.c | 438 ++++++++++++++++++++++ drivers/hid/ithc/ithc-quickspi.c | 607 +++++++++++++++++++++++++++++++ drivers/hid/ithc/ithc-quickspi.h | 39 ++ drivers/hid/ithc/ithc-regs.c | 154 ++++++++ drivers/hid/ithc/ithc-regs.h | 211 +++++++++++ drivers/hid/ithc/ithc.h | 89 +++++ 18 files changed, 2575 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-debug.h 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-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-main.c create mode 100644 drivers/hid/ithc/ithc-quickspi.c create mode 100644 drivers/hid/ithc/ithc-quickspi.h 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 314e3b0479be..59d597b4effd 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1444,6 +1444,8 @@ source "drivers/hid/intel-thc-hid/Kconfig" source "drivers/hid/ipts/Kconfig" +source "drivers/hid/ithc/Kconfig" + endif # HID # USB support may be used with HID disabled diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index b73a1bbd3a1d..3190ece25626 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -188,5 +188,6 @@ obj-$(CONFIG_HID_SURFACE) += hid-surface.o obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ obj-$(CONFIG_HID_IPTS) += ipts/ +obj-$(CONFIG_HID_ITHC) += ithc/ obj-$(CONFIG_HID_MSI_CLAW) += hid-msi-claw.o diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild new file mode 100644 index 000000000000..4937ba131297 --- /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-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/Kconfig b/drivers/hid/ithc/Kconfig new file mode 100644 index 000000000000..ede713023609 --- /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 000000000000..2d8c6afe9966 --- /dev/null +++ b/drivers/hid/ithc/ithc-debug.c @@ -0,0 +1,149 @@ +// 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 < 1) + return -EINVAL; + 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: + return -EINVAL; + } + ithc_log_regs(ithc); + 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, +}; + +static void ithc_debugfs_devres_release(struct device *dev, void *res) +{ + struct dentry **dbgm = res; + debugfs_remove_recursive(*dbgm); +} + +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(pci_name(ithc->pci), dbg_dir); + 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-debug.h b/drivers/hid/ithc/ithc-debug.h new file mode 100644 index 000000000000..38c53d916bdb --- /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 new file mode 100644 index 000000000000..bf4eab33062b --- /dev/null +++ b/drivers/hid/ithc/ithc-dma.c @@ -0,0 +1,312 @@ +// 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. + 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, 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]); + + // 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. + 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", + 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); + + // 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_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 + 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); + } +} +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, const struct ithc_data *data) +{ + // Send a single TX buffer to the THC. + 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. + 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); + 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, const struct ithc_data *data) +{ + mutex_lock(&ithc->dma_tx.mutex); + 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 new file mode 100644 index 000000000000..1749a5819b3e --- /dev/null +++ b/drivers/hid/ithc/ithc-dma.h @@ -0,0 +1,47 @@ +/* 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; +}; + +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; + 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, 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 000000000000..065646ab499e --- /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 000000000000..599eb912c8c8 --- /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 000000000000..8883987fb352 --- /dev/null +++ b/drivers/hid/ithc/ithc-legacy.c @@ -0,0 +1,254 @@ +// 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 ADL devices. + switch (ithc->pci->device) { + case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: + case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: + 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 000000000000..28d692462072 --- /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 new file mode 100644 index 000000000000..094d878d671b --- /dev/null +++ b/drivers/hid/ithc/ithc-main.c @@ -0,0 +1,438 @@ +// 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"); + +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) }, + // MTL and up are handled by drivers/hid/intel-thc-hid + {} +}; +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"); + +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)"); + +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 unsigned int ithc_idle_delay_ms = 1000; +module_param_named(idledelay, ithc_idle_delay_ms, uint, 0); +MODULE_PARM_DESC(idleltr, "Minimum idle time before applying idle LTR value (in milliseconds)"); + +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)"); + +// Interrupts/polling + +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_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_READY | 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_idle_timer_callback(struct timer_list *t) +{ + struct ithc *ithc = container_of(t, struct ithc, idle_timer); + ithc_set_ltr_idle(ithc); +} + +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 after a delay. + mod_timer(&ithc->idle_timer, jiffies + msecs_to_jiffies(ithc_idle_delay_ms)); + + 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; + + // 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) + pci_err(ithc->pci, "DMA RX timeout/error (try decreasing activeltr/idleltr if this happens frequently)\n"); + } + + // 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); + } + + 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. + sleep = n != ithc->dma_rx[1].num_received ? 20 + : 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) +{ + // 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); + 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; + + // 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 50us, 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 + : 50 * 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); + + 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); + if (ithc->use_quickspi) + ithc_quickspi_exit(ithc); + else + ithc_legacy_exit(ithc); + ithc_disable(ithc); + timer_delete_sync(&ithc->idle_timer); + + // 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)); + 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); + + // Initialize HID and DMA. + CHECK_RET(ithc_hid_init, ithc); + 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); + + timer_setup(&ithc->idle_timer, ithc_idle_timer_callback, 0); + + // 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); + + // 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.dev); + + CHECK(ithc_debug_init_device, ithc); + + ithc_set_ltr_idle(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, + }, + .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS, +}; + +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); +module_exit(ithc_exit); + diff --git a/drivers/hid/ithc/ithc-quickspi.c b/drivers/hid/ithc/ithc-quickspi.c new file mode 100644 index 000000000000..e2d1690b8cf8 --- /dev/null +++ b/drivers/hid/ithc/ithc-quickspi.c @@ -0,0 +1,607 @@ +// 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 void set_opcode(struct ithc *ithc, size_t i, u8 opcode) +{ + writeb(opcode, &ithc->regs->opcode[i].header); + writeb(opcode, &ithc->regs->opcode[i].single); + writeb(opcode, &ithc->regs->opcode[i].dual); + writeb(opcode, &ithc->regs->opcode[i].quad); +} + +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); + writel(cfg->input_report_body_address, &ithc->regs->dma_rx[1].spi_addr); + } + if (cfg->has_output_report_body_address) + writel(cfg->output_report_body_address, &ithc->regs->dma_tx.spi_addr); + + switch (ithc->pci->device) { + // LKF/TGL don't support QuickSPI. + // For ADL, opcode layout is RX/TX/unused. + case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT1: + case PCI_DEVICE_ID_INTEL_THC_ADL_S_PORT2: + 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: + if (cfg->has_read_opcode) { + set_opcode(ithc, 0, cfg->read_opcode); + } + if (cfg->has_write_opcode) { + set_opcode(ithc, 1, cfg->write_opcode); + } + break; + // For MTL, opcode layout was changed to RX/RX/TX. + // (RPL layout is unknown.) + default: + if (cfg->has_read_opcode) { + set_opcode(ithc, 0, cfg->read_opcode); + set_opcode(ithc, 1, cfg->read_opcode); + } + if (cfg->has_write_opcode) { + set_opcode(ithc, 2, cfg->write_opcode); + } + break; + } + + ithc_log_regs(ithc); + + // The rest... + bitsl_set(&ithc->regs->dma_rx[0].init_unknown, INIT_UNKNOWN_31); + + 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 000000000000..74d882f6b2f0 --- /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 new file mode 100644 index 000000000000..c0f13506af20 --- /dev/null +++ b/drivers/hid/ithc/ithc-regs.c @@ -0,0 +1,154 @@ +// 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) +{ + 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; +} + +static void calc_ltr(u64 *ns, unsigned int *val, unsigned int *scale) +{ + 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_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 0x%x\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 000000000000..4f541fe533fa --- /dev/null +++ b/drivers/hid/ithc/ithc-regs.h @@ -0,0 +1,211 @@ +/* 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_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 +#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 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_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_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[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; + struct { + /* 1014/1018/101c */ u8 header; + /* 1015/1019/101d */ u8 quad; + /* 1016/101a/101e */ u8 dual; + /* 1017/101b/101f */ u8 single; + } opcode[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 + /* 10a0 */ u32 _unknown_10a0[5]; + /* 10b4 */ u32 spi_addr; + } dma_tx; + /* 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 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; + /* 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 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[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); + +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); + +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 new file mode 100644 index 000000000000..aec320d4e945 --- /dev/null +++ b/drivers/hid/ithc/ithc.h @@ -0,0 +1,89 @@ +/* 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 + +#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 + +// 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_S_PORT1 0x7f59 +#define PCI_DEVICE_ID_INTEL_THC_MTL_S_PORT2 0x7f5b +#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT1 0x7e49 +#define PCI_DEVICE_ID_INTEL_THC_MTL_MP_PORT2 0x7e4b + +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]; + struct pci_dev *pci; + int irq; + struct task_struct *poll_thread; + struct timer_list idle_timer; + + struct ithc_registers __iomem *regs; + struct ithc_registers *prev_regs; // for debugging + 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); + -- 2.53.0 From ce0fa0a3ecff792f7a9bfdac05d8fa21718a0dff Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 17 Jun 2022 02:14:00 +0200 Subject: [PATCH] rtc: Add basic support for RTC via Surface System Aggregator Module Signed-off-by: Maximilian Luz Patchset: surface-sam --- drivers/rtc/Kconfig | 7 +++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-surface.c | 129 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 drivers/rtc/rtc-surface.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 50dc779f7f98..63aaff40a0fd 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1423,6 +1423,13 @@ config RTC_DRV_NTXEC embedded controller found in certain e-book readers designed by the original design manufacturer Netronix. +config RTC_DRV_SURFACE + tristate "Microsoft Surface Aggregator RTC" + depends on SURFACE_AGGREGATOR + depends on SURFACE_AGGREGATOR_BUS + help + TODO + comment "on-CPU RTC drivers" config RTC_DRV_ASM9260 diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 6cf7e066314e..18bd33b84d13 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -186,6 +186,7 @@ obj-$(CONFIG_RTC_DRV_SUN4V) += rtc-sun4v.o obj-$(CONFIG_RTC_DRV_SUN6I) += rtc-sun6i.o obj-$(CONFIG_RTC_DRV_SUNPLUS) += rtc-sunplus.o obj-$(CONFIG_RTC_DRV_SUNXI) += rtc-sunxi.o +obj-$(CONFIG_RTC_DRV_SURFACE) += rtc-surface.o obj-$(CONFIG_RTC_DRV_TEGRA) += rtc-tegra.o obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o obj-$(CONFIG_RTC_DRV_TI_K3) += rtc-ti-k3.o diff --git a/drivers/rtc/rtc-surface.c b/drivers/rtc/rtc-surface.c new file mode 100644 index 000000000000..f6c17c4e98d5 --- /dev/null +++ b/drivers/rtc/rtc-surface.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AC driver for 7th-generation Microsoft Surface devices via Surface System + * Aggregator Module (SSAM). + * + * Copyright (C) 2019-2021 Maximilian Luz + */ + +#include +#include +#include +#include +#include + +#include + +struct surface_rtc { + struct ssam_device *sdev; + struct rtc_device *rtc; +}; + +SSAM_DEFINE_SYNC_REQUEST_R(__ssam_rtc_get_unix_time, __le32, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = SSAM_SSH_TID_SAM, + .instance_id = 0x00, + .command_id = 0x10, +}); + +SSAM_DEFINE_SYNC_REQUEST_W(__ssam_rtc_set_unix_time, __le32, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = SSAM_SSH_TID_SAM, + .instance_id = 0x00, + .command_id = 0x0f, +}); + +static int ssam_rtc_get_unix_time(struct surface_rtc *srtc, u32 *time) +{ + __le32 time_le; + int status; + + status = __ssam_rtc_get_unix_time(srtc->sdev->ctrl, &time_le); + if (status) + return status; + + *time = le32_to_cpu(time_le); + return 0; +} + +static int ssam_rtc_set_unix_time(struct surface_rtc *srtc, u32 time) +{ + __le32 time_le = cpu_to_le32(time); + + return __ssam_rtc_set_unix_time(srtc->sdev->ctrl, &time_le); +} + +static int surface_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct surface_rtc *srtc = dev_get_drvdata(dev); + int status; + u32 time; + + status = ssam_rtc_get_unix_time(srtc, &time); + if (status) + return status; + + rtc_time64_to_tm(time, tm); + return 0; +} + +static int surface_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct surface_rtc *srtc = dev_get_drvdata(dev); + time64_t time = rtc_tm_to_time64(tm); + + return ssam_rtc_set_unix_time(srtc, (u32)time); +} + +static const struct rtc_class_ops surface_rtc_ops = { + .read_time = surface_rtc_read_time, + .set_time = surface_rtc_set_time, +}; + +static int surface_rtc_probe(struct ssam_device *sdev) +{ + struct surface_rtc *srtc; + + srtc = devm_kzalloc(&sdev->dev, sizeof(*srtc), GFP_KERNEL); + if (!srtc) + return -ENOMEM; + + srtc->sdev = sdev; + + srtc->rtc = devm_rtc_allocate_device(&sdev->dev); + if (IS_ERR(srtc->rtc)) + return PTR_ERR(srtc->rtc); + + srtc->rtc->ops = &surface_rtc_ops; + srtc->rtc->range_max = U32_MAX; + + ssam_device_set_drvdata(sdev, srtc); + + return devm_rtc_register_device(srtc->rtc); +} + +static void surface_rtc_remove(struct ssam_device *sdev) +{ + /* Device-managed allocations take care of everything... */ +} + +static const struct ssam_device_id surface_rtc_match[] = { + { SSAM_SDEV(SAM, SAM, 0x00, 0x00) }, + { }, +}; +MODULE_DEVICE_TABLE(ssam, surface_rtc_match); + +static struct ssam_device_driver surface_rtc_driver = { + .probe = surface_rtc_probe, + .remove = surface_rtc_remove, + .match_table = surface_rtc_match, + .driver = { + .name = "surface_rtc", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_ssam_device_driver(surface_rtc_driver); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("RTC driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); -- 2.53.0 From 88065461b3da56157ade06b0cbab4b4bf2aa5cac Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 20 Apr 2025 01:05:14 +0200 Subject: [PATCH] platform/surface: aggregator_registry: Add Surface Laptop 7 (ACPI) 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 78ac3a8fbb73..10fe004e4e21 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -460,6 +460,9 @@ static const struct acpi_device_id ssam_platform_hub_acpi_match[] = { /* Surface Laptop 6 */ { "MSHW0530", (unsigned long)ssam_node_group_sl6 }, + /* Surface Laptop 7 */ + { "MSHW0551", (unsigned long)ssam_node_group_sl7 }, + /* Surface Laptop Go 1 */ { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, -- 2.53.0 From 2e4050a5807541e471392dccf9548e88489e70b6 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 | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c index ed90858a27b7..070c36637811 100644 --- a/drivers/i2c/i2c-core-acpi.c +++ b/drivers/i2c/i2c-core-acpi.c @@ -662,6 +662,27 @@ 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; + + 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, @@ -763,6 +784,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.53.0 From 179eceb312b4dd735e9665d42637fe1805592412 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 | 136 ++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index f775c6ca1ec1..2075e3852053 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH Select M or Y here, if you want to provide tablet-mode switch input events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. +config SURFACE_BOOK1_DGPU_SWITCH + tristate "Surface Book 1 dGPU Switch Driver" + depends on SYSFS + help + This driver provides a sysfs switch to set the power-state of the + discrete GPU found on the Microsoft Surface Book 1. + config SURFACE_DTX tristate "Surface DTX (Detachment System) Driver" depends on SURFACE_AGGREGATOR diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 53344330939b..7efcd0cdb532 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o +obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c new file mode 100644 index 000000000000..68db237734a1 --- /dev/null +++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +/* MSHW0040/VGBI DSM UUID: 6fd05c69-cde3-49f4-95ed-ab1665498035 */ +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 *obj; + acpi_handle handle; + acpi_status status; + + status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); + if (status) + return -EINVAL; + + obj = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); + if (!obj) + return -EINVAL; + + ACPI_FREE(obj); + return 0; +} + +static int sb1_dgpu_sw_hgon(struct device *dev) +{ + struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; + acpi_status status; + + status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); + if (status) { + dev_err(dev, "failed to run HGON: %d\n", status); + return -EINVAL; + } + + ACPI_FREE(buf.pointer); + + dev_info(dev, "turned-on dGPU via HGON\n"); + return 0; +} + +static int sb1_dgpu_sw_hgof(struct device *dev) +{ + struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; + acpi_status status; + + status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); + if (status) { + dev_err(dev, "failed to run HGOF: %d\n", status); + return -EINVAL; + } + + ACPI_FREE(buf.pointer); + + dev_info(dev, "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) +{ + bool value; + int status; + + status = kstrtobool(buf, &value); + if (status < 0) + return status; + + if (!value) + return 0; + + status = sb1_dgpu_sw_dsmcall(); + + return status < 0 ? status : len; +} +static DEVICE_ATTR_WO(dgpu_dsmcall); + +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(dev); + else + status = sb1_dgpu_sw_hgof(dev); + + return status < 0 ? status : len; +} +static DEVICE_ATTR_WO(dgpu_power); + +static struct attribute *sb1_dgpu_sw_attrs[] = { + &dev_attr_dgpu_dsmcall.attr, + &dev_attr_dgpu_power.attr, + NULL +}; +ATTRIBUTE_GROUPS(sb1_dgpu_sw); + +/* + * 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 = { + .driver = { + .name = "surfacebook1_dgpu_switch", + .acpi_match_table = sb1_dgpu_sw_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .dev_groups = sb1_dgpu_sw_groups, + }, +}; +module_platform_driver(sb1_dgpu_sw); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); +MODULE_LICENSE("GPL"); -- 2.53.0 From 93e2e5694855fa7a53c6e465cedf07e18653bd43 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 b8cad415c62c..43b5d56383e3 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.53.0 From 63462c31da1c67a2a472be398ddc9ceee69ac32c 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 2755601f979c..4240c98ca226 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.53.0 From 65a6164c1eadb5f8ec8ccfa46e6c632ee0de16be Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 18 Feb 2023 01:02:49 +0100 Subject: [PATCH] USB: quirks: Add USB_QUIRK_DELAY_INIT for Surface Go 3 Type-Cover The touchpad on the Type-Cover of the Surface Go 3 is sometimes not being initialized properly. Apply USB_QUIRK_DELAY_INIT to fix this issue. More specifically, the device in question is a fairly standard modern touchpad with pointer and touchpad input modes. During setup, the device needs to be switched from pointer- to touchpad-mode (which is done in hid-multitouch) to fully utilize it as intended. Unfortunately, however, this seems to occasionally fail silently, leaving the device in pointer-mode. Applying USB_QUIRK_DELAY_INIT seems to fix this. Link: https://github.com/linux-surface/linux-surface/issues/1059 Signed-off-by: Maximilian Luz Patchset: surface-typecover --- drivers/usb/core/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index c4d85089d19b..3d0d35c72189 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -223,6 +223,9 @@ static const struct usb_device_id usb_quirk_list[] = { /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, + /* Microsoft Surface Go 3 Type-Cover */ + { USB_DEVICE(0x045e, 0x09b5), .driver_info = USB_QUIRK_DELAY_INIT }, + /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, -- 2.53.0 From 5950b1c8141212f47505f566e315858c79f33597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= Date: Thu, 5 Nov 2020 13:09:45 +0100 Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when suspending The Type Cover for Microsoft Surface devices supports a special usb control request to disable or enable the built-in keyboard backlight. On Windows, this request happens when putting the device into suspend or resuming it, without it the backlight of the Type Cover will remain enabled for some time even though the computer is suspended, which looks weird to the user. So add support for this special usb control request to hid-multitouch, which is the driver that's handling the Type Cover. The reason we have to use a pm_notifier for this instead of the usual suspend/resume methods is that those won't get called in case the usb device is already autosuspended. Also, if the device is autosuspended, we have to briefly autoresume it in order to send the request. Doing that should be fine, the usb-core driver does something similar during suspend inside choose_wakeup(). To make sure we don't send that request to every device but only to devices which support it, add a new quirk MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk is only enabled for the usb id of the Surface Pro 2017 Type Cover, which is where I confirmed that it's working. Patchset: surface-typecover --- drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index b1c3ef129058..44ac1d672ee9 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -35,7 +35,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -48,6 +51,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); MODULE_LICENSE("GPL"); #include "hid-ids.h" +#include "usbhid/usbhid.h" #include "hid-haptic.h" @@ -76,6 +80,7 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_DISABLE_WAKEUP BIT(21) #define MT_QUIRK_ORIENTATION_INVERT BIT(22) #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) +#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(24) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -83,6 +88,8 @@ MODULE_LICENSE("GPL"); #define MT_BUTTONTYPE_CLICKPAD 0 #define MT_BUTTONTYPE_PRESSUREPAD 1 +#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 + enum latency_mode { HID_LATENCY_NORMAL = 0, HID_LATENCY_HIGH = 1, @@ -186,6 +193,8 @@ struct mt_device { struct list_head applications; struct list_head reports; + + struct notifier_block pm_notifier; }; static void mt_post_parse_default_settings(struct mt_device *td, @@ -231,6 +240,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 #define MT_CLS_SMART_TECH 0x0113 #define MT_CLS_APPLE_TOUCHBAR 0x0114 +#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0115 #define MT_CLS_SIS 0x0457 #define MT_DEFAULT_MAXCONTACT 10 @@ -428,6 +438,16 @@ static const struct mt_class mt_classes[] = { MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE, }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_STICKY_FINGERS | + MT_QUIRK_WIN8_PTP_BUTTONS, + .export_all_inputs = true + }, { } }; @@ -1853,6 +1873,69 @@ static void mt_expired_timeout(struct timer_list *t) clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); } +static void get_type_cover_backlight_field(struct hid_device *hdev, + struct hid_field **field) +{ + struct hid_report_enum *rep_enum; + struct hid_report *rep; + struct hid_field *cur_field; + int i, j; + + rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; + list_for_each_entry(rep, &rep_enum->report_list, list) { + for (i = 0; i < rep->maxfield; i++) { + cur_field = rep->field[i]; + + for (j = 0; j < cur_field->maxusage; j++) { + if (cur_field->usage[j].hid + == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { + *field = cur_field; + return; + } + } + } + } +} + +static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) +{ + struct usb_device *udev = hid_to_usb_dev(hdev); + struct hid_field *field = NULL; + + /* Wake up the device in case it's already suspended */ + pm_runtime_get_sync(&udev->dev); + + get_type_cover_backlight_field(hdev, &field); + if (!field) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } + + field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; + hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); + +out: + pm_runtime_put_sync(&udev->dev); +} + +static int mt_pm_notifier(struct notifier_block *notifier, + unsigned long pm_event, + void *unused) +{ + struct mt_device *td = + container_of(notifier, struct mt_device, pm_notifier); + struct hid_device *hdev = td->hdev; + + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { + if (pm_event == PM_SUSPEND_PREPARE) + update_keyboard_backlight(hdev, 0); + else if (pm_event == PM_POST_SUSPEND) + update_keyboard_backlight(hdev, 1); + } + + return NOTIFY_DONE; +} + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret, i; @@ -1881,6 +1964,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; hid_set_drvdata(hdev, td); + td->pm_notifier.notifier_call = mt_pm_notifier; + register_pm_notifier(&td->pm_notifier); + INIT_LIST_HEAD(&td->applications); INIT_LIST_HEAD(&td->reports); @@ -1919,8 +2005,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) timer_setup(&td->release_timer, mt_expired_timeout, 0); ret = hid_parse(hdev); - if (ret != 0) + if (ret != 0) { + unregister_pm_notifier(&td->pm_notifier); return ret; + } if (mtclass->name == MT_CLS_APPLE_TOUCHBAR && !hid_find_field(hdev, HID_INPUT_REPORT, @@ -1934,8 +2022,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) hdev->quirks |= HID_QUIRK_NOGET; ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); - if (ret) + if (ret) { + unregister_pm_notifier(&td->pm_notifier); return ret; + } ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); if (ret) @@ -1996,6 +2086,7 @@ static void mt_remove(struct hid_device *hdev) { struct mt_device *td = hid_get_drvdata(hdev); + unregister_pm_notifier(&td->pm_notifier); timer_delete_sync(&td->release_timer); sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); @@ -2440,6 +2531,11 @@ static const struct hid_device_id mt_devices[] = { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) }, + /* Microsoft Surface type cover */ + { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, + USB_VENDOR_ID_MICROSOFT, 0x09c0) }, + /* Google MT devices */ { .driver_data = MT_CLS_GOOGLE, HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, -- 2.53.0 From 07990c05b76076db813df4fefa9451595fb123c5 Mon Sep 17 00:00:00 2001 From: PJungkamp Date: Fri, 25 Feb 2022 12:04:25 +0100 Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet switch The Surface Pro Type Cover has several non standard HID usages in it's hid report descriptor. I noticed that, upon folding the typecover back, a vendor specific range of 4 32 bit integer hid usages is transmitted. Only the first byte of the message seems to convey reliable information about the keyboard state. 0x22 => Normal (keys enabled) 0x33 => Folded back (keys disabled) 0x53 => Rotated left/right side up (keys disabled) 0x13 => Cover closed (keys disabled) 0x43 => Folded back and Tablet upside down (keys disabled) This list may not be exhaustive. The tablet mode switch will be disabled for a value of 0x22 and enabled on any other value. Patchset: surface-typecover --- drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ 1 file changed, 122 insertions(+), 26 deletions(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 44ac1d672ee9..bd2554f2127f 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -81,6 +81,7 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_ORIENTATION_INVERT BIT(22) #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(24) +#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(25) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -89,6 +90,8 @@ MODULE_LICENSE("GPL"); #define MT_BUTTONTYPE_PRESSUREPAD 1 #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 +#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 +#define MS_TYPE_COVER_APPLICATION 0xff050050 enum latency_mode { HID_LATENCY_NORMAL = 0, @@ -440,6 +443,7 @@ static const struct mt_class mt_classes[] = { }, { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | + MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | MT_QUIRK_ALWAYS_VALID | MT_QUIRK_IGNORE_DUPLICATES | MT_QUIRK_HOVERING | @@ -1474,6 +1478,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, field->application != HID_CP_CONSUMER_CONTROL && field->application != HID_GD_WIRELESS_RADIO_CTLS && field->application != HID_GD_SYSTEM_MULTIAXIS && + !(field->application == MS_TYPE_COVER_APPLICATION && + application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && + usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) return -1; @@ -1501,6 +1508,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, return 1; } + /* + * The Microsoft Surface Pro Typecover has a non-standard HID + * tablet mode switch on a vendor specific usage page with vendor + * specific usage. + */ + if (field->application == MS_TYPE_COVER_APPLICATION && + application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && + usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { + usage->type = EV_SW; + usage->code = SW_TABLET_MODE; + *max = SW_MAX; + *bit = hi->input->swbit; + return 1; + } + if (rdata->is_mt_collection) return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, application); @@ -1527,6 +1549,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, { struct mt_device *td = hid_get_drvdata(hdev); struct mt_report_data *rdata; + struct input_dev *input; rdata = mt_find_report_data(td, field->report); if (rdata && rdata->is_mt_collection) { @@ -1534,6 +1557,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, return -1; } + /* + * We own an input device which acts as a tablet mode switch for + * the Surface Pro Typecover. + */ + if (field->application == MS_TYPE_COVER_APPLICATION && + rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && + usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { + input = hi->input; + input_set_capability(input, EV_SW, SW_TABLET_MODE); + input_report_switch(input, SW_TABLET_MODE, 0); + return -1; + } + /* let hid-core decide for the others */ return 0; } @@ -1543,11 +1579,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, { struct mt_device *td = hid_get_drvdata(hid); struct mt_report_data *rdata; + struct input_dev *input; rdata = mt_find_report_data(td, field->report); if (rdata && rdata->is_mt_collection) return mt_touch_event(hid, field, usage, value); + if (field->application == MS_TYPE_COVER_APPLICATION && + rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && + usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { + input = field->hidinput->input; + input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); + input_sync(input); + return 1; + } + return 0; } @@ -1730,6 +1776,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; } +static int get_type_cover_field(struct hid_report_enum *rep_enum, + struct hid_field **field, int usage) +{ + struct hid_report *rep; + struct hid_field *cur_field; + int i, j; + + list_for_each_entry(rep, &rep_enum->report_list, list) { + for (i = 0; i < rep->maxfield; i++) { + cur_field = rep->field[i]; + if (cur_field->application != MS_TYPE_COVER_APPLICATION) + continue; + for (j = 0; j < cur_field->maxusage; j++) { + if (cur_field->usage[j].hid == usage) { + *field = cur_field; + return true; + } + } + } + } + return false; +} + +static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) +{ + struct hid_field *field; + + if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], + &field, + MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { + hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); + } else { + hid_err(hdev, "couldn't find tablet mode field\n"); + } +} + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) { struct mt_device *td = hid_get_drvdata(hdev); @@ -1787,6 +1869,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) /* force BTN_STYLUS to allow tablet matching in udev */ __set_bit(BTN_STYLUS, hi->input->keybit); break; + case MS_TYPE_COVER_APPLICATION: + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { + suffix = "Tablet Mode Switch"; + request_type_cover_tablet_mode_switch(hdev); + break; + } + fallthrough; default: suffix = "UNKNOWN"; break; @@ -1873,30 +1962,6 @@ static void mt_expired_timeout(struct timer_list *t) clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); } -static void get_type_cover_backlight_field(struct hid_device *hdev, - struct hid_field **field) -{ - struct hid_report_enum *rep_enum; - struct hid_report *rep; - struct hid_field *cur_field; - int i, j; - - rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; - list_for_each_entry(rep, &rep_enum->report_list, list) { - for (i = 0; i < rep->maxfield; i++) { - cur_field = rep->field[i]; - - for (j = 0; j < cur_field->maxusage; j++) { - if (cur_field->usage[j].hid - == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { - *field = cur_field; - return; - } - } - } - } -} - static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) { struct usb_device *udev = hid_to_usb_dev(hdev); @@ -1905,8 +1970,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) /* Wake up the device in case it's already suspended */ pm_runtime_get_sync(&udev->dev); - get_type_cover_backlight_field(hdev, &field); - if (!field) { + if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], + &field, + MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { hid_err(hdev, "couldn't find backlight field\n"); goto out; } @@ -2064,13 +2130,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) static int mt_reset_resume(struct hid_device *hdev) { + struct mt_device *td = hid_get_drvdata(hdev); + mt_release_contacts(hdev); mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + + /* Request an update on the typecover folding state on resume + * after reset. + */ + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) + request_type_cover_tablet_mode_switch(hdev); + return 0; } static int mt_resume(struct hid_device *hdev) { + struct mt_device *td = hid_get_drvdata(hdev); + /* Some Elan legacy devices require SET_IDLE to be set on resume. * It should be safe to send it to other devices too. * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ @@ -2079,12 +2156,31 @@ static int mt_resume(struct hid_device *hdev) mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + /* Request an update on the typecover folding state on resume. */ + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) + request_type_cover_tablet_mode_switch(hdev); + return 0; } static void mt_remove(struct hid_device *hdev) { struct mt_device *td = hid_get_drvdata(hdev); + struct hid_field *field; + struct input_dev *input; + + /* Reset tablet mode switch on disconnect. */ + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { + if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], + &field, + MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { + input = field->hidinput->input; + input_report_switch(input, SW_TABLET_MODE, 0); + input_sync(input); + } else { + hid_err(hdev, "couldn't find tablet mode field\n"); + } + } unregister_pm_notifier(&td->pm_notifier); timer_delete_sync(&td->release_timer); -- 2.53.0 From f1da140af03cace2df8b9462fe4ce041bf8dd954 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 method 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 7c2d9d596258..dc792984c2a7 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 280cd50d693b..454d1cb23dde 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -6340,3 +6340,39 @@ static void pci_mask_replay_timer_timeout(struct pci_dev *pdev) DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout); DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout); #endif + +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 b5cc0c2b9906..9cd35904dafc 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -488,6 +488,7 @@ struct pci_dev { unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ unsigned int non_mappable_bars:1; /* BARs can't be mapped to user-space */ + 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.53.0 From 1fbb5e7dcfcfafc56d810d0c3bb974211fd35c7b Mon Sep 17 00:00:00 2001 From: LegendaryFire Date: Wed, 7 Jan 2026 01:06:25 +0100 Subject: [PATCH] PCI: Add Surface Laptop Studio 2 devices to shutdown ops quirk Link: https://github.com/linux-surface/linux-surface/pull/1948 Patchset: surface-shutdown --- drivers/pci/quirks.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 454d1cb23dde..7d2e7f6572db 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -6359,6 +6359,13 @@ static const struct dmi_system_id no_shutdown_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), }, }, + { + .ident = "Microsoft Surface Laptop Studio 2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop Studio 2"), + }, + }, {} }; @@ -6376,3 +6383,9 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thu 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 + +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa76e, quirk_no_shutdown); // Thunderbolt 4 USB Controller +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa71e, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa73f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa73e, quirk_no_shutdown); // Thunderbolt 4 NHI +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0xa7a0, quirk_no_shutdown); // GPU -- 2.53.0 From a3871f3b131eda9f5dfc8534915339bff3700bed 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 b359413903b1..b4496db79f39 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.53.0 From 11bafd85c10d84fbabd327c0dc6fb3dd6f16ba19 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 416d87f9bd10..ce740ec99863 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -2205,6 +2205,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.53.0 From 56075a65d86fcbd501c34548e8bb0e717cea243e 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 09d031bc873f..757208a8e370 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) @@ -208,12 +215,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; @@ -1409,6 +1418,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; } @@ -1701,6 +1713,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; @@ -3949,6 +3964,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)) @@ -3999,6 +4026,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.53.0 From 998402c80cb2ab636207e1e01d1885bdbbbfaf69 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 0133405697dc..9e0763bdc758 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.53.0 From 5d74ab9a86f7ea746e7bcf00541eddaec4d4db85 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 27afc3fc0175..28b192c9a5ec 100644 --- a/drivers/media/i2c/ov7251.c +++ b/drivers/media/i2c/ov7251.c @@ -1053,7 +1053,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: @@ -1579,7 +1579,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.53.0 From 3ccb6e791a14beda5ecf46b1ff6bc1567f2c9eb7 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 ee884a8221fb..4f6bafd900ee 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c @@ -799,6 +799,10 @@ int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module) 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 cb153ce42c45..f11b499e14bb 100644 --- a/drivers/media/v4l2-core/v4l2-fwnode.c +++ b/drivers/media/v4l2-core/v4l2-fwnode.c @@ -1260,10 +1260,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.53.0 From 7125994d148d732de64f0dfede3dad68958bcd19 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 9e0763bdc758..0976b267972b 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" }, @@ -203,7 +203,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.53.0 From 9743d4b7ae82c9942549a183d4f48b135a52b324 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 7807fa329db0..2d2abb25b944 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.53.0 From 437b2a4533778de6d361f397da107cc9b4982d0d 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 11e7282dc297..c5665bd5e522 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -998,6 +998,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 9a0333ec1a86..4e1ca8fc9b41 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -93,6 +93,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_UPBOARD) += leds-upboard.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c new file mode 100644 index 000000000000..35aeb5db89c8 --- /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.53.0 From 59e06c991c522d4ded28c1f9a5b901df53b7c4df 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 59558335989e..b89765521af5 100644 --- a/drivers/media/i2c/dw9719.c +++ b/drivers/media/i2c/dw9719.c @@ -121,6 +121,9 @@ static int dw9719_power_up(struct dw9719_device *dw9719, bool detect) if (ret) return ret; + /* Wait for device to be acknowledged */ + fsleep(10000); + /* * Need 100us to transition from SHUTDOWN to STANDBY. * Jiggle the SCL pin to wake up the device (even when the regulator is -- 2.53.0 From 800e119032793baa8a62173144ddc79d221a75e2 Mon Sep 17 00:00:00 2001 From: Tooraj Taraz Date: Wed, 31 Dec 2025 00:35:50 +0100 Subject: [PATCH] Add camera support for Surface Pro 9 Experimental camera support for the Surface Pro 9. Link: https://github.com/linux-surface/linux-surface/pull/1867 Patchset: cameras --- drivers/media/i2c/ov13858.c | 158 +++++++++++++++++- drivers/media/i2c/ov5693.c | 1 + drivers/media/pci/intel/ipu-bridge.c | 4 + drivers/platform/x86/intel/int3472/discrete.c | 23 +++ 4 files changed, 184 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/ov13858.c b/drivers/media/i2c/ov13858.c index 162b49046990..85cd0c98af03 100644 --- a/drivers/media/i2c/ov13858.c +++ b/drivers/media/i2c/ov13858.c @@ -2,6 +2,7 @@ // Copyright (c) 2017 Intel Corporation. #include +#include #include #include #include @@ -119,6 +120,14 @@ struct ov13858_mode { struct ov13858_reg_list reg_list; }; +// Or use standard names that might be more correct: +static const char * const ov13858_supply_names[] = { + "avdd", // Analog voltage + "pwr1", // Digital core voltage +}; + +#define OV13858_NUM_SUPPLIES ARRAY_SIZE(ov13858_supply_names) + /* 4224x3136 needs 1080Mbps/lane, 4 lanes */ static const struct ov13858_reg mipi_data_rate_1080mbps[] = { /* PLL1 registers */ @@ -1046,12 +1055,126 @@ struct ov13858 { /* Current mode */ const struct ov13858_mode *cur_mode; + struct regulator_bulk_data supplies[OV13858_NUM_SUPPLIES]; + struct gpio_desc *reset; + struct clk *xvclk; + /* Mutex for serialized access */ struct mutex mutex; }; #define to_ov13858(_sd) container_of(_sd, struct ov13858, sd) + +static int ov13858_get_regulators(struct ov13858 *ov13858) +{ + unsigned int i; + + for (i = 0; i < OV13858_NUM_SUPPLIES; i++) + ov13858->supplies[i].supply = ov13858_supply_names[i]; + + return devm_regulator_bulk_get(ov13858->dev, OV13858_NUM_SUPPLIES, + ov13858->supplies); +} + +static int ov13858_sensor_powerup(struct ov13858 *ov13858) +{ + int ret; + + dev_info(ov13858->dev, "Powering up sensor\n"); + + /* Assert reset */ + gpiod_set_value_cansleep(ov13858->reset, 1); + + /* Enable clock FIRST - sensor needs clock to communicate */ + ret = clk_prepare_enable(ov13858->xvclk); + if (ret) { + dev_err(ov13858->dev, "Failed to enable clock: %d\n", ret); + return ret; + } + dev_info(ov13858->dev, "Clock enabled\n"); + + /* Enable regulators */ + ret = regulator_bulk_enable(OV13858_NUM_SUPPLIES, ov13858->supplies); + if (ret) { + dev_err(ov13858->dev, "Failed to enable regulators: %d\n", ret); + clk_disable_unprepare(ov13858->xvclk); + return ret; + } + + dev_info(ov13858->dev, "Regulators enabled\n"); + + /* Wait for power to stabilize */ + usleep_range(5000, 10000); + + /* De-assert reset */ + gpiod_set_value_cansleep(ov13858->reset, 0); + + /* Wait for sensor to boot */ + msleep(20); + + dev_info(ov13858->dev, "Reset de-asserted, sensor should be ready\n"); + + return 0; +} + +static void ov13858_sensor_powerdown(struct ov13858 *ov13858) +{ + dev_info(ov13858->dev, "Powering down sensor\n"); + + /* Assert reset to put sensor in reset state */ + gpiod_set_value_cansleep(ov13858->reset, 1); + + /* Disable regulators */ + regulator_bulk_disable(OV13858_NUM_SUPPLIES, ov13858->supplies); + dev_info(ov13858->dev, "Regulators disabled\n"); + + /* Disable clock */ + clk_disable_unprepare(ov13858->xvclk); + dev_info(ov13858->dev, "Clock disabled\n"); +} + +static int __maybe_unused ov13858_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov13858 *ov13858 = to_ov13858(sd); + + ov13858_sensor_powerdown(ov13858); + + return 0; +} + +static int __maybe_unused ov13858_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov13858 *ov13858 = to_ov13858(sd); + int ret; + + ret = ov13858_sensor_powerup(ov13858); + if (ret) + return ret; + + return 0; +} + +static const struct dev_pm_ops ov13858_pm_ops = { + SET_RUNTIME_PM_OPS(ov13858_suspend, ov13858_resume, NULL) +}; + +static int ov13858_get_gpios(struct ov13858 *ov13858) +{ + ov13858->reset = devm_gpiod_get_optional(ov13858->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(ov13858->reset)) { + dev_err(ov13858->dev, "Error fetching reset GPIO\n"); + return PTR_ERR(ov13858->reset); + } + + return 0; +} + /* Read registers up to 4 at a time */ static int ov13858_read_reg(struct ov13858 *ov13858, u16 reg, u32 len, u32 *val) @@ -1498,8 +1621,11 @@ static int ov13858_identify_module(struct ov13858 *ov13858) int ret; u32 val; + dev_info(ov13858->dev, "Attempting to read chip ID from register 0x300a\n"); ret = ov13858_read_reg(ov13858, OV13858_REG_CHIP_ID, OV13858_REG_VALUE_24BIT, &val); + dev_info(ov13858->dev, "Chip ID read result: ret=%d, val=0x%06x (expected 0x%06x)\n", + ret, val, OV13858_CHIP_ID); if (ret) return ret; @@ -1509,6 +1635,7 @@ static int ov13858_identify_module(struct ov13858 *ov13858) return -EIO; } + dev_info(ov13858->dev, "Chip ID verified successfully!\n"); return 0; } @@ -1678,14 +1805,33 @@ static int ov13858_probe(struct i2c_client *client) "external clock %lu is not supported\n", freq); + /* Get the external clock */ + ov13858->xvclk = devm_clk_get_optional(ov13858->dev, "xvclk"); + if (IS_ERR(ov13858->xvclk)) + return dev_err_probe(ov13858->dev, PTR_ERR(ov13858->xvclk), + "failed to get xvclk\n"); + /* Initialize subdev */ v4l2_i2c_subdev_init(&ov13858->sd, client, &ov13858_subdev_ops); + ret = ov13858_get_regulators(ov13858); + if (ret) + return dev_err_probe(ov13858->dev, ret, + "Error fetching regulators\n"); + + ret = ov13858_get_gpios(ov13858); + if (ret) + return ret; + + ret = ov13858_sensor_powerup(ov13858); + if (ret) + return ret; // No cleanup needed yet, devm handles GPIOs/regulators + /* Check module identity */ ret = ov13858_identify_module(ov13858); if (ret) { dev_err(ov13858->dev, "failed to find sensor: %d\n", ret); - return ret; + goto error_power_off; // Now we need to power down } /* Set default mode to max resolution */ @@ -1693,7 +1839,7 @@ static int ov13858_probe(struct i2c_client *client) ret = ov13858_init_controls(ov13858); if (ret) - return ret; + goto error_power_off; /* Initialize subdev */ ov13858->sd.internal_ops = &ov13858_internal_ops; @@ -1729,6 +1875,10 @@ static int ov13858_probe(struct i2c_client *client) error_handler_free: ov13858_free_controls(ov13858); + +error_power_off: + ov13858_sensor_powerdown(ov13858); + dev_err(ov13858->dev, "%s failed:%d\n", __func__, ret); return ret; @@ -1744,6 +1894,9 @@ static void ov13858_remove(struct i2c_client *client) ov13858_free_controls(ov13858); pm_runtime_disable(ov13858->dev); + if (!pm_runtime_status_suspended(ov13858->dev)) + ov13858_sensor_powerdown(ov13858); + pm_runtime_set_suspended(ov13858->dev); } static const struct i2c_device_id ov13858_id_table[] = { @@ -1765,6 +1918,7 @@ MODULE_DEVICE_TABLE(acpi, ov13858_acpi_ids); static struct i2c_driver ov13858_i2c_driver = { .driver = { .name = "ov13858", + .pm = &ov13858_pm_ops, .acpi_match_table = ACPI_PTR(ov13858_acpi_ids), }, .probe = ov13858_probe, diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c index 4cc796bbee92..02236f3db19d 100644 --- a/drivers/media/i2c/ov5693.c +++ b/drivers/media/i2c/ov5693.c @@ -1396,6 +1396,7 @@ static const struct dev_pm_ops ov5693_pm_ops = { static const struct acpi_device_id ov5693_acpi_match[] = { {"INT33BE"}, + {"OVTI5693"}, {}, }; MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index b2b710094914..f5456330bbc9 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -61,6 +61,10 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("INT33BE", 1, 419200000), /* Onsemi MT9M114 */ IPU_SENSOR_CONFIG("INT33F0", 1, 384000000), + /* Omnivision OV5693 - Surface Pro 9 */ + IPU_SENSOR_CONFIG("OVTI5693", 1, 419200000), + /* Omnivision OV13858 - Surface Pro 9 */ + IPU_SENSOR_CONFIG("OVTID858", 4, 540000000), /* Omnivision OV2740 */ IPU_SENSOR_CONFIG("INT3474", 1, 180000000), /* Omnivision OV5670 */ diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 1505fc3ef7a8..20962e8acb26 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -229,6 +229,14 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 /* Setups using a handshake pin need 25 ms enable delay */ *enable_time_us = 25 * USEC_PER_MSEC; break; + case 0x08: /* Surface Pro 9 - additional power rail */ + *con_id = "pwr1"; + *gpio_flags = GPIO_ACTIVE_HIGH; + break; + case 0x10: /* Surface Pro 9 - secondary power rail */ + *con_id = "pwr2"; + *gpio_flags = GPIO_ACTIVE_HIGH; + break; default: *con_id = "unknown"; *gpio_flags = GPIO_ACTIVE_HIGH; @@ -333,6 +341,8 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, case INT3472_GPIO_TYPE_PRIVACY_LED: case INT3472_GPIO_TYPE_POWER_ENABLE: case INT3472_GPIO_TYPE_HANDSHAKE: + case 0x08: /* Surface Pro 9 power rails */ + case 0x10: gpio = skl_int3472_gpiod_get_from_temp_lookup(int3472, agpio, con_id, gpio_flags); if (IS_ERR(gpio)) { ret = PTR_ERR(gpio); @@ -363,6 +373,19 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, err_msg = "Failed to register regulator\n"; break; + case 0x08: /* Surface Pro 9 - treat as power*/ + dev_info(int3472->dev, "GPIO type 0x%02x detected on pin 0x%02x\n", type, agpio->pin_table[0]); + dev_info(int3472->dev, " con_id=%s, flags=0x%x\n", con_id, gpio_flags); + ret = skl_int3472_register_regulator(int3472, gpio, + GPIO_REGULATOR_ENABLE_TIME, + con_id, NULL); + dev_info(int3472->dev, " register_regulator returned: %d\n", ret); + if (ret) { + dev_err(int3472->dev, "Failed to register type 0x02x: %d\n", type, ret); + } + break; + case 0x10: + break; default: /* Never reached */ ret = -EINVAL; break; -- 2.53.0 From d97bb3ff6090aa36907b602f41fe706968b1c6d4 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 9fa321a95eb3..8914a922be2b 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -1171,6 +1172,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 @@ -1227,6 +1239,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.53.0 From 3dba74f758f1ba60e5ccc3bf62fc20b116716555 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 8914a922be2b..c43d0a553867 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -1174,12 +1174,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.53.0 From 637e1e0d2226b877d44666f60dbe1998ad509c03 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 6d870d97ada6..a19cd08e1554 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -438,6 +438,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) { @@ -486,15 +494,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, @@ -571,14 +578,19 @@ static void acpi_tad_remove(struct platform_device *pdev) if (dd->capabilities & ACPI_TAD_RT) sysfs_remove_group(&dev->kobj, &acpi_tad_time_attr_group); + 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); scoped_guard(pm_runtime_noresume, dev) { - 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); @@ -621,12 +633,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; @@ -657,6 +663,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.53.0 From 8de1717f5eafe891c12671c2a2bc35424658b45c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Coffey?= Date: Fri, 10 Oct 2025 19:04:03 +0100 Subject: [PATCH] HID: Add hid-surface driver to filter BTN_0 (FN key) The Surface Aggregator Module firmware incorrectly reports the FN key as BTN_0 (button 256) through runtime HID events. This button can get stuck in pressed state, flooding the input system and breaking focus tracking in KWin Wayland compositor. Add a new hid-surface driver that filters BTN_0 events using the event callback. The FN key should be handled as a hardware modifier and not reported to the OS. Tested on Surface Laptop 4 AMD with KDE Plasma Wayland. After the fix: - FN key combinations work correctly (volume, brightness) - Focus tracking no longer breaks - Mouse clicks work on all windows Fixes: https://github.com/linux-surface/linux-surface/issues/1851 Patchset: hid-surface --- drivers/hid/Kconfig | 8 ++++ drivers/hid/Makefile | 1 + drivers/hid/hid-surface.c | 90 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 drivers/hid/hid-surface.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 59d597b4effd..d59290a9388e 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1142,6 +1142,14 @@ config HID_SUNPLUS help Support for Sunplus wireless desktop. +config HID_SURFACE + tristate "Microsoft Surface" + depends on SURFACE_AGGREGATOR + help + Say Y here to enable HID driver for Microsoft Surface integrated + keyboard and touchpad. This driver filters out erroneous BTN_0 + (FN key) events that can cause input focus issues. + config HID_RMI tristate "Synaptics RMI4 device support" select RMI4_CORE diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 3190ece25626..4d2891879548 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -174,6 +174,7 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ +obj-$(CONFIG_HID_SURFACE) += hid-surface.o obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ diff --git a/drivers/hid/hid-surface.c b/drivers/hid/hid-surface.c new file mode 100644 index 000000000000..a171ea65672f --- /dev/null +++ b/drivers/hid/hid-surface.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Microsoft Surface devices + * + * Copyright (c) 2025 Linux Surface Project + */ + +#include +#include +#include +#include + +#include "hid-ids.h" + +/* + * The Surface Aggregator Module firmware incorrectly reports the FN key + * as BTN_0 (button 256). This button can get stuck in pressed state, + * flooding the input system and breaking focus tracking in some + * compositors. Filter out BTN_0 as FN should be handled as a hardware + * modifier, not reported to the OS. + */ +static int surface_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + /* + * Filter BTN_0 during input mapping in case it appears in the + * HID descriptor (defense in depth). + */ + if (usage->type == EV_KEY && usage->code == BTN_0) + return -1; /* Don't map this usage */ + + return 0; /* Use default mapping */ +} + +static int surface_event(struct hid_device *hdev, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + /* + * The Surface Aggregator Module firmware reports the FN key as BTN_0 + * at runtime. This button can get stuck in pressed state, flooding + * the input system and breaking focus tracking. Filter out these + * events as FN should be a hardware modifier, not reported to the OS. + */ + if (usage->type == EV_KEY && usage->code == BTN_0) + return 1; /* Event handled, don't process further */ + + return 0; /* Process event normally */ +} + +static int surface_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "hw start failed\n"); + return ret; + } + + return 0; +} + +static const struct hid_device_id surface_devices[] = { + { HID_DEVICE(BUS_HOST, HID_GROUP_GENERIC, + USB_VENDOR_ID_MICROSOFT, 0x09AE) }, /* Surface Keyboard */ + { HID_DEVICE(BUS_HOST, HID_GROUP_GENERIC, + USB_VENDOR_ID_MICROSOFT, 0x09AF) }, /* Surface Mouse/Touchpad */ + { } +}; +MODULE_DEVICE_TABLE(hid, surface_devices); + +static struct hid_driver surface_driver = { + .name = "surface", + .id_table = surface_devices, + .probe = surface_probe, + .input_mapping = surface_input_mapping, + .event = surface_event, +}; +module_hid_driver(surface_driver); + +MODULE_AUTHOR("Linux Surface Project"); +MODULE_DESCRIPTION("Microsoft Surface HID driver"); +MODULE_LICENSE("GPL"); -- 2.53.0 From de9b50c2e866713da82ecd3856216a7c3ff5dce4 Mon Sep 17 00:00:00 2001 From: LegendaryFire Date: Wed, 24 Dec 2025 00:54:44 -0800 Subject: [PATCH] hid/multitouch: Prevent mode set on Surface Laptop Studio 2 touch pad Patchset: hid-surface --- drivers/hid/hid-multitouch.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index bd2554f2127f..973f55f23ddf 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -82,6 +82,7 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(24) #define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(25) +#define MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE BIT(26) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -244,6 +245,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_SMART_TECH 0x0113 #define MT_CLS_APPLE_TOUCHBAR 0x0114 #define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0115 +#define MT_CLS_SURFACE_TOUCHPAD 0x0116 #define MT_CLS_SIS 0x0457 #define MT_DEFAULT_MAXCONTACT 10 @@ -452,6 +454,10 @@ static const struct mt_class mt_classes[] = { MT_QUIRK_WIN8_PTP_BUTTONS, .export_all_inputs = true }, + { .name = MT_CLS_SURFACE_TOUCHPAD, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE + }, { } }; @@ -2191,11 +2197,30 @@ static void mt_remove(struct hid_device *hdev) static void mt_on_hid_hw_open(struct hid_device *hdev) { + /* + * Some devices (e.g. Surface Laptop Studio 2 touchpad) can get stuck + * non-functional if we change touchpad reporting modes from the HID + * open/close hooks. Avoid mode switching on hw_open/hw_close for + * those devices. + */ + struct mt_device *td = hid_get_drvdata(hdev); + if (td && td->mtclass.quirks & MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE) + return; mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); } static void mt_on_hid_hw_close(struct hid_device *hdev) { + /* + * Some devices (e.g. Surface Laptop Studio 2 touchpad) can get stuck + * non-functional if we change touchpad reporting modes from the HID + * open/close hooks. Avoid mode switching on hw_open/hw_close for + * those devices. + */ + struct mt_device *td = hid_get_drvdata(hdev); + if (td && td->mtclass.quirks & MT_QUIRK_SKIP_MODESET_ON_HW_OPEN_CLOSE) + return; + mt_set_modes(hdev, HID_LATENCY_HIGH, TOUCHPAD_REPORT_NONE); } @@ -2632,6 +2657,11 @@ static const struct hid_device_id mt_devices[] = { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_MICROSOFT, 0x09c0) }, + /* Microsoft Surface touch pad */ + { .driver_data = MT_CLS_SURFACE_TOUCHPAD, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, + USB_VENDOR_ID_MICROSOFT, 0x0C46) }, + /* Google MT devices */ { .driver_data = MT_CLS_GOOGLE, HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, -- 2.53.0 From ce7514b67d4a0481bbc29536ce468c679679dbe6 Mon Sep 17 00:00:00 2001 From: Daniel Tang Date: Wed, 14 Jan 2026 20:31:38 -0500 Subject: [PATCH] powercap: intel_rapl: Add PL4 support for Ice Lake Microsoft Surface Pro 7 firmware throttles the processor upon boot/resume. Userspace needs to be able to restore the correct value. Link: https://github.com/linux-surface/linux-surface/issues/706 Signed-off-by: Daniel Tang Patchset: powercap --- drivers/powercap/intel_rapl_msr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/powercap/intel_rapl_msr.c b/drivers/powercap/intel_rapl_msr.c index 9a7e150b3536..a2bc0a9c1e10 100644 --- a/drivers/powercap/intel_rapl_msr.c +++ b/drivers/powercap/intel_rapl_msr.c @@ -162,6 +162,7 @@ static int rapl_msr_write_raw(int cpu, struct reg_action *ra) /* List of verified CPUs. */ static const struct x86_cpu_id pl4_support_ids[] = { + X86_MATCH_VFM(INTEL_ICELAKE_L, NULL), X86_MATCH_VFM(INTEL_TIGERLAKE_L, NULL), X86_MATCH_VFM(INTEL_ALDERLAKE, NULL), X86_MATCH_VFM(INTEL_ALDERLAKE_L, NULL), -- 2.53.0