From b1ce7e2a0ea543867d0aa1dd7a5d23dad9336a73 Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Sun, 18 Oct 2020 16:42:44 +0900 Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI table On some Surface 3, the DMI table gets corrupted for unknown reasons and breaks existing DMI matching used for device-specific quirks. This commit adds the (broken) DMI data into dmi_system_id tables used for quirks so that each driver can enable quirks even on the affected systems. On affected systems, DMI data will look like this: $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ chassis_vendor,product_name,sys_vendor} /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. /sys/devices/virtual/dmi/id/board_name:OEMB /sys/devices/virtual/dmi/id/board_vendor:OEMB /sys/devices/virtual/dmi/id/chassis_vendor:OEMB /sys/devices/virtual/dmi/id/product_name:OEMB /sys/devices/virtual/dmi/id/sys_vendor:OEMB Expected: $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ chassis_vendor,product_name,sys_vendor} /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. /sys/devices/virtual/dmi/id/board_name:Surface 3 /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation /sys/devices/virtual/dmi/id/product_name:Surface 3 /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation Signed-off-by: Tsuchiya Yuto Patchset: surface3-oemb --- drivers/platform/surface/surface3-wmi.c | 7 +++++++ sound/soc/codecs/rt5645.c | 9 +++++++++ sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c index ca4602bcc7de..490b9731068a 100644 --- a/drivers/platform/surface/surface3-wmi.c +++ b/drivers/platform/surface/surface3-wmi.c @@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), }, }, + { + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), + DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), + DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), + }, + }, #endif { } }; diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 620ecbfa4a7a..b07d06d2971a 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3717,6 +3717,15 @@ static const struct dmi_system_id dmi_platform_data[] = { }, .driver_data = (void *)&intel_braswell_platform_data, }, + { + .ident = "Microsoft Surface 3", + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), + DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), + DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, { /* * Match for the GPDwin which unfortunately uses somewhat diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c index 6beb00858c33..d82d77387a0a 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c @@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), }, }, + { + .callback = cht_surface_quirk_cb, + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), + DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), + DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), + }, + }, { } }; -- 2.39.0 From f68e13904fe89824383047ad407cdf2868c0c644 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 5dcf61761a16..84be9289caa6 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie.c +++ b/drivers/net/wireless/marvell/mwifiex/pcie.c @@ -1762,9 +1762,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) { struct pcie_service_card *card = adapter->card; + struct pci_dev *pdev = card->dev; + struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + /* Trigger a function level reset of the PCI bridge device, this makes + * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value + * that prevents the system from entering package C10 and S0ix powersaving + * states. + * We need to do it here because it must happen after firmware + * initialization and this function is called after that is done. + */ + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + pci_reset_function(parent_pdev); + /* Write the RX ring read pointer in to reg->rx_rdptr */ if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | tx_wrap)) { diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c index 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.39.0 From 14ecac240936e807dd2366c70cdf9d108a5b8f40 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 84be9289caa6..98be0d3cc41c 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie.c +++ b/drivers/net/wireless/marvell/mwifiex/pcie.c @@ -368,6 +368,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", @@ -409,6 +410,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.39.0 From 2aa594c47d5d32cba044b4b249c7b191d4c5c2e3 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 6beafd62d722..633f77fb4f96 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -63,6 +63,7 @@ static struct usb_driver btusb_driver; #define BTUSB_INTEL_BROKEN_SHUTDOWN_LED BIT(24) #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) +#define BTUSB_LOWER_LESCAN_INTERVAL BIT(27) static const struct usb_device_id btusb_table[] = { /* Generic Bluetooth USB device */ @@ -382,6 +383,7 @@ static const struct usb_device_id blacklist_table[] = { { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, /* Intel Bluetooth devices */ { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, @@ -3862,6 +3864,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.39.0 From 272e87cedc1e0b5c51e54c99e36f667c731d0dbb Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 27 Feb 2021 00:45:52 +0100 Subject: [PATCH] ath10k: Add module parameters to override board files Some Surface devices, specifically the Surface Go and AMD version of the Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better with a different board file, as it seems that the firmeware included upstream is buggy. As it is generally not a good idea to randomly overwrite files, let alone doing so via packages, we add module parameters to override those file names in the driver. This allows us to package/deploy the override via a modprobe.d config. Signed-off-by: Maximilian Luz Patchset: ath10k --- drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c index 5eb131ab916f..67f074a126d1 100644 --- a/drivers/net/wireless/ath/ath10k/core.c +++ b/drivers/net/wireless/ath/ath10k/core.c @@ -38,6 +38,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); @@ -50,6 +53,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"); @@ -59,6 +65,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, @@ -911,6 +920,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) @@ -925,6 +970,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, if (dir == NULL) dir = "."; + /* HACK: Override board.bin and board-2.bin files if specified. + * + * Some Surface devices perform better with a different board + * configuration. To this end, one would need to replace the board.bin + * file with the modified config and remove the board-2.bin file. + * Unfortunately, that's not a solution that we can easily package. So + * we add module options to perform these overrides here. + */ + + file = ath10k_override_board_fw_file(ar, file); + if (!file) + return ERR_PTR(-ENOENT); + snprintf(filename, sizeof(filename), "%s/%s", dir, file); ret = firmware_request_nowarn(&fw, filename, ar->dev); ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", -- 2.39.0 From 6152dcef77a334b7844aa565275902111194a78f Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Thu, 30 Jul 2020 13:21:53 +0200 Subject: [PATCH] misc: mei: Add missing IPTS device IDs Patchset: ipts --- drivers/misc/mei/hw-me-regs.h | 1 + drivers/misc/mei/pci-me.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index 99966cd3e7d8..67c602919b7e 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 704cd0caa172..b4dc3fddd20d 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.39.0 From 81499f96035b9144633c1dd0942610ad9f4b0dce Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Thu, 6 Aug 2020 11:20:41 +0200 Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus Based on linux-surface/intel-precise-touch@3f362c Signed-off-by: Dorian Stoll Patchset: ipts --- drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 1 + drivers/misc/ipts/Kconfig | 17 ++ drivers/misc/ipts/Makefile | 12 ++ drivers/misc/ipts/context.h | 47 +++++ drivers/misc/ipts/control.c | 113 +++++++++++ drivers/misc/ipts/control.h | 24 +++ drivers/misc/ipts/mei.c | 125 ++++++++++++ drivers/misc/ipts/protocol.h | 347 ++++++++++++++++++++++++++++++++++ drivers/misc/ipts/receiver.c | 224 ++++++++++++++++++++++ drivers/misc/ipts/receiver.h | 16 ++ drivers/misc/ipts/resources.c | 128 +++++++++++++ drivers/misc/ipts/resources.h | 17 ++ drivers/misc/ipts/uapi.c | 208 ++++++++++++++++++++ drivers/misc/ipts/uapi.h | 47 +++++ 15 files changed, 1327 insertions(+) create mode 100644 drivers/misc/ipts/Kconfig create mode 100644 drivers/misc/ipts/Makefile create mode 100644 drivers/misc/ipts/context.h create mode 100644 drivers/misc/ipts/control.c create mode 100644 drivers/misc/ipts/control.h create mode 100644 drivers/misc/ipts/mei.c create mode 100644 drivers/misc/ipts/protocol.h create mode 100644 drivers/misc/ipts/receiver.c create mode 100644 drivers/misc/ipts/receiver.h create mode 100644 drivers/misc/ipts/resources.c create mode 100644 drivers/misc/ipts/resources.h create mode 100644 drivers/misc/ipts/uapi.c create mode 100644 drivers/misc/ipts/uapi.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 358ad56f6524..1fd6f755f98b 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -514,4 +514,5 @@ source "drivers/misc/habanalabs/Kconfig" source "drivers/misc/uacce/Kconfig" source "drivers/misc/pvpanic/Kconfig" source "drivers/misc/mchp_pci1xxxx/Kconfig" +source "drivers/misc/ipts/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index ac9b3e757ba1..5a82ffe77911 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -62,3 +62,4 @@ obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o obj-$(CONFIG_OPEN_DICE) += open-dice.o obj-$(CONFIG_GP_PCI1XXXX) += mchp_pci1xxxx/ obj-$(CONFIG_VCPU_STALL_DETECTOR) += vcpu_stall_detector.o +obj-$(CONFIG_MISC_IPTS) += ipts/ diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig new file mode 100644 index 000000000000..83e2a930c396 --- /dev/null +++ b/drivers/misc/ipts/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +config MISC_IPTS + tristate "Intel Precise Touch & Stylus" + depends on INTEL_MEI + help + Say Y here if your system has a touchscreen using Intels + Precise Touch & Stylus (IPTS) technology. + + If unsure say N. + + To compile this driver as a module, choose M here: the + module will be called ipts. + + Building this driver alone will not give you a working touchscreen. + It only exposed a userspace API that can be used by a daemon to + receive and process data from the touchscreen hardware. diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile new file mode 100644 index 000000000000..8f58b9adbc94 --- /dev/null +++ b/drivers/misc/ipts/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Makefile for the IPTS touchscreen driver +# + +obj-$(CONFIG_MISC_IPTS) += ipts.o +ipts-objs := control.o +ipts-objs += mei.o +ipts-objs += receiver.o +ipts-objs += resources.o +ipts-objs += uapi.o + diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h new file mode 100644 index 000000000000..f4b06a2d3f72 --- /dev/null +++ b/drivers/misc/ipts/context.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef _IPTS_CONTEXT_H_ +#define _IPTS_CONTEXT_H_ + +#include +#include +#include +#include + +#include "protocol.h" + +enum ipts_host_status { + IPTS_HOST_STATUS_STARTING, + IPTS_HOST_STATUS_STARTED, + IPTS_HOST_STATUS_STOPPING, + IPTS_HOST_STATUS_STOPPED, +}; + +struct ipts_buffer_info { + u8 *address; + dma_addr_t dma_address; +}; + +struct ipts_context { + struct mei_cl_device *cldev; + struct device *dev; + + bool restart; + enum ipts_host_status status; + struct ipts_get_device_info_rsp device_info; + + struct ipts_buffer_info data[IPTS_BUFFERS]; + struct ipts_buffer_info doorbell; + + struct ipts_buffer_info feedback[IPTS_BUFFERS]; + struct ipts_buffer_info workqueue; + struct ipts_buffer_info host2me; +}; + +#endif /* _IPTS_CONTEXT_H_ */ diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c new file mode 100644 index 000000000000..a1d1f97a13d7 --- /dev/null +++ b/drivers/misc/ipts/control.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include + +#include "context.h" +#include "protocol.h" +#include "resources.h" +#include "uapi.h" + +int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, + size_t size) +{ + int ret; + struct ipts_command cmd; + + memset(&cmd, 0, sizeof(struct ipts_command)); + cmd.code = code; + + if (payload && size > 0) + memcpy(&cmd.payload, payload, size); + + ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size); + if (ret >= 0) + return 0; + + /* + * During shutdown the device might get pulled away from below our feet. + * Dont log an error in this case, because it will confuse people. + */ + if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) + dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); + + return ret; +} + +int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) +{ + struct ipts_feedback_cmd cmd; + + memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); + cmd.buffer = buffer; + + return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, + sizeof(struct ipts_feedback_cmd)); +} + +int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) +{ + struct ipts_feedback_buffer *feedback; + + memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); + feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; + + feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; + feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; + feedback->buffer = IPTS_HOST2ME_BUFFER; + feedback->size = 2; + feedback->payload[0] = report; + feedback->payload[1] = value; + + return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); +} + +int ipts_control_start(struct ipts_context *ipts) +{ + if (ipts->status != IPTS_HOST_STATUS_STOPPED) + return -EBUSY; + + dev_info(ipts->dev, "Starting IPTS\n"); + ipts->status = IPTS_HOST_STATUS_STARTING; + ipts->restart = false; + + ipts_uapi_link(ipts); + return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); +} + +int ipts_control_stop(struct ipts_context *ipts) +{ + int ret; + + if (ipts->status == IPTS_HOST_STATUS_STOPPING) + return -EBUSY; + + if (ipts->status == IPTS_HOST_STATUS_STOPPED) + return -EBUSY; + + dev_info(ipts->dev, "Stopping IPTS\n"); + ipts->status = IPTS_HOST_STATUS_STOPPING; + + ipts_uapi_unlink(); + ipts_resources_free(ipts); + + ret = ipts_control_send_feedback(ipts, 0); + if (ret == -ENODEV) + ipts->status = IPTS_HOST_STATUS_STOPPED; + + return ret; +} + +int ipts_control_restart(struct ipts_context *ipts) +{ + if (ipts->restart) + return -EBUSY; + + ipts->restart = true; + return ipts_control_stop(ipts); +} diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h new file mode 100644 index 000000000000..2c44e9e0e99f --- /dev/null +++ b/drivers/misc/ipts/control.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef _IPTS_CONTROL_H_ +#define _IPTS_CONTROL_H_ + +#include + +#include "context.h" + +int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, + size_t size); +int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); +int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); +int ipts_control_start(struct ipts_context *ipts); +int ipts_control_restart(struct ipts_context *ipts); +int ipts_control_stop(struct ipts_context *ipts); + +#endif /* _IPTS_CONTROL_H_ */ diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c new file mode 100644 index 000000000000..59ecf13e00d2 --- /dev/null +++ b/drivers/misc/ipts/mei.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "protocol.h" +#include "receiver.h" +#include "uapi.h" + +static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev) +{ + int ret; + + ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)); + if (!ret) + return 0; + + return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); +} + +static int ipts_mei_probe(struct mei_cl_device *cldev, + const struct mei_cl_device_id *id) +{ + int ret; + struct ipts_context *ipts; + + if (ipts_mei_set_dma_mask(cldev)) { + dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n"); + return -EFAULT; + } + + ret = mei_cldev_enable(cldev); + if (ret) { + dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); + return ret; + } + + ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); + if (!ipts) { + mei_cldev_disable(cldev); + return -ENOMEM; + } + + ipts->cldev = cldev; + ipts->dev = &cldev->dev; + ipts->status = IPTS_HOST_STATUS_STOPPED; + + mei_cldev_set_drvdata(cldev, ipts); + mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); + + return ipts_control_start(ipts); +} + +static void ipts_mei_remove(struct mei_cl_device *cldev) +{ + int i; + struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); + + ipts_control_stop(ipts); + + for (i = 0; i < 20; i++) { + if (ipts->status == IPTS_HOST_STATUS_STOPPED) + break; + + msleep(25); + } + + mei_cldev_disable(cldev); + kfree(ipts); +} + +static struct mei_cl_device_id ipts_mei_device_id_table[] = { + { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, + {}, +}; +MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); + +static struct mei_cl_driver ipts_mei_driver = { + .id_table = ipts_mei_device_id_table, + .name = "ipts", + .probe = ipts_mei_probe, + .remove = ipts_mei_remove, +}; + +static int __init ipts_mei_init(void) +{ + int ret; + + ret = ipts_uapi_init(); + if (ret) + return ret; + + ret = mei_cldev_driver_register(&ipts_mei_driver); + if (ret) { + ipts_uapi_free(); + return ret; + } + + return 0; +} + +static void __exit ipts_mei_exit(void) +{ + mei_cldev_driver_unregister(&ipts_mei_driver); + ipts_uapi_free(); +} + +MODULE_DESCRIPTION("IPTS touchscreen driver"); +MODULE_AUTHOR("Dorian Stoll "); +MODULE_LICENSE("GPL"); + +module_init(ipts_mei_init); +module_exit(ipts_mei_exit); diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h new file mode 100644 index 000000000000..c3458904a94d --- /dev/null +++ b/drivers/misc/ipts/protocol.h @@ -0,0 +1,347 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef _IPTS_PROTOCOL_H_ +#define _IPTS_PROTOCOL_H_ + +#include + +/* + * The MEI client ID for IPTS functionality. + */ +#define IPTS_MEI_UUID \ + UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ + 0x02, 0xae, 0x04) + +/* + * Queries the device for vendor specific information. + * + * The command must not contain any payload. + * The response will contain struct ipts_get_device_info_rsp as payload. + */ +#define IPTS_CMD_GET_DEVICE_INFO 0x00000001 +#define IPTS_RSP_GET_DEVICE_INFO 0x80000001 + +/* + * Sets the mode that IPTS will operate in. + * + * The command must contain struct ipts_set_mode_cmd as payload. + * The response will not contain any payload. + */ +#define IPTS_CMD_SET_MODE 0x00000002 +#define IPTS_RSP_SET_MODE 0x80000002 + +/* + * Configures the memory buffers that the ME will use + * for passing data to the host. + * + * The command must contain struct ipts_set_mem_window_cmd as payload. + * The response will not contain any payload. + */ +#define IPTS_CMD_SET_MEM_WINDOW 0x00000003 +#define IPTS_RSP_SET_MEM_WINDOW 0x80000003 + +/* + * Signals that the host is ready to receive data to the ME. + * + * The command must not contain any payload. + * The response will not contain any payload. + */ +#define IPTS_CMD_READY_FOR_DATA 0x00000005 +#define IPTS_RSP_READY_FOR_DATA 0x80000005 + +/* + * Signals that a buffer can be refilled to the ME. + * + * The command must contain struct ipts_feedback_cmd as payload. + * The response will not contain any payload. + */ +#define IPTS_CMD_FEEDBACK 0x00000006 +#define IPTS_RSP_FEEDBACK 0x80000006 + +/* + * Resets the data flow from the ME to the hosts and + * clears the buffers that were set with SET_MEM_WINDOW. + * + * The command must not contain any payload. + * The response will not contain any payload. + */ +#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007 +#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 + +/* + * Instructs the ME to reset the touch sensor. + * + * The command must contain struct ipts_reset_sensor_cmd as payload. + * The response will not contain any payload. + */ +#define IPTS_CMD_RESET_SENSOR 0x0000000B +#define IPTS_RSP_RESET_SENSOR 0x8000000B + +/** + * enum ipts_status - Possible status codes returned by IPTS commands. + * @IPTS_STATUS_SUCCESS: Operation completed successfully. + * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. + * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. + * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. + * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. + * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. + * The host must wait for a response before sending another + * command of the same type. + * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it + * has not been initialized yet, or the system is improperly + * configured. + * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. + * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. + * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. + * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. + * The host can ignore this error and attempt to continue. + * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. + * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. + * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. + * @IPTS_STATUS_TIMEOUT: The operation timed out. + * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. + * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. + * Further progress is not possible. + * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. + * The host can attempt to continue. + * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. + * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. + */ +enum ipts_status { + IPTS_STATUS_SUCCESS = 0, + IPTS_STATUS_INVALID_PARAMS = 1, + IPTS_STATUS_ACCESS_DENIED = 2, + IPTS_STATUS_CMD_SIZE_ERROR = 3, + IPTS_STATUS_NOT_READY = 4, + IPTS_STATUS_REQUEST_OUTSTANDING = 5, + IPTS_STATUS_NO_SENSOR_FOUND = 6, + IPTS_STATUS_OUT_OF_MEMORY = 7, + IPTS_STATUS_INTERNAL_ERROR = 8, + IPTS_STATUS_SENSOR_DISABLED = 9, + IPTS_STATUS_COMPAT_CHECK_FAIL = 10, + IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, + IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, + IPTS_STATUS_RESET_FAILED = 13, + IPTS_STATUS_TIMEOUT = 14, + IPTS_STATUS_TEST_MODE_FAIL = 15, + IPTS_STATUS_SENSOR_FAIL_FATAL = 16, + IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, + IPTS_STATUS_INVALID_DEVICE_CAPS = 18, + IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, +}; + +/* + * The amount of buffers that is used for IPTS + */ +#define IPTS_BUFFERS 16 + +/* + * The special buffer ID that is used for direct host2me feedback. + */ +#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS + +/** + * enum ipts_mode - Operation mode for IPTS hardware + * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. + * The data is received as a HID report with ID 64. + * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return + * stylus data as well as capacitive heatmap touch data. + * This data needs to be processed in userspace. + */ +enum ipts_mode { + IPTS_MODE_SINGLETOUCH = 0, + IPTS_MODE_MULTITOUCH = 1, +}; + +/** + * struct ipts_set_mode_cmd - Payload for the SET_MODE command. + * @mode: The mode that IPTS should operate in. + */ +struct ipts_set_mode_cmd { + enum ipts_mode mode; + u8 reserved[12]; +} __packed; + +#define IPTS_WORKQUEUE_SIZE 8192 +#define IPTS_WORKQUEUE_ITEM_SIZE 16 + +/** + * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. + * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. + * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. + * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. + * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. + * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. + * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. + * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses. + * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. + * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. + * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. + * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) + * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) + * + * The data buffers are buffers that get filled with touch data by the ME. + * The doorbell buffer is a u32 that gets incremented by the ME once a data + * buffer has been filled with new data. + * + * The other buffers are required for using GuC submission with binary + * firmware. Since support for GuC submission has been dropped from i915, + * they are not used anymore, but they need to be allocated and passed, + * otherwise the hardware will refuse to start. + */ +struct ipts_set_mem_window_cmd { + u32 data_buffer_addr_lower[IPTS_BUFFERS]; + u32 data_buffer_addr_upper[IPTS_BUFFERS]; + u32 workqueue_addr_lower; + u32 workqueue_addr_upper; + u32 doorbell_addr_lower; + u32 doorbell_addr_upper; + u32 feedback_buffer_addr_lower[IPTS_BUFFERS]; + u32 feedback_buffer_addr_upper[IPTS_BUFFERS]; + u32 host2me_addr_lower; + u32 host2me_addr_upper; + u32 host2me_size; + u8 reserved1; + u8 workqueue_item_size; + u16 workqueue_size; + u8 reserved[32]; +} __packed; + +/** + * struct ipts_feedback_cmd - Payload for the FEEDBACK command. + * @buffer: The buffer that the ME should refill. + */ +struct ipts_feedback_cmd { + u32 buffer; + u8 reserved[12]; +} __packed; + +/** + * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. + */ +enum ipts_feedback_cmd_type { + IPTS_FEEDBACK_CMD_TYPE_NONE = 0, + IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, + IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, + IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, + IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, + IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, + IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, +}; + +/** + * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. + * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. + * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. + * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. + * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. + * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. + */ +enum ipts_feedback_data_type { + IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, + IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, + IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, + IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, + IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, +}; + +/** + * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. + * @cmd_type: A command that should be executed on the sensor. + * @size: The size of the payload to be written. + * @buffer: The ID of the buffer that contains this feedback data. + * @protocol: The protocol version of the EDS. + * @data_type: The type of payload that the buffer contains. + * @spi_offset: The offset at which to write the payload data. + * @payload: Payload for the feedback command, or 0 if no payload is sent. + */ +struct ipts_feedback_buffer { + enum ipts_feedback_cmd_type cmd_type; + u32 size; + u32 buffer; + u32 protocol; + enum ipts_feedback_data_type data_type; + u32 spi_offset; + u8 reserved[40]; + u8 payload[]; +} __packed; + +/** + * enum ipts_reset_type - Possible ways of resetting the touch sensor + * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. + * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. + */ +enum ipts_reset_type { + IPTS_RESET_TYPE_HARD = 0, + IPTS_RESET_TYPE_SOFT = 1, +}; + +/** + * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. + * @type: What type of reset should be performed. + */ +struct ipts_reset_sensor_cmd { + enum ipts_reset_type type; + u8 reserved[4]; +} __packed; + +/** + * struct ipts_command - A message sent from the host to the ME. + * @code: The message code describing the command. (see IPTS_CMD_*) + * @payload: Payload for the command, or 0 if no payload is required. + */ +struct ipts_command { + u32 code; + u8 payload[320]; +} __packed; + +/** + * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. + * @vendor_id: Vendor ID of the touch sensor. + * @device_id: Device ID of the touch sensor. + * @hw_rev: Hardware revision of the touch sensor. + * @fw_rev: Firmware revision of the touch sensor. + * @data_size: Required size of one data buffer. + * @feedback_size: Required size of one feedback buffer. + * @mode: Current operation mode of IPTS. + * @max_contacts: The amount of concurrent touches supported by the sensor. + */ +struct ipts_get_device_info_rsp { + u16 vendor_id; + u16 device_id; + u32 hw_rev; + u32 fw_rev; + u32 data_size; + u32 feedback_size; + enum ipts_mode mode; + u8 max_contacts; + u8 reserved[19]; +} __packed; + +/** + * struct ipts_feedback_rsp - Payload for the FEEDBACK response. + * @buffer: The buffer that has received feedback. + */ +struct ipts_feedback_rsp { + u32 buffer; +} __packed; + +/** + * struct ipts_response - A message sent from the ME to the host. + * @code: The message code describing the response. (see IPTS_RSP_*) + * @status: The status code returned by the command. + * @payload: Payload returned by the command. + */ +struct ipts_response { + u32 code; + enum ipts_status status; + u8 payload[80]; +} __packed; + +#endif /* _IPTS_PROTOCOL_H_ */ diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c new file mode 100644 index 000000000000..23dca13c2139 --- /dev/null +++ b/drivers/misc/ipts/receiver.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "protocol.h" +#include "resources.h" + +/* + * Temporary parameter to guard gen7 multitouch mode. + * Remove once gen7 has stable iptsd support. + */ +static bool gen7mt; +module_param(gen7mt, bool, 0644); + +static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, + struct ipts_response *rsp) +{ + struct ipts_set_mode_cmd cmd; + + memcpy(&ipts->device_info, rsp->payload, + sizeof(struct ipts_get_device_info_rsp)); + + memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); + cmd.mode = IPTS_MODE_MULTITOUCH; + + return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, + sizeof(struct ipts_set_mode_cmd)); +} + +static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) +{ + int i, ret; + struct ipts_set_mem_window_cmd cmd; + + ret = ipts_resources_alloc(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to allocate resources\n"); + return ret; + } + + memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); + + for (i = 0; i < IPTS_BUFFERS; i++) { + cmd.data_buffer_addr_lower[i] = + lower_32_bits(ipts->data[i].dma_address); + + cmd.data_buffer_addr_upper[i] = + upper_32_bits(ipts->data[i].dma_address); + + cmd.feedback_buffer_addr_lower[i] = + lower_32_bits(ipts->feedback[i].dma_address); + + cmd.feedback_buffer_addr_upper[i] = + upper_32_bits(ipts->feedback[i].dma_address); + } + + cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); + cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); + + cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); + cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); + + cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); + cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); + + cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; + cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; + + return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, + sizeof(struct ipts_set_mem_window_cmd)); +} + +static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) +{ + int ret; + + dev_info(ipts->dev, "Device %04hX:%04hX ready\n", + ipts->device_info.vendor_id, ipts->device_info.device_id); + ipts->status = IPTS_HOST_STATUS_STARTED; + + ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); + if (ret) + return ret; + + if (!gen7mt) + return 0; + + return ipts_control_set_feature(ipts, 0x5, 0x1); +} + +static int ipts_receiver_handle_feedback(struct ipts_context *ipts, + struct ipts_response *rsp) +{ + struct ipts_feedback_rsp feedback; + + if (ipts->status != IPTS_HOST_STATUS_STOPPING) + return 0; + + memcpy(&feedback, rsp->payload, sizeof(feedback)); + + if (feedback.buffer < IPTS_BUFFERS - 1) + return ipts_control_send_feedback(ipts, feedback.buffer + 1); + + return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); +} + +static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) +{ + ipts->status = IPTS_HOST_STATUS_STOPPED; + + if (ipts->restart) + return ipts_control_start(ipts); + + return 0; +} + +static bool ipts_receiver_sensor_was_reset(u32 status) +{ + return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || + status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; +} + +static bool ipts_receiver_handle_error(struct ipts_context *ipts, + struct ipts_response *rsp) +{ + bool error; + + switch (rsp->status) { + case IPTS_STATUS_SUCCESS: + case IPTS_STATUS_COMPAT_CHECK_FAIL: + error = false; + break; + case IPTS_STATUS_INVALID_PARAMS: + error = rsp->code != IPTS_RSP_FEEDBACK; + break; + case IPTS_STATUS_SENSOR_DISABLED: + error = ipts->status != IPTS_HOST_STATUS_STOPPING; + break; + default: + error = true; + break; + } + + if (!error) + return false; + + dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, + rsp->status); + + if (ipts_receiver_sensor_was_reset(rsp->status)) { + dev_err(ipts->dev, "Sensor was reset\n"); + + if (ipts_control_restart(ipts)) + dev_err(ipts->dev, "Failed to restart IPTS\n"); + } + + return true; +} + +static void ipts_receiver_handle_response(struct ipts_context *ipts, + struct ipts_response *rsp) +{ + int ret; + + if (ipts_receiver_handle_error(ipts, rsp)) + return; + + switch (rsp->code) { + case IPTS_RSP_GET_DEVICE_INFO: + ret = ipts_receiver_handle_get_device_info(ipts, rsp); + break; + case IPTS_RSP_SET_MODE: + ret = ipts_receiver_handle_set_mode(ipts); + break; + case IPTS_RSP_SET_MEM_WINDOW: + ret = ipts_receiver_handle_set_mem_window(ipts); + break; + case IPTS_RSP_FEEDBACK: + ret = ipts_receiver_handle_feedback(ipts, rsp); + break; + case IPTS_RSP_CLEAR_MEM_WINDOW: + ret = ipts_receiver_handle_clear_mem_window(ipts); + break; + default: + ret = 0; + break; + } + + if (!ret) + return; + + dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", + rsp->code, ret); + + if (ipts_control_stop(ipts)) + dev_err(ipts->dev, "Failed to stop IPTS\n"); +} + +void ipts_receiver_callback(struct mei_cl_device *cldev) +{ + int ret; + struct ipts_response rsp; + struct ipts_context *ipts; + + ipts = mei_cldev_get_drvdata(cldev); + + ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response)); + if (ret <= 0) { + dev_err(ipts->dev, "Error while reading response: %d\n", ret); + return; + } + + ipts_receiver_handle_response(ipts, &rsp); +} diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h new file mode 100644 index 000000000000..7f075afa7ef8 --- /dev/null +++ b/drivers/misc/ipts/receiver.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef _IPTS_RECEIVER_H_ +#define _IPTS_RECEIVER_H_ + +#include + +void ipts_receiver_callback(struct mei_cl_device *cldev); + +#endif /* _IPTS_RECEIVER_H_ */ diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c new file mode 100644 index 000000000000..8e3a2409e438 --- /dev/null +++ b/drivers/misc/ipts/resources.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include + +#include "context.h" + +void ipts_resources_free(struct ipts_context *ipts) +{ + int i; + struct ipts_buffer_info *buffers; + + u32 data_buffer_size = ipts->device_info.data_size; + u32 feedback_buffer_size = ipts->device_info.feedback_size; + + buffers = ipts->data; + for (i = 0; i < IPTS_BUFFERS; i++) { + if (!buffers[i].address) + continue; + + dma_free_coherent(ipts->dev, data_buffer_size, + buffers[i].address, buffers[i].dma_address); + + buffers[i].address = NULL; + buffers[i].dma_address = 0; + } + + buffers = ipts->feedback; + for (i = 0; i < IPTS_BUFFERS; i++) { + if (!buffers[i].address) + continue; + + dma_free_coherent(ipts->dev, feedback_buffer_size, + buffers[i].address, buffers[i].dma_address); + + buffers[i].address = NULL; + buffers[i].dma_address = 0; + } + + if (ipts->doorbell.address) { + dma_free_coherent(ipts->dev, sizeof(u32), + ipts->doorbell.address, + ipts->doorbell.dma_address); + + ipts->doorbell.address = NULL; + ipts->doorbell.dma_address = 0; + } + + if (ipts->workqueue.address) { + dma_free_coherent(ipts->dev, sizeof(u32), + ipts->workqueue.address, + ipts->workqueue.dma_address); + + ipts->workqueue.address = NULL; + ipts->workqueue.dma_address = 0; + } + + if (ipts->host2me.address) { + dma_free_coherent(ipts->dev, feedback_buffer_size, + ipts->host2me.address, + ipts->host2me.dma_address); + + ipts->host2me.address = NULL; + ipts->host2me.dma_address = 0; + } +} + +int ipts_resources_alloc(struct ipts_context *ipts) +{ + int i; + struct ipts_buffer_info *buffers; + + u32 data_buffer_size = ipts->device_info.data_size; + u32 feedback_buffer_size = ipts->device_info.feedback_size; + + buffers = ipts->data; + for (i = 0; i < IPTS_BUFFERS; i++) { + buffers[i].address = + dma_alloc_coherent(ipts->dev, data_buffer_size, + &buffers[i].dma_address, GFP_KERNEL); + + if (!buffers[i].address) + goto release_resources; + } + + buffers = ipts->feedback; + for (i = 0; i < IPTS_BUFFERS; i++) { + buffers[i].address = + dma_alloc_coherent(ipts->dev, feedback_buffer_size, + &buffers[i].dma_address, GFP_KERNEL); + + if (!buffers[i].address) + goto release_resources; + } + + ipts->doorbell.address = + dma_alloc_coherent(ipts->dev, sizeof(u32), + &ipts->doorbell.dma_address, GFP_KERNEL); + + if (!ipts->doorbell.address) + goto release_resources; + + ipts->workqueue.address = + dma_alloc_coherent(ipts->dev, sizeof(u32), + &ipts->workqueue.dma_address, GFP_KERNEL); + + if (!ipts->workqueue.address) + goto release_resources; + + ipts->host2me.address = + dma_alloc_coherent(ipts->dev, feedback_buffer_size, + &ipts->host2me.dma_address, GFP_KERNEL); + + if (!ipts->workqueue.address) + goto release_resources; + + return 0; + +release_resources: + + ipts_resources_free(ipts); + return -ENOMEM; +} diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h new file mode 100644 index 000000000000..fdac0eee9156 --- /dev/null +++ b/drivers/misc/ipts/resources.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef _IPTS_RESOURCES_H_ +#define _IPTS_RESOURCES_H_ + +#include "context.h" + +int ipts_resources_alloc(struct ipts_context *ipts); +void ipts_resources_free(struct ipts_context *ipts); + +#endif /* _IPTS_RESOURCES_H_ */ diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c new file mode 100644 index 000000000000..598f0710ad64 --- /dev/null +++ b/drivers/misc/ipts/uapi.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "protocol.h" +#include "uapi.h" + +struct ipts_uapi uapi; + +static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, + loff_t *offset) +{ + int buffer; + int maxbytes; + struct ipts_context *ipts = uapi.ipts; + + buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); + + if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) + return -ENODEV; + + maxbytes = ipts->device_info.data_size - *offset; + if (maxbytes <= 0 || count > maxbytes) + return -EINVAL; + + if (copy_to_user(buf, ipts->data[buffer].address + *offset, count)) + return -EFAULT; + + return count; +} + +static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, + unsigned long arg) +{ + void __user *buffer = (void __user *)arg; + u8 ready = 0; + + if (ipts) + ready = ipts->status == IPTS_HOST_STATUS_STARTED; + + if (copy_to_user(buffer, &ready, sizeof(u8))) + return -EFAULT; + + return 0; +} + +static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, + unsigned long arg) +{ + struct ipts_device_info info; + void __user *buffer = (void __user *)arg; + + if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) + return -ENODEV; + + info.vendor = ipts->device_info.vendor_id; + info.product = ipts->device_info.device_id; + info.version = ipts->device_info.fw_rev; + info.buffer_size = ipts->device_info.data_size; + info.max_contacts = ipts->device_info.max_contacts; + + if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info))) + return -EFAULT; + + return 0; +} + +static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, + unsigned long arg) +{ + void __user *buffer = (void __user *)arg; + + if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) + return -ENODEV; + + if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) + return -EFAULT; + + return 0; +} + +static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, + struct file *file) +{ + int ret; + u32 buffer; + + if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) + return -ENODEV; + + buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); + + ret = ipts_control_send_feedback(ipts, buffer); + if (ret) + return -EFAULT; + + return 0; +} + +static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) +{ + int ret; + struct ipts_reset_sensor_cmd cmd; + + if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) + return -ENODEV; + + memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); + cmd.type = IPTS_RESET_TYPE_SOFT; + + ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, + sizeof(struct ipts_reset_sensor_cmd)); + + if (ret) + return -EFAULT; + + return 0; +} + +static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct ipts_context *ipts = uapi.ipts; + + switch (cmd) { + case IPTS_IOCTL_GET_DEVICE_READY: + return ipts_uapi_ioctl_get_device_ready(ipts, arg); + case IPTS_IOCTL_GET_DEVICE_INFO: + return ipts_uapi_ioctl_get_device_info(ipts, arg); + case IPTS_IOCTL_GET_DOORBELL: + return ipts_uapi_ioctl_get_doorbell(ipts, arg); + case IPTS_IOCTL_SEND_FEEDBACK: + return ipts_uapi_ioctl_send_feedback(ipts, file); + case IPTS_IOCTL_SEND_RESET: + return ipts_uapi_ioctl_send_reset(ipts); + default: + return -ENOTTY; + } +} + +static const struct file_operations ipts_uapi_fops = { + .owner = THIS_MODULE, + .read = ipts_uapi_read, + .unlocked_ioctl = ipts_uapi_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ipts_uapi_ioctl, +#endif +}; + +void ipts_uapi_link(struct ipts_context *ipts) +{ + uapi.ipts = ipts; +} + +void ipts_uapi_unlink(void) +{ + uapi.ipts = NULL; +} + +int ipts_uapi_init(void) +{ + int i, major; + + alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts"); + uapi.class = class_create(THIS_MODULE, "ipts"); + + major = MAJOR(uapi.dev); + + cdev_init(&uapi.cdev, &ipts_uapi_fops); + uapi.cdev.owner = THIS_MODULE; + cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); + + for (i = 0; i < IPTS_BUFFERS; i++) { + device_create(uapi.class, NULL, MKDEV(major, i), NULL, + "ipts/%d", i); + } + + return 0; +} + +void ipts_uapi_free(void) +{ + int i; + int major; + + major = MAJOR(uapi.dev); + + for (i = 0; i < IPTS_BUFFERS; i++) + device_destroy(uapi.class, MKDEV(major, i)); + + cdev_del(&uapi.cdev); + + unregister_chrdev_region(MKDEV(major, 0), MINORMASK); + class_destroy(uapi.class); +} diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h new file mode 100644 index 000000000000..53fb86a88f97 --- /dev/null +++ b/drivers/misc/ipts/uapi.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef _IPTS_UAPI_H_ +#define _IPTS_UAPI_H_ + +#include + +#include "context.h" + +struct ipts_uapi { + dev_t dev; + struct class *class; + struct cdev cdev; + + struct ipts_context *ipts; +}; + +struct ipts_device_info { + __u16 vendor; + __u16 product; + __u32 version; + __u32 buffer_size; + __u8 max_contacts; + + /* For future expansion */ + __u8 reserved[19]; +}; + +#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) +#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) +#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) +#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) +#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) + +void ipts_uapi_link(struct ipts_context *ipts); +void ipts_uapi_unlink(void); + +int ipts_uapi_init(void); +void ipts_uapi_free(void); + +#endif /* _IPTS_UAPI_H_ */ -- 2.39.0 From 1b39126e5e54ffd417c985ca1e08736fca1a51ee Mon Sep 17 00:00:00 2001 From: Liban Hannan Date: Tue, 12 Apr 2022 23:31:12 +0100 Subject: [PATCH] iommu: ipts: use IOMMU passthrough mode for IPTS Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr 0x104ea3000 [fault reason 0x06] PTE Read access is not set This is very similar to the bug described at: https://bugs.launchpad.net/bugs/1958004 Fixed with the following patch which this patch basically copies: https://launchpadlibrarian.net/586396847/43255ca.diff Patchset: ipts --- drivers/iommu/intel/iommu.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index 644ca49e8cf8..408c321b929a 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -37,6 +37,8 @@ #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) +#define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ + ((pdev)->device == 0x9d3e)) #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) #define IOAPIC_RANGE_START (0xfee00000) @@ -286,12 +288,14 @@ int intel_iommu_enabled = 0; EXPORT_SYMBOL_GPL(intel_iommu_enabled); static int dmar_map_gfx = 1; +static int dmar_map_ipts = 1; static int intel_iommu_superpage = 1; static int iommu_identity_mapping; static int iommu_skip_te_disable; #define IDENTMAP_GFX 2 #define IDENTMAP_AZALIA 4 +#define IDENTMAP_IPTS 16 const struct iommu_ops intel_iommu_ops; @@ -2630,6 +2634,9 @@ static int device_def_domain_type(struct device *dev) if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) return IOMMU_DOMAIN_IDENTITY; + + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; } return 0; @@ -3019,6 +3026,9 @@ static int __init init_dmars(void) if (!dmar_map_gfx) iommu_identity_mapping |= IDENTMAP_GFX; + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + check_tylersburg_isoch(); ret = si_domain_init(hw_pass_through); @@ -4774,6 +4784,17 @@ static void quirk_iommu_igfx(struct pci_dev *dev) dmar_map_gfx = 0; } +static void quirk_iommu_ipts(struct pci_dev *dev) +{ + if (!IS_IPTS(dev)) + return; + + if (risky_device(dev)) + return; + + pci_info(dev, "Passthrough IOMMU for IPTS\n"); + dmar_map_ipts = 0; +} /* G4x/GM45 integrated gfx dmar support is totally busted. */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); @@ -4809,6 +4830,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); +/* disable IPTS dmar support */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); + static void quirk_iommu_rwbf(struct pci_dev *dev) { if (risky_device(dev)) -- 2.39.0 From cb437b5308cb73255a86b0a6f1f5e1178d8533f0 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 5 Nov 2022 21:23:53 +0100 Subject: [PATCH] platform/surface: aggregator: Improve documentation and handling of message target and source IDs The `tid_in` and `tid_out` fields of the serial hub protocol command struct (struct ssh_command) are actually source and target IDs, indicating the peer from which the message originated and the peer for which it is intended. Change the naming of those fields accordingly and improve the protocol documenation. Additionally, introduce an enum containing all currently known peers, i.e. targets and sources. Signed-off-by: Maximilian Luz Patchset: surface-sam --- .../driver-api/surface_aggregator/client.rst | 4 +- .../driver-api/surface_aggregator/ssh.rst | 36 +++++++++-------- .../platform/surface/aggregator/controller.c | 12 +++--- .../platform/surface/aggregator/ssh_msgb.h | 4 +- include/linux/surface_aggregator/controller.h | 4 +- include/linux/surface_aggregator/serial_hub.h | 40 +++++++++++++------ 6 files changed, 58 insertions(+), 42 deletions(-) diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst index 27f95abdbe99..9d7411223a84 100644 --- a/Documentation/driver-api/surface_aggregator/client.rst +++ b/Documentation/driver-api/surface_aggregator/client.rst @@ -191,7 +191,7 @@ data received from it is converted from little-endian to host endianness. * they do not correspond to an actual SAM/EC request. */ rqst.target_category = SSAM_SSH_TC_SAM; - rqst.target_id = 0x01; + rqst.target_id = SSAM_SSH_TID_SAM; rqst.command_id = 0x02; rqst.instance_id = 0x03; rqst.flags = SSAM_REQUEST_HAS_RESPONSE; @@ -241,7 +241,7 @@ one of the generator macros, for example via: SSAM_DEFINE_SYNC_REQUEST_W(__ssam_tmp_perf_mode_set, __le32, { .target_category = SSAM_SSH_TC_TMP, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x03, .instance_id = 0x00, }); diff --git a/Documentation/driver-api/surface_aggregator/ssh.rst b/Documentation/driver-api/surface_aggregator/ssh.rst index bf007d6c9873..18fd0f0aee84 100644 --- a/Documentation/driver-api/surface_aggregator/ssh.rst +++ b/Documentation/driver-api/surface_aggregator/ssh.rst @@ -13,6 +13,7 @@ .. |DATA_NSQ| replace:: ``DATA_NSQ`` .. |TC| replace:: ``TC`` .. |TID| replace:: ``TID`` +.. |SID| replace:: ``SID`` .. |IID| replace:: ``IID`` .. |RQID| replace:: ``RQID`` .. |CID| replace:: ``CID`` @@ -219,13 +220,13 @@ following fields, packed together and in order: - |u8| - Target category. - * - |TID| (out) + * - |TID| - |u8| - - Target ID for outgoing (host to EC) commands. + - Target ID for commands/messages. - * - |TID| (in) + * - |SID| - |u8| - - Target ID for incoming (EC to host) commands. + - Source ID for commands/messages. * - |IID| - |u8| @@ -286,19 +287,20 @@ general, however, a single target category should map to a single reserved event request ID. Furthermore, requests, responses, and events have an associated target ID -(``TID``). This target ID is split into output (host to EC) and input (EC to -host) fields, with the respecting other field (e.g. output field on incoming -messages) set to zero. Two ``TID`` values are known: Primary (``0x01``) and -secondary (``0x02``). In general, the response to a request should have the -same ``TID`` value, however, the field (output vs. input) should be used in -accordance to the direction in which the response is sent (i.e. on the input -field, as responses are generally sent from the EC to the host). - -Note that, even though requests and events should be uniquely identifiable -by target category and command ID alone, the EC may require specific -target ID and instance ID values to accept a command. A command that is -accepted for ``TID=1``, for example, may not be accepted for ``TID=2`` -and vice versa. +(``TID``) and source ID (``SID``). These two fields indicate where a message +originates from (``SID``) and what the intended target of the message is +(``TID``). Note that a response to a specific request therefore has the source +and target IDs swapped when compared to the original request (i.e. the request +target is the response source and the request source is the response target). +See (:c:type:`enum ssh_request_id `) for possible values of +both. + +Note that, even though requests and events should be uniquely identifiable by +target category and command ID alone, the EC may require specific target ID and +instance ID values to accept a command. A command that is accepted for +``TID=1``, for example, may not be accepted for ``TID=2`` and vice versa. While +this may not always hold in reality, you can think of different target/source +IDs indicating different physical ECs with potentially different feature sets. Limitations and Observations diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index 43e765199137..54bfb1f3d2dc 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -994,7 +994,7 @@ static void ssam_handle_event(struct ssh_rtl *rtl, item->rqid = get_unaligned_le16(&cmd->rqid); item->event.target_category = cmd->tc; - item->event.target_id = cmd->tid_in; + item->event.target_id = cmd->sid; item->event.command_id = cmd->cid; item->event.instance_id = cmd->iid; memcpy(&item->event.data[0], data->ptr, data->len); @@ -1777,35 +1777,35 @@ EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer); SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x13, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x15, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x16, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x33, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x34, .instance_id = 0x00, }); diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h index f3ecad92eefd..438873e06098 100644 --- a/drivers/platform/surface/aggregator/ssh_msgb.h +++ b/drivers/platform/surface/aggregator/ssh_msgb.h @@ -189,8 +189,8 @@ static inline void msgb_push_cmd(struct msgbuf *msgb, u8 seq, u16 rqid, __msgb_push_u8(msgb, SSH_PLD_TYPE_CMD); /* Payload type. */ __msgb_push_u8(msgb, rqst->target_category); /* Target category. */ - __msgb_push_u8(msgb, rqst->target_id); /* Target ID (out). */ - __msgb_push_u8(msgb, 0x00); /* Target ID (in). */ + __msgb_push_u8(msgb, rqst->target_id); /* Target ID. */ + __msgb_push_u8(msgb, SSAM_SSH_TID_HOST); /* Source ID. */ __msgb_push_u8(msgb, rqst->instance_id); /* Instance ID. */ __msgb_push_u16(msgb, rqid); /* Request ID. */ __msgb_push_u8(msgb, rqst->command_id); /* Command ID. */ diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h index d11a1c6e3186..8932bc0bae18 100644 --- a/include/linux/surface_aggregator/controller.h +++ b/include/linux/surface_aggregator/controller.h @@ -912,10 +912,10 @@ enum ssam_event_mask { }) #define SSAM_EVENT_REGISTRY_SAM \ - SSAM_EVENT_REGISTRY(SSAM_SSH_TC_SAM, 0x01, 0x0b, 0x0c) + SSAM_EVENT_REGISTRY(SSAM_SSH_TC_SAM, SSAM_SSH_TID_SAM, 0x0b, 0x0c) #define SSAM_EVENT_REGISTRY_KIP \ - SSAM_EVENT_REGISTRY(SSAM_SSH_TC_KIP, 0x02, 0x27, 0x28) + SSAM_EVENT_REGISTRY(SSAM_SSH_TC_KIP, SSAM_SSH_TID_KIP, 0x27, 0x28) #define SSAM_EVENT_REGISTRY_REG(tid)\ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, tid, 0x01, 0x02) diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h index 45501b6e54e8..5c4ae1a26183 100644 --- a/include/linux/surface_aggregator/serial_hub.h +++ b/include/linux/surface_aggregator/serial_hub.h @@ -83,23 +83,21 @@ enum ssh_payload_type { /** * struct ssh_command - Payload of a command-type frame. - * @type: The type of the payload. See &enum ssh_payload_type. Should be - * SSH_PLD_TYPE_CMD for this struct. - * @tc: Command target category. - * @tid_out: Output target ID. Should be zero if this an incoming (EC to host) - * message. - * @tid_in: Input target ID. Should be zero if this is an outgoing (host to - * EC) message. - * @iid: Instance ID. - * @rqid: Request ID. Used to match requests with responses and differentiate - * between responses and events. - * @cid: Command ID. + * @type: The type of the payload. See &enum ssh_payload_type. Should be + * SSH_PLD_TYPE_CMD for this struct. + * @tc: Command target category. + * @tid: Target ID. Indicates the target of the message. + * @sid: Source ID. Indicates the source of the message. + * @iid: Instance ID. + * @rqid: Request ID. Used to match requests with responses and differentiate + * between responses and events. + * @cid: Command ID. */ struct ssh_command { u8 type; u8 tc; - u8 tid_out; - u8 tid_in; + u8 tid; + u8 sid; u8 iid; __le16 rqid; u8 cid; @@ -280,6 +278,22 @@ struct ssam_span { size_t len; }; +/** + * enum ssam_ssh_tid - Target/source IDs for Serial Hub messages. + * @SSAM_SSH_TID_HOST: We as the kernel Serial Hub driver. + * @SSAM_SSH_TID_SAM: The Surface Aggregator EC. + * @SSAM_SSH_TID_KIP: Keyboard and perihperal controller. + * @SSAM_SSH_TID_DEBUG: Debug connector. + * @SSAM_SSH_TID_SURFLINK: SurfLink connector. + */ +enum ssam_ssh_tid { + SSAM_SSH_TID_HOST = 0x00, + SSAM_SSH_TID_SAM = 0x01, + SSAM_SSH_TID_KIP = 0x02, + SSAM_SSH_TID_DEBUG = 0x03, + SSAM_SSH_TID_SURFLINK = 0x04, +}; + /* * Known SSH/EC target categories. * -- 2.39.0 From e925dfd94cac673e7327260fd876a96e6fbb50a0 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 5 Nov 2022 21:30:58 +0100 Subject: [PATCH] platform/surface: aggregator: Ignore command messages not intended for us It is possible that we (the host/kernel driver) receive command messages that are not intended for us. Ignore those for now. The whole story is a bit more complicated: It is possible to enable debug output on SAM, which is sent via SSH command messages. By default this output is sent to a debug connector, with its own target ID (TID=0x03). It is possible to override the target of the debug output and set it to the host/kernel driver. This, however, does not change the original target ID of the message. Meaning, we receive messages with TID=0x03 (debug) but expect to only receive messages with TID=0x00 (host). The problem is that the different target ID also comes with a different scope of request IDs. In particular, these do not follow the standard event rules (i.e. do not fall into a set of small reserved values). Therefore, current message handling interprets them as responses to pending requests and tries to match them up via the request ID. However, these debug output messages are not in fact responses, and therefore this will at best fail to find the request and at worst pass on the wrong data as response for a request. Therefore ignore any command messages not intended for us (host) for now. We can implement support for the debug messages once we have a better understanding of them. Note that this may also provide a bit more stability and avoid some driver confusion in case any other targets want to talk to us in the future, since we don't yet know what to do with those as well. A warning for the dropped messages should suffice for now and also give us a chance of discovering new targets if they come along without any potential for bugs/instabilities. Signed-off-by: Maximilian Luz Patchset: surface-sam --- .../surface/aggregator/ssh_request_layer.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c index f5565570f16c..90634dcacabf 100644 --- a/drivers/platform/surface/aggregator/ssh_request_layer.c +++ b/drivers/platform/surface/aggregator/ssh_request_layer.c @@ -916,6 +916,21 @@ static void ssh_rtl_rx_command(struct ssh_ptl *p, const struct ssam_span *data) if (sshp_parse_command(dev, data, &command, &command_data)) return; + /* + * Check if the message was intended for us. If not, drop it. + * + * Note: We will need to change this to handle debug messages. On newer + * generation devices, these seem to be sent to SSAM_SSH_TID_DEBUG. We + * as host can still receive them as they can be forwarded via an + * override option on SAM, but doing so does not change the target ID + * to SSAM_SSH_TID_HOST. + */ + if (command->tid != SSAM_SSH_TID_HOST) { + rtl_warn(rtl, "rtl: dropping message not intended for us (tid = %#04x)\n", + command->tid); + return; + } + if (ssh_rqid_is_event(get_unaligned_le16(&command->rqid))) ssh_rtl_rx_event(rtl, command, &command_data); else -- 2.39.0 From ff41cec74dd5826fd4ea5b8f6627bfbe3743e1e6 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 5 Nov 2022 21:42:19 +0100 Subject: [PATCH] platform/surface: aggregator: Add target and source IDs to command trace events Add command source and target IDs to trace events. Tracing support for the Surface Aggregator driver was originally implemented at a time when only two peers were known: Host and SAM. We now know that there are at least five, with three actively being used (Host, SAM, KIP; four with Debug if you want to count manually enabling that interface). So it makes sense to also explicitly name the peers involved when tracing. Signed-off-by: Maximilian Luz Patchset: surface-sam --- drivers/platform/surface/aggregator/trace.h | 73 +++++++++++++++++++-- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h index 2a2c17771d01..55cc61bba1da 100644 --- a/drivers/platform/surface/aggregator/trace.h +++ b/drivers/platform/surface/aggregator/trace.h @@ -96,6 +96,7 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_POS); #define SSAM_SEQ_NOT_APPLICABLE ((u16)-1) #define SSAM_RQID_NOT_APPLICABLE ((u32)-1) #define SSAM_SSH_TC_NOT_APPLICABLE 0 +#define SSAM_SSH_TID_NOT_APPLICABLE ((u8)-1) #ifndef _SURFACE_AGGREGATOR_TRACE_HELPERS #define _SURFACE_AGGREGATOR_TRACE_HELPERS @@ -150,12 +151,44 @@ static inline u32 ssam_trace_get_request_id(const struct ssh_packet *p) return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(rqid)]); } +/** + * ssam_trace_get_request_tid() - Read the packet's request target ID. + * @p: The packet. + * + * Return: Returns the packet's request target ID (TID) field if the packet + * represents a request with command data, or %SSAM_SSH_TID_NOT_APPLICABLE + * if not (e.g. flush request, control packet). + */ +static inline u32 ssam_trace_get_request_tid(const struct ssh_packet *p) +{ + if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) + return SSAM_SSH_TID_NOT_APPLICABLE; + + return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(tid)]); +} + +/** + * ssam_trace_get_request_sid() - Read the packet's request source ID. + * @p: The packet. + * + * Return: Returns the packet's request source ID (SID) field if the packet + * represents a request with command data, or %SSAM_SSH_TID_NOT_APPLICABLE + * if not (e.g. flush request, control packet). + */ +static inline u32 ssam_trace_get_request_sid(const struct ssh_packet *p) +{ + if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) + return SSAM_SSH_TID_NOT_APPLICABLE; + + return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(sid)]); +} + /** * ssam_trace_get_request_tc() - Read the packet's request target category. * @p: The packet. * * Return: Returns the packet's request target category (TC) field if the - * packet represents a request with command data, or %SSAM_TC_NOT_APPLICABLE + * packet represents a request with command data, or %SSAM_SSH_TC_NOT_APPLICABLE * if not (e.g. flush request, control packet). */ static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) @@ -232,8 +265,18 @@ static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) { SSAM_RQID_NOT_APPLICABLE, "N/A" } \ ) -#define ssam_show_ssh_tc(rqid) \ - __print_symbolic(rqid, \ +#define ssam_show_ssh_tid(tid) \ + __print_symbolic(tid, \ + { SSAM_SSH_TID_NOT_APPLICABLE, "N/A" }, \ + { SSAM_SSH_TID_HOST, "Host" }, \ + { SSAM_SSH_TID_SAM, "SAM" }, \ + { SSAM_SSH_TID_KIP, "KIP" }, \ + { SSAM_SSH_TID_DEBUG, "Debug" }, \ + { SSAM_SSH_TID_SURFLINK, "SurfLink" } \ + ) + +#define ssam_show_ssh_tc(tc) \ + __print_symbolic(tc, \ { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ { SSAM_SSH_TC_SAM, "SAM" }, \ { SSAM_SSH_TC_BAT, "BAT" }, \ @@ -313,6 +356,8 @@ DECLARE_EVENT_CLASS(ssam_command_class, TP_STRUCT__entry( __field(u16, rqid) __field(u16, len) + __field(u8, tid) + __field(u8, sid) __field(u8, tc) __field(u8, cid) __field(u8, iid) @@ -320,14 +365,18 @@ DECLARE_EVENT_CLASS(ssam_command_class, TP_fast_assign( __entry->rqid = get_unaligned_le16(&cmd->rqid); + __entry->tid = cmd->tid; + __entry->sid = cmd->sid; __entry->tc = cmd->tc; __entry->cid = cmd->cid; __entry->iid = cmd->iid; __entry->len = len; ), - TP_printk("rqid=%#06x, tc=%s, cid=%#04x, iid=%#04x, len=%u", + TP_printk("rqid=%#06x, tid=%s, sid=%s, tc=%s, cid=%#04x, iid=%#04x, len=%u", __entry->rqid, + ssam_show_ssh_tid(__entry->tid), + ssam_show_ssh_tid(__entry->sid), ssam_show_ssh_tc(__entry->tc), __entry->cid, __entry->iid, @@ -430,6 +479,8 @@ DECLARE_EVENT_CLASS(ssam_request_class, __field(u8, tc) __field(u16, cid) __field(u16, iid) + __field(u8, tid) + __field(u8, sid) ), TP_fast_assign( @@ -439,16 +490,20 @@ DECLARE_EVENT_CLASS(ssam_request_class, __entry->state = READ_ONCE(request->state); __entry->rqid = ssam_trace_get_request_id(p); ssam_trace_ptr_uid(p, __entry->uid); + __entry->tid = ssam_trace_get_request_tid(p); + __entry->sid = ssam_trace_get_request_sid(p); __entry->tc = ssam_trace_get_request_tc(p); __entry->cid = ssam_trace_get_command_field_u8(p, cid); __entry->iid = ssam_trace_get_command_field_u8(p, iid); ), - TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s", + TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tid=%s, sid=%s, tc=%s, cid=%s, iid=%s", __entry->uid, ssam_show_request_id(__entry->rqid), ssam_show_request_type(__entry->state), ssam_show_request_state(__entry->state), + ssam_show_ssh_tid(__entry->tid), + ssam_show_ssh_tid(__entry->sid), ssam_show_ssh_tc(__entry->tc), ssam_show_generic_u8_field(__entry->cid), ssam_show_generic_u8_field(__entry->iid) @@ -474,6 +529,8 @@ DECLARE_EVENT_CLASS(ssam_request_status_class, __field(u8, tc) __field(u16, cid) __field(u16, iid) + __field(u8, tid) + __field(u8, sid) ), TP_fast_assign( @@ -484,16 +541,20 @@ DECLARE_EVENT_CLASS(ssam_request_status_class, __entry->rqid = ssam_trace_get_request_id(p); __entry->status = status; ssam_trace_ptr_uid(p, __entry->uid); + __entry->tid = ssam_trace_get_request_tid(p); + __entry->sid = ssam_trace_get_request_sid(p); __entry->tc = ssam_trace_get_request_tc(p); __entry->cid = ssam_trace_get_command_field_u8(p, cid); __entry->iid = ssam_trace_get_command_field_u8(p, iid); ), - TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s, status=%d", + TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tid=%s, sid=%s, tc=%s, cid=%s, iid=%s, status=%d", __entry->uid, ssam_show_request_id(__entry->rqid), ssam_show_request_type(__entry->state), ssam_show_request_state(__entry->state), + ssam_show_ssh_tid(__entry->tid), + ssam_show_ssh_tid(__entry->sid), ssam_show_ssh_tc(__entry->tc), ssam_show_generic_u8_field(__entry->cid), ssam_show_generic_u8_field(__entry->iid), -- 2.39.0 From 698898dad24512d70a21ab78844e617486028a25 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 5 Nov 2022 21:32:16 +0100 Subject: [PATCH] platform/surface: aggregator_hub: Use target-ID enum instead of hard-coding values Instead of hard-coding the target ID, use the respective enum ssam_ssh_tid value. Signed-off-by: Maximilian Luz Patchset: surface-sam --- drivers/platform/surface/surface_aggregator_hub.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c index 43061514be38..62f27cdb6ca8 100644 --- a/drivers/platform/surface/surface_aggregator_hub.c +++ b/drivers/platform/surface/surface_aggregator_hub.c @@ -214,7 +214,7 @@ static void ssam_hub_remove(struct ssam_device *sdev) SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x0d, .instance_id = 0x00, }); @@ -292,7 +292,7 @@ static const struct ssam_hub_desc base_hub = { SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_query_state, u8, { .target_category = SSAM_SSH_TC_KIP, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x2c, .instance_id = 0x00, }); -- 2.39.0 From 3c5d1c4ef943afa97f68879843797aff93f62395 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 5 Nov 2022 21:33:26 +0100 Subject: [PATCH] platform/surface: aggregator_tabletsw: Use target-ID enum instead of hard-coding values Instead of hard-coding the target ID, use the respective enum ssam_ssh_tid value. Signed-off-by: Maximilian Luz Patchset: surface-sam --- drivers/platform/surface/surface_aggregator_tabletsw.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c index 27d95a6a7851..bd8cd453c393 100644 --- a/drivers/platform/surface/surface_aggregator_tabletsw.c +++ b/drivers/platform/surface/surface_aggregator_tabletsw.c @@ -247,7 +247,7 @@ static bool ssam_kip_cover_state_is_tablet_mode(struct ssam_tablet_sw *sw, u32 s SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_cover_state, u8, { .target_category = SSAM_SSH_TC_KIP, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x1d, .instance_id = 0x00, }); @@ -371,7 +371,7 @@ static int ssam_pos_get_sources_list(struct ssam_tablet_sw *sw, struct ssam_sour int status; rqst.target_category = SSAM_SSH_TC_POS; - rqst.target_id = 0x01; + rqst.target_id = SSAM_SSH_TID_SAM; rqst.command_id = 0x01; rqst.instance_id = 0x00; rqst.flags = SSAM_REQUEST_HAS_RESPONSE; @@ -430,7 +430,7 @@ static int ssam_pos_get_source(struct ssam_tablet_sw *sw, u32 *source_id) SSAM_DEFINE_SYNC_REQUEST_WR(__ssam_pos_get_posture_for_source, __le32, __le32, { .target_category = SSAM_SSH_TC_POS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x02, .instance_id = 0x00, }); -- 2.39.0 From cbe57259ff7b94af0ae62170a9814594fe1f6705 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 5 Nov 2022 21:34:06 +0100 Subject: [PATCH] platform/surface: dtx: Use target-ID enum instead of hard-coding values Instead of hard-coding the target ID, use the respective enum ssam_ssh_tid value. Signed-off-by: Maximilian Luz Patchset: surface-sam --- drivers/platform/surface/surface_dtx.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c index ed36944467f9..0de76a784a35 100644 --- a/drivers/platform/surface/surface_dtx.c +++ b/drivers/platform/surface/surface_dtx.c @@ -71,63 +71,63 @@ static_assert(sizeof(struct ssam_bas_base_info) == 2); SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x06, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x07, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x08, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x09, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x0a, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x0b, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x0c, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x0d, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x11, .instance_id = 0x00, }); -- 2.39.0 From f79e434b9f3851364689210195aea86a1720c466 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 5 Nov 2022 21:40:06 +0100 Subject: [PATCH] HID: surface-hid: Use target-ID enum instead of hard-coding values Instead of hard-coding the target ID, use the respective enum ssam_ssh_tid value. Signed-off-by: Maximilian Luz Patchset: surface-sam --- drivers/hid/surface-hid/surface_kbd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c index 0635341bc517..42933bf3e925 100644 --- a/drivers/hid/surface-hid/surface_kbd.c +++ b/drivers/hid/surface-hid/surface_kbd.c @@ -250,7 +250,7 @@ static int surface_kbd_probe(struct platform_device *pdev) shid->uid.domain = SSAM_DOMAIN_SERIALHUB; shid->uid.category = SSAM_SSH_TC_KBD; - shid->uid.target = 2; + shid->uid.target = SSAM_SSH_TID_KIP; shid->uid.instance = 0; shid->uid.function = 0; -- 2.39.0 From 495ca177f24e437bfd86c75057f120e4c836149c Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 5 Nov 2022 21:45:28 +0100 Subject: [PATCH] platform/surface: aggregator: Enforce use of target-ID enum in device ID macros Similar to the target category (TC), the target ID (TID) can be one value out of a small number of choices, given in enum ssam_ssh_tid. In the device ID macros, SSAM_SDEV() and SSAM_VDEV() we already use text expansion to, both, remove some textual clutter for the target category values and enforce that the value belongs to the known set. Now that we know the names for the target IDs, use the same trick for them as well. Also rename the SSAM_ANY_x macros to SSAM_SSH_x_ANY to better fit in. Signed-off-by: Maximilian Luz Patchset: surface-sam --- drivers/hid/surface-hid/surface_hid.c | 2 +- .../platform/surface/surface_aggregator_hub.c | 4 +- .../surface/surface_aggregator_tabletsw.c | 4 +- drivers/platform/surface/surface_dtx.c | 2 +- .../surface/surface_platform_profile.c | 2 +- drivers/power/supply/surface_battery.c | 4 +- drivers/power/supply/surface_charger.c | 2 +- include/linux/surface_aggregator/device.h | 50 +++++++++---------- 8 files changed, 35 insertions(+), 35 deletions(-) diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c index d4aa8c81903a..aa80d83a83d1 100644 --- a/drivers/hid/surface-hid/surface_hid.c +++ b/drivers/hid/surface-hid/surface_hid.c @@ -230,7 +230,7 @@ static void surface_hid_remove(struct ssam_device *sdev) } static const struct ssam_device_id surface_hid_match[] = { - { SSAM_SDEV(HID, SSAM_ANY_TID, SSAM_ANY_IID, 0x00) }, + { SSAM_SDEV(HID, ANY, SSAM_SSH_IID_ANY, 0x00) }, { }, }; MODULE_DEVICE_TABLE(ssam, surface_hid_match); diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c index 62f27cdb6ca8..6abd1efe2088 100644 --- a/drivers/platform/surface/surface_aggregator_hub.c +++ b/drivers/platform/surface/surface_aggregator_hub.c @@ -348,8 +348,8 @@ static const struct ssam_hub_desc kip_hub = { /* -- Driver registration. -------------------------------------------------- */ static const struct ssam_device_id ssam_hub_match[] = { - { SSAM_VDEV(HUB, 0x01, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, - { SSAM_VDEV(HUB, 0x02, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, + { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, + { SSAM_VDEV(HUB, KIP, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, { } }; MODULE_DEVICE_TABLE(ssam, ssam_hub_match); diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c index bd8cd453c393..6147aa887939 100644 --- a/drivers/platform/surface/surface_aggregator_tabletsw.c +++ b/drivers/platform/surface/surface_aggregator_tabletsw.c @@ -510,8 +510,8 @@ static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { /* -- Driver registration. -------------------------------------------------- */ static const struct ssam_device_id ssam_tablet_sw_match[] = { - { SSAM_SDEV(KIP, 0x01, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, - { SSAM_SDEV(POS, 0x01, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, + { SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, + { SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, { }, }; MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c index 0de76a784a35..30cbde278c59 100644 --- a/drivers/platform/surface/surface_dtx.c +++ b/drivers/platform/surface/surface_dtx.c @@ -1214,7 +1214,7 @@ static void surface_dtx_ssam_remove(struct ssam_device *sdev) } static const struct ssam_device_id surface_dtx_ssam_match[] = { - { SSAM_SDEV(BAS, 0x01, 0x00, 0x00) }, + { SSAM_SDEV(BAS, SAM, 0x00, 0x00) }, { }, }; MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match); diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c index fbf2e11fd6ce..f433a13c3689 100644 --- a/drivers/platform/surface/surface_platform_profile.c +++ b/drivers/platform/surface/surface_platform_profile.c @@ -169,7 +169,7 @@ static void surface_platform_profile_remove(struct ssam_device *sdev) } static const struct ssam_device_id ssam_platform_profile_match[] = { - { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) }, + { SSAM_SDEV(TMP, SAM, 0x00, 0x01) }, { }, }; MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match); diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c index 540707882bb0..19d2f8834e56 100644 --- a/drivers/power/supply/surface_battery.c +++ b/drivers/power/supply/surface_battery.c @@ -852,8 +852,8 @@ static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { }; static const struct ssam_device_id surface_battery_match[] = { - { SSAM_SDEV(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, - { SSAM_SDEV(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, + { SSAM_SDEV(BAT, SAM, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, + { SSAM_SDEV(BAT, KIP, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, { }, }; MODULE_DEVICE_TABLE(ssam, surface_battery_match); diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c index 59182d55742d..cabdd8da12d0 100644 --- a/drivers/power/supply/surface_charger.c +++ b/drivers/power/supply/surface_charger.c @@ -260,7 +260,7 @@ static const struct spwr_psy_properties spwr_psy_props_adp1 = { }; static const struct ssam_device_id surface_ac_match[] = { - { SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, + { SSAM_SDEV(BAT, SAM, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, { }, }; MODULE_DEVICE_TABLE(ssam, surface_ac_match); diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h index 46c45d1b6368..4da20b7a0ee5 100644 --- a/include/linux/surface_aggregator/device.h +++ b/include/linux/surface_aggregator/device.h @@ -68,9 +68,9 @@ struct ssam_device_uid { * match_flags member of the device ID structure. Do not use them directly * with struct ssam_device_id or struct ssam_device_uid. */ -#define SSAM_ANY_TID 0xffff -#define SSAM_ANY_IID 0xffff -#define SSAM_ANY_FUN 0xffff +#define SSAM_SSH_TID_ANY 0xffff +#define SSAM_SSH_IID_ANY 0xffff +#define SSAM_SSH_FUN_ANY 0xffff /** * SSAM_DEVICE() - Initialize a &struct ssam_device_id with the given @@ -83,25 +83,25 @@ struct ssam_device_uid { * * Initializes a &struct ssam_device_id with the given parameters. See &struct * ssam_device_uid for details regarding the parameters. The special values - * %SSAM_ANY_TID, %SSAM_ANY_IID, and %SSAM_ANY_FUN can be used to specify that + * %SSAM_SSH_TID_ANY, %SSAM_SSH_IID_ANY, and %SSAM_SSH_FUN_ANY can be used to specify that * matching should ignore target ID, instance ID, and/or sub-function, * respectively. This macro initializes the ``match_flags`` field based on the * given parameters. * * Note: The parameters @d and @cat must be valid &u8 values, the parameters - * @tid, @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, - * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not + * @tid, @iid, and @fun must be either valid &u8 values or %SSAM_SSH_TID_ANY, + * %SSAM_SSH_IID_ANY, or %SSAM_SSH_FUN_ANY, respectively. Other non-&u8 values are not * allowed. */ #define SSAM_DEVICE(d, cat, tid, iid, fun) \ - .match_flags = (((tid) != SSAM_ANY_TID) ? SSAM_MATCH_TARGET : 0) \ - | (((iid) != SSAM_ANY_IID) ? SSAM_MATCH_INSTANCE : 0) \ - | (((fun) != SSAM_ANY_FUN) ? SSAM_MATCH_FUNCTION : 0), \ + .match_flags = (((tid) != SSAM_SSH_TID_ANY) ? SSAM_MATCH_TARGET : 0) \ + | (((iid) != SSAM_SSH_IID_ANY) ? SSAM_MATCH_INSTANCE : 0) \ + | (((fun) != SSAM_SSH_FUN_ANY) ? SSAM_MATCH_FUNCTION : 0), \ .domain = d, \ .category = cat, \ - .target = __builtin_choose_expr((tid) != SSAM_ANY_TID, (tid), 0), \ - .instance = __builtin_choose_expr((iid) != SSAM_ANY_IID, (iid), 0), \ - .function = __builtin_choose_expr((fun) != SSAM_ANY_FUN, (fun), 0) + .target = __builtin_choose_expr((tid) != SSAM_SSH_TID_ANY, (tid), 0), \ + .instance = __builtin_choose_expr((iid) != SSAM_SSH_IID_ANY, (iid), 0), \ + .function = __builtin_choose_expr((fun) != SSAM_SSH_FUN_ANY, (fun), 0) /** * SSAM_VDEV() - Initialize a &struct ssam_device_id as virtual device with @@ -113,18 +113,18 @@ struct ssam_device_uid { * * Initializes a &struct ssam_device_id with the given parameters in the * virtual domain. See &struct ssam_device_uid for details regarding the - * parameters. The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and - * %SSAM_ANY_FUN can be used to specify that matching should ignore target ID, + * parameters. The special values %SSAM_SSH_TID_ANY, %SSAM_SSH_IID_ANY, and + * %SSAM_SSH_FUN_ANY can be used to specify that matching should ignore target ID, * instance ID, and/or sub-function, respectively. This macro initializes the * ``match_flags`` field based on the given parameters. * * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, - * @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, - * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not + * @iid, and @fun must be either valid &u8 values or %SSAM_SSH_TID_ANY, + * %SSAM_SSH_IID_ANY, or %SSAM_SSH_FUN_ANY, respectively. Other non-&u8 values are not * allowed. */ #define SSAM_VDEV(cat, tid, iid, fun) \ - SSAM_DEVICE(SSAM_DOMAIN_VIRTUAL, SSAM_VIRTUAL_TC_##cat, tid, iid, fun) + SSAM_DEVICE(SSAM_DOMAIN_VIRTUAL, SSAM_VIRTUAL_TC_##cat, SSAM_SSH_TID_##tid, iid, fun) /** * SSAM_SDEV() - Initialize a &struct ssam_device_id as physical SSH device @@ -136,18 +136,18 @@ struct ssam_device_uid { * * Initializes a &struct ssam_device_id with the given parameters in the SSH * domain. See &struct ssam_device_uid for details regarding the parameters. - * The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and %SSAM_ANY_FUN can be - * used to specify that matching should ignore target ID, instance ID, and/or - * sub-function, respectively. This macro initializes the ``match_flags`` - * field based on the given parameters. + * The special values %SSAM_SSH_TID_ANY, %SSAM_SSH_IID_ANY, and + * %SSAM_SSH_FUN_ANY can be used to specify that matching should ignore target + * ID, instance ID, and/or sub-function, respectively. This macro initializes + * the ``match_flags`` field based on the given parameters. * * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, - * @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, - * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not - * allowed. + * @iid, and @fun must be either valid &u8 values or %SSAM_SSH_TID_ANY, + * %SSAM_SSH_IID_ANY, or %SSAM_SSH_FUN_ANY, respectively. Other non-&u8 values + * are not allowed. */ #define SSAM_SDEV(cat, tid, iid, fun) \ - SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) + SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, SSAM_SSH_TID_##tid, iid, fun) /* * enum ssam_device_flags - Flags for SSAM client devices. -- 2.39.0 From edf30b3b5d55e5b3d8064d2edc60b594fe684a3b Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 5 Nov 2022 21:46:37 +0100 Subject: [PATCH] platform/surface: aggregator_registry: Fix target-ID of base-hub The target ID of the base hub is currently set to KIP (keyboard/ peripherals). However, even though it manages such devices with the KIP target ID, the base hub itself is actually accessed via the SAM target ID. So set it accordingly. Signed-off-by: Maximilian Luz Patchset: surface-sam --- drivers/platform/surface/surface_aggregator_hub.c | 2 +- drivers/platform/surface/surface_aggregator_registry.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c index 6abd1efe2088..8b8b80228c14 100644 --- a/drivers/platform/surface/surface_aggregator_hub.c +++ b/drivers/platform/surface/surface_aggregator_hub.c @@ -349,7 +349,7 @@ static const struct ssam_hub_desc kip_hub = { static const struct ssam_device_id ssam_hub_match[] = { { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, - { SSAM_VDEV(HUB, KIP, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, + { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, { } }; MODULE_DEVICE_TABLE(ssam, ssam_hub_match); diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index 023f126121d7..296f72d52e6a 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -46,7 +46,7 @@ static const struct software_node ssam_node_hub_kip = { /* Base device hub (devices attached to Surface Book 3 base). */ static const struct software_node ssam_node_hub_base = { - .name = "ssam:00:00:02:11:00", + .name = "ssam:00:00:01:11:00", .parent = &ssam_node_root, }; -- 2.39.0 From 0d55229782727243733f867a6b8148276d63fdd3 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 25 Jul 2020 17:19:53 +0200 Subject: [PATCH] i2c: acpi: Implement RawBytes read access Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C device via a generic serial bus operation region and RawBytes read access. On the Surface Book 1, this access is required to turn on (and off) the discrete GPU. Multiple things are to note here: a) The RawBytes access is device/driver dependent. The ACPI specification states: > Raw accesses assume that the writer has knowledge of the bus that > the access is made over and the device that is being accessed. The > protocol may only ensure that the buffer is transmitted to the > appropriate driver, but the driver must be able to interpret the > buffer to communicate to a register. Thus this implementation may likely not work on other devices accessing I2C via the RawBytes accessor type. b) The MSHW0030 I2C device is an HID-over-I2C device which seems to serve multiple functions: 1. It is the main access point for the legacy-type Surface Aggregator Module (also referred to as SAM-over-HID, as opposed to the newer SAM-over-SSH/UART). It has currently not been determined on how support for the legacy SAM should be implemented. Likely via a custom HID driver. 2. It seems to serve as the HID device for the Integrated Sensor Hub. This might complicate matters with regards to implementing a SAM-over-HID driver required by legacy SAM. In light of this, the simplest approach has been chosen for now. However, it may make more sense regarding breakage and compatibility to either provide functionality for replacing or enhancing the default operation region handler via some additional API functions, or even to completely blacklist MSHW0030 from the I2C core and provide a custom driver for it. Replacing/enhancing the default operation region handler would, however, either require some sort of secondary driver and access point for it, from which the new API functions would be called and the new handler (part) would be installed, or hard-coding them via some sort of quirk-like interface into the I2C core. Signed-off-by: Maximilian Luz Patchset: surface-sam-over-hid --- drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c index 4dd777cc0c89..b2338618163a 100644 --- a/drivers/i2c/i2c-core-acpi.c +++ b/drivers/i2c/i2c-core-acpi.c @@ -639,6 +639,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, return (ret == 1) ? 0 : -EIO; } +static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, + u8 *data, u8 data_len) +{ + struct i2c_msg msgs[1]; + int ret = AE_OK; + + msgs[0].addr = client->addr; + msgs[0].flags = client->flags; + msgs[0].len = data_len + 1; + msgs[0].buf = data; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + + if (ret < 0) { + dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); + return ret; + } + + /* 1 transfer must have completed successfully */ + return (ret == 1) ? 0 : -EIO; +} + static acpi_status i2c_acpi_space_handler(u32 function, acpi_physical_address command, u32 bits, u64 *value64, @@ -740,6 +762,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.39.0 From f65d13f177fe2c309ad238254caaae4e4312cefd Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 13 Feb 2021 16:41:18 +0100 Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch Add driver exposing the discrete GPU power-switch of the Microsoft Surface Book 1 to user-space. On the Surface Book 1, the dGPU power is controlled via the Surface System Aggregator Module (SAM). The specific SAM-over-HID command for this is exposed via ACPI. This module provides a simple driver exposing the ACPI call via a sysfs parameter to user-space, so that users can easily power-on/-off the dGPU. Patchset: surface-sam-over-hid --- drivers/platform/surface/Kconfig | 7 + drivers/platform/surface/Makefile | 1 + .../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index b629e82af97c..68656e8f309e 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH Select M or Y here, if you want to provide tablet-mode switch input events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. +config SURFACE_BOOK1_DGPU_SWITCH + tristate "Surface Book 1 dGPU Switch Driver" + depends on SYSFS + help + This driver provides a sysfs switch to set the power-state of the + discrete GPU found on the Microsoft Surface Book 1. + config SURFACE_DTX tristate "Surface DTX (Detachment System) Driver" depends on SURFACE_AGGREGATOR diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 53344330939b..7efcd0cdb532 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o +obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c new file mode 100644 index 000000000000..8b816ed8f35c --- /dev/null +++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + + +#ifdef pr_fmt +#undef pr_fmt +#endif +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ + + +static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, + 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); + +#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" +#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" +#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" + + +static int sb1_dgpu_sw_dsmcall(void) +{ + union acpi_object *ret; + acpi_handle handle; + acpi_status status; + + status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); + if (status) + return -EINVAL; + + ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); + if (!ret) + return -EINVAL; + + ACPI_FREE(ret); + return 0; +} + +static int sb1_dgpu_sw_hgon(void) +{ + struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; + acpi_status status; + + status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); + if (status) { + pr_err("failed to run HGON: %d\n", status); + return -EINVAL; + } + + if (buf.pointer) + ACPI_FREE(buf.pointer); + + pr_info("turned-on dGPU via HGON\n"); + return 0; +} + +static int sb1_dgpu_sw_hgof(void) +{ + struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; + acpi_status status; + + status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); + if (status) { + pr_err("failed to run HGOF: %d\n", status); + return -EINVAL; + } + + if (buf.pointer) + ACPI_FREE(buf.pointer); + + pr_info("turned-off dGPU via HGOF\n"); + return 0; +} + + +static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + int status, value; + + status = kstrtoint(buf, 0, &value); + if (status < 0) + return status; + + if (value != 1) + return -EINVAL; + + status = sb1_dgpu_sw_dsmcall(); + + return status < 0 ? status : len; +} + +static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + bool power; + int status; + + status = kstrtobool(buf, &power); + if (status < 0) + return status; + + if (power) + status = sb1_dgpu_sw_hgon(); + else + status = sb1_dgpu_sw_hgof(); + + return status < 0 ? status : len; +} + +static DEVICE_ATTR_WO(dgpu_dsmcall); +static DEVICE_ATTR_WO(dgpu_power); + +static struct attribute *sb1_dgpu_sw_attrs[] = { + &dev_attr_dgpu_dsmcall.attr, + &dev_attr_dgpu_power.attr, + NULL, +}; + +static const struct attribute_group sb1_dgpu_sw_attr_group = { + .attrs = sb1_dgpu_sw_attrs, +}; + + +static int sb1_dgpu_sw_probe(struct platform_device *pdev) +{ + return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); +} + +static int sb1_dgpu_sw_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); + return 0; +} + +/* + * The dGPU power seems to be actually handled by MSHW0040. However, that is + * also the power-/volume-button device with a mainline driver. So let's use + * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. + */ +static const struct acpi_device_id sb1_dgpu_sw_match[] = { + { "MSHW0041", }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); + +static struct platform_driver sb1_dgpu_sw = { + .probe = sb1_dgpu_sw_probe, + .remove = sb1_dgpu_sw_remove, + .driver = { + .name = "surfacebook1_dgpu_switch", + .acpi_match_table = sb1_dgpu_sw_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_platform_driver(sb1_dgpu_sw); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); +MODULE_LICENSE("GPL"); -- 2.39.0 From 183996b2e1e626c5c40a986fa25084b11429daa5 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 09489380afda..0f02411a60f1 100644 --- a/drivers/input/misc/soc_button_array.c +++ b/drivers/input/misc/soc_button_array.c @@ -507,8 +507,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 @@ -519,31 +519,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.39.0 From 87897dbc255aaac49a53478463e1f7d23f3e5622 Mon Sep 17 00:00:00 2001 From: Sachi King Date: Tue, 5 Oct 2021 00:22:57 +1100 Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd variant The AMD variant of the Surface Laptop report 0 for their OEM platform revision. The Surface devices that require the surfacepro3_button driver do not have the _DSM that gets the OEM platform revision. If the method does not exist, load surfacepro3_button. Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") Co-developed-by: Maximilian Luz Signed-off-by: Sachi King Patchset: surface-button --- drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c index 242fb690dcaf..30eea54dbb47 100644 --- a/drivers/platform/surface/surfacepro3_button.c +++ b/drivers/platform/surface/surfacepro3_button.c @@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) /* * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device * ID (MSHW0040) for the power/volume buttons. Make sure this is the right - * device by checking for the _DSM method and OEM Platform Revision. + * device by checking for the _DSM method and OEM Platform Revision DSM + * function. * * Returns true if the driver should bind to this device, i.e. the device is * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. @@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) static bool surface_button_check_MSHW0040(struct acpi_device *dev) { acpi_handle handle = dev->handle; - union acpi_object *result; - u64 oem_platform_rev = 0; // valid revisions are nonzero - - // get OEM platform revision - result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, - MSHW0040_DSM_REVISION, - MSHW0040_DSM_GET_OMPR, - NULL, ACPI_TYPE_INTEGER); - - /* - * If evaluating the _DSM fails, the method is not present. This means - * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we - * should use this driver. We use revision 0 indicating it is - * unavailable. - */ - - if (result) { - oem_platform_rev = result->integer.value; - ACPI_FREE(result); - } - - dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); - return oem_platform_rev == 0; + // make sure that OEM platform revision DSM call does not exist + return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, + MSHW0040_DSM_REVISION, + BIT(MSHW0040_DSM_GET_OMPR)); } -- 2.39.0 From 172e2d4a0907bdf22b8bc6d7468c96481a7f355a 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 372cbdd223e0..fe849df6a948 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -34,7 +34,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); MODULE_LICENSE("GPL"); #include "hid-ids.h" +#include "usbhid/usbhid.h" /* quirks to control the device */ #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) @@ -71,12 +75,15 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) #define MT_QUIRK_DISABLE_WAKEUP BIT(21) +#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 #define MT_BUTTONTYPE_CLICKPAD 0 +#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 + enum latency_mode { HID_LATENCY_NORMAL = 0, HID_LATENCY_HIGH = 1, @@ -168,6 +175,8 @@ struct mt_device { struct list_head applications; struct list_head reports; + + struct notifier_block pm_notifier; }; static void mt_post_parse_default_settings(struct mt_device *td, @@ -212,6 +221,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_GOOGLE 0x0111 #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 #define MT_CLS_SMART_TECH 0x0113 +#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 #define MT_DEFAULT_MAXCONTACT 10 #define MT_MAX_MAXCONTACT 250 @@ -396,6 +406,16 @@ static const struct mt_class mt_classes[] = { MT_QUIRK_CONTACT_CNT_ACCURATE | MT_QUIRK_SEPARATE_APP_REPORT, }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_STICKY_FINGERS | + MT_QUIRK_WIN8_PTP_BUTTONS, + .export_all_inputs = true + }, { } }; @@ -1706,6 +1726,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; @@ -1729,6 +1812,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); @@ -1758,15 +1844,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) timer_setup(&td->release_timer, mt_expired_timeout, 0); ret = hid_parse(hdev); - if (ret != 0) + if (ret != 0) { + unregister_pm_notifier(&td->pm_notifier); return ret; + } if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) mt_fix_const_fields(hdev, HID_DG_CONTACTID); ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); - if (ret) + if (ret) { + unregister_pm_notifier(&td->pm_notifier); return ret; + } ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); if (ret) @@ -1818,6 +1908,7 @@ static void mt_remove(struct hid_device *hdev) { struct mt_device *td = hid_get_drvdata(hdev); + unregister_pm_notifier(&td->pm_notifier); del_timer_sync(&td->release_timer); sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); @@ -2195,6 +2286,11 @@ static const struct hid_device_id mt_devices[] = { MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, USB_DEVICE_ID_XIROKU_CSR2) }, + /* Microsoft Surface type cover */ + { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, + USB_VENDOR_ID_MICROSOFT, 0x09c0) }, + /* Google MT devices */ { .driver_data = MT_CLS_GOOGLE, HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, -- 2.39.0 From 0a6239b736a59c94c1039d2c1fc553e450b3f721 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 fe849df6a948..3a7a43eb9fbe 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -76,6 +76,7 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) #define MT_QUIRK_DISABLE_WAKEUP BIT(21) #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) +#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(23) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -83,6 +84,8 @@ MODULE_LICENSE("GPL"); #define MT_BUTTONTYPE_CLICKPAD 0 #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 +#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 +#define MS_TYPE_COVER_APPLICATION 0xff050050 enum latency_mode { HID_LATENCY_NORMAL = 0, @@ -408,6 +411,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 | @@ -1368,6 +1372,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; @@ -1395,6 +1402,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); @@ -1416,6 +1438,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) { @@ -1423,6 +1446,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; } @@ -1432,11 +1468,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; } @@ -1589,6 +1635,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); @@ -1638,6 +1720,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; @@ -1726,30 +1815,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); @@ -1758,8 +1823,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; } @@ -1885,13 +1951,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) static int mt_reset_resume(struct hid_device *hdev) { + struct mt_device *td = hid_get_drvdata(hdev); + mt_release_contacts(hdev); mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); + + /* Request an update on the typecover folding state on resume + * after reset. + */ + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) + request_type_cover_tablet_mode_switch(hdev); + return 0; } static int mt_resume(struct hid_device *hdev) { + struct mt_device *td = hid_get_drvdata(hdev); + /* Some Elan legacy devices require SET_IDLE to be set on resume. * It should be safe to send it to other devices too. * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ @@ -1900,6 +1977,10 @@ static int mt_resume(struct hid_device *hdev) mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); + /* Request an update on the typecover folding state on resume. */ + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) + request_type_cover_tablet_mode_switch(hdev); + return 0; } #endif @@ -1907,6 +1988,21 @@ static int mt_resume(struct hid_device *hdev) static void mt_remove(struct hid_device *hdev) { struct mt_device *td = hid_get_drvdata(hdev); + struct hid_field *field; + struct input_dev *input; + + /* Reset tablet mode switch on disconnect. */ + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { + if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], + &field, + MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { + input = field->hidinput->input; + input_report_switch(input, SW_TABLET_MODE, 0); + input_sync(input); + } else { + hid_err(hdev, "couldn't find tablet mode field\n"); + } + } unregister_pm_notifier(&td->pm_notifier); del_timer_sync(&td->release_timer); -- 2.39.0 From dd4e87b9c7f4930a07b93c73b4d85de6282c9910 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 b47e93a24a9a..22c921886cbe 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -2103,6 +2103,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.39.0 From 04d727b390677c247ce35cd3c459a9706c92149b 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 408c321b929a..6e58effbe12b 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -37,6 +37,12 @@ #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) +#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ + ((pdev)->device == 0x9a19 || \ + (pdev)->device == 0x9a39 || \ + (pdev)->device == 0x4e19 || \ + (pdev)->device == 0x465d || \ + (pdev)->device == 0x1919)) #define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ((pdev)->device == 0x9d3e)) #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) @@ -289,12 +295,14 @@ EXPORT_SYMBOL_GPL(intel_iommu_enabled); static int dmar_map_gfx = 1; static int dmar_map_ipts = 1; +static int dmar_map_ipu = 1; static int intel_iommu_superpage = 1; static int iommu_identity_mapping; static int iommu_skip_te_disable; #define IDENTMAP_GFX 2 #define IDENTMAP_AZALIA 4 +#define IDENTMAP_IPU 8 #define IDENTMAP_IPTS 16 const struct iommu_ops intel_iommu_ops; @@ -2635,6 +2643,9 @@ static int device_def_domain_type(struct device *dev) if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) return IOMMU_DOMAIN_IDENTITY; + if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) + return IOMMU_DOMAIN_IDENTITY; + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) return IOMMU_DOMAIN_IDENTITY; } @@ -3026,6 +3037,9 @@ static int __init init_dmars(void) if (!dmar_map_gfx) iommu_identity_mapping |= IDENTMAP_GFX; + if (!dmar_map_ipu) + iommu_identity_mapping |= IDENTMAP_IPU; + if (!dmar_map_ipts) iommu_identity_mapping |= IDENTMAP_IPTS; @@ -4784,6 +4798,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) dmar_map_gfx = 0; } +static void quirk_iommu_ipu(struct pci_dev *dev) +{ + if (!IS_INTEL_IPU(dev)) + return; + + if (risky_device(dev)) + return; + + pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); + dmar_map_ipu = 0; +} + static void quirk_iommu_ipts(struct pci_dev *dev) { if (!IS_IPTS(dev)) @@ -4795,6 +4821,7 @@ static void quirk_iommu_ipts(struct pci_dev *dev) pci_info(dev, "Passthrough IOMMU for IPTS\n"); dmar_map_ipts = 0; } + /* G4x/GM45 integrated gfx dmar support is totally busted. */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); @@ -4830,6 +4857,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); +/* disable IPU dmar support */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); + /* disable IPTS dmar support */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); -- 2.39.0 From aab7172afbd549a758e01f936b9a6683ba59b474 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 5b8d1a9620a5..6a0ff035cf20 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.39.0 From c429d67a19c8860fd3b7f7b0a4f95fa465028fe6 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 28 Oct 2021 21:55:16 +0100 Subject: [PATCH] media: i2c: Add driver for DW9719 VCM Add a driver for the DW9719 VCM. The driver creates a v4l2 subdevice and registers a control to set the desired focus. Signed-off-by: Daniel Scally Patchset: cameras --- MAINTAINERS | 7 + drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/dw9719.c | 425 +++++++++++++++++++++++++++++++++++++ 4 files changed, 444 insertions(+) create mode 100644 drivers/media/i2c/dw9719.c diff --git a/MAINTAINERS b/MAINTAINERS index 886d3f69ee64..659fc8af4a12 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6286,6 +6286,13 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/dongwoon,dw9714.yaml F: drivers/media/i2c/dw9714.c +DONGWOON DW9719 LENS VOICE COIL DRIVER +M: Daniel Scally +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: drivers/media/i2c/dw9719.c + DONGWOON DW9768 LENS VOICE COIL DRIVER M: Dongchun Zhu L: linux-media@vger.kernel.org diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 7806d4b81716..98d081efeef7 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -821,6 +821,17 @@ config VIDEO_DW9714 capability. This is designed for linear control of voice coil motors, controlled via I2C serial interface. +config VIDEO_DW9719 + tristate "DW9719 lens voice coil support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_ASYNC + help + This is a driver for the DW9719 camera lens voice coil. + This is designed for linear control of voice coil motors, + controlled via I2C serial interface. + config VIDEO_DW9768 tristate "DW9768 lens voice coil support" depends on I2C && VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 0a2933103dd9..b82a07c76388 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_VIDEO_CS5345) += cs5345.o obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o obj-$(CONFIG_VIDEO_CX25840) += cx25840/ obj-$(CONFIG_VIDEO_DW9714) += dw9714.o +obj-$(CONFIG_VIDEO_DW9719) += dw9719.o obj-$(CONFIG_VIDEO_DW9768) += dw9768.o obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c new file mode 100644 index 000000000000..180b04d2a6b3 --- /dev/null +++ b/drivers/media/i2c/dw9719.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2012 Intel Corporation + +/* + * Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c in this repo: + * https://github.com/ZenfoneArea/android_kernel_asus_zenfone5 + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DW9719_MAX_FOCUS_POS 1023 +#define DW9719_CTRL_STEPS 16 +#define DW9719_CTRL_DELAY_US 1000 +#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) + +#define DW9719_INFO 0 +#define DW9719_ID 0xF1 +#define DW9719_CONTROL 2 +#define DW9719_VCM_CURRENT 3 + +#define DW9719_MODE 6 +#define DW9719_VCM_FREQ 7 + +#define DW9719_MODE_SAC3 0x40 +#define DW9719_DEFAULT_VCM_FREQ 0x60 +#define DW9719_ENABLE_RINGING 0x02 + +#define NUM_REGULATORS 2 + +#define to_dw9719_device(x) container_of(x, struct dw9719_device, sd) + +struct dw9719_device { + struct device *dev; + struct i2c_client *client; + struct regulator_bulk_data regulators[NUM_REGULATORS]; + struct v4l2_subdev sd; + + struct dw9719_v4l2_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *focus; + } ctrls; +}; + +static int dw9719_i2c_rd8(struct i2c_client *client, u8 reg, u8 *val) +{ + struct i2c_msg msg[2]; + u8 buf[2] = { reg }; + int ret; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = buf; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 1; + msg[1].buf = &buf[1]; + *val = 0; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) + return ret; + + *val = buf[1]; + + return 0; +} + +static int dw9719_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) +{ + struct i2c_msg msg; + int ret; + + u8 buf[2] = { reg, val }; + + msg.addr = client->addr; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + ret = i2c_transfer(client->adapter, &msg, 1); + + return ret < 0 ? ret : 0; +} + +static int dw9719_i2c_wr16(struct i2c_client *client, u8 reg, u16 val) +{ + struct i2c_msg msg; + u8 buf[3] = { reg }; + int ret; + + put_unaligned_be16(val, buf + 1); + + msg.addr = client->addr; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + ret = i2c_transfer(client->adapter, &msg, 1); + + return ret < 0 ? ret : 0; +} + +static int dw9719_detect(struct dw9719_device *dw9719) +{ + int ret; + u8 val; + + ret = dw9719_i2c_rd8(dw9719->client, DW9719_INFO, &val); + if (ret < 0) + return ret; + + if (val != DW9719_ID) { + dev_err(dw9719->dev, "Failed to detect correct id\n"); + ret = -ENXIO; + } + + return 0; +} + +static int dw9719_power_down(struct dw9719_device *dw9719) +{ + return regulator_bulk_disable(NUM_REGULATORS, dw9719->regulators); +} + +static int dw9719_power_up(struct dw9719_device *dw9719) +{ + int ret; + + ret = regulator_bulk_enable(NUM_REGULATORS, dw9719->regulators); + if (ret) + return ret; + + /* Jiggle SCL pin to wake up device */ + ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, 1); + + /* Need 100us to transit from SHUTDOWN to STANDBY*/ + usleep_range(100, 1000); + + ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, + DW9719_ENABLE_RINGING); + if (ret < 0) + goto fail_powerdown; + + ret = dw9719_i2c_wr8(dw9719->client, DW9719_MODE, DW9719_MODE_SAC3); + if (ret < 0) + goto fail_powerdown; + + ret = dw9719_i2c_wr8(dw9719->client, DW9719_VCM_FREQ, + DW9719_DEFAULT_VCM_FREQ); + if (ret < 0) + goto fail_powerdown; + + return 0; + +fail_powerdown: + dw9719_power_down(dw9719); + return ret; +} + +static int dw9719_t_focus_abs(struct dw9719_device *dw9719, s32 value) +{ + int ret; + + value = clamp(value, 0, DW9719_MAX_FOCUS_POS); + ret = dw9719_i2c_wr16(dw9719->client, DW9719_VCM_CURRENT, value); + if (ret < 0) + return ret; + + return 0; +} + +static int dw9719_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw9719_device *dw9719 = container_of(ctrl->handler, + struct dw9719_device, + ctrls.handler); + int ret; + + /* Only apply changes to the controls if the device is powered up */ + if (!pm_runtime_get_if_in_use(dw9719->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_FOCUS_ABSOLUTE: + ret = dw9719_t_focus_abs(dw9719, ctrl->val); + break; + default: + ret = -EINVAL; + } + + pm_runtime_put(dw9719->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops dw9719_ctrl_ops = { + .s_ctrl = dw9719_set_ctrl, +}; + +static int __maybe_unused dw9719_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct dw9719_device *dw9719 = to_dw9719_device(sd); + int ret; + int val; + + for (val = dw9719->ctrls.focus->val; val >= 0; + val -= DW9719_CTRL_STEPS) { + ret = dw9719_t_focus_abs(dw9719, val); + if (ret) + return ret; + + usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); + } + + return dw9719_power_down(dw9719); +} + +static int __maybe_unused dw9719_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct dw9719_device *dw9719 = to_dw9719_device(sd); + int current_focus = dw9719->ctrls.focus->val; + int ret; + int val; + + ret = dw9719_power_up(dw9719); + if (ret) + return ret; + + for (val = current_focus % DW9719_CTRL_STEPS; val < current_focus; + val += DW9719_CTRL_STEPS) { + ret = dw9719_t_focus_abs(dw9719, val); + if (ret) + goto err_power_down; + + usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); + } + + return 0; + +err_power_down: + dw9719_power_down(dw9719); + return ret; +} + +static int dw9719_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return pm_runtime_resume_and_get(sd->dev); +} + +static int dw9719_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops dw9719_internal_ops = { + .open = dw9719_open, + .close = dw9719_close, +}; + +static int dw9719_init_controls(struct dw9719_device *dw9719) +{ + const struct v4l2_ctrl_ops *ops = &dw9719_ctrl_ops; + int ret; + + ret = v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1); + if (ret) + return ret; + + dw9719->ctrls.focus = v4l2_ctrl_new_std(&dw9719->ctrls.handler, ops, + V4L2_CID_FOCUS_ABSOLUTE, 0, + DW9719_MAX_FOCUS_POS, 1, 0); + + if (dw9719->ctrls.handler.error) { + dev_err(dw9719->dev, "Error initialising v4l2 ctrls\n"); + ret = dw9719->ctrls.handler.error; + goto err_free_handler; + } + + dw9719->sd.ctrl_handler = &dw9719->ctrls.handler; + + return ret; + +err_free_handler: + v4l2_ctrl_handler_free(&dw9719->ctrls.handler); + return ret; +} + +static const struct v4l2_subdev_ops dw9719_ops = { }; + +static int dw9719_probe(struct i2c_client *client) +{ + struct dw9719_device *dw9719; + int ret; + + dw9719 = devm_kzalloc(&client->dev, sizeof(*dw9719), GFP_KERNEL); + if (!dw9719) + return -ENOMEM; + + dw9719->client = client; + dw9719->dev = &client->dev; + + dw9719->regulators[0].supply = "vdd"; + /* + * The DW9719 has only the 1 VDD voltage input, but some PMICs such as + * the TPS68470 PMIC have I2C passthrough capability, to disconnect the + * sensor's I2C pins from the I2C bus when the sensors VSIO (Sensor-IO) + * is off, because some sensors then short these pins to ground; + * and the DW9719 might sit behind this passthrough, this it needs to + * enable VSIO as that will also enable the I2C passthrough. + */ + dw9719->regulators[1].supply = "vsio"; + + ret = devm_regulator_bulk_get(&client->dev, NUM_REGULATORS, + dw9719->regulators); + if (ret) + return dev_err_probe(&client->dev, ret, "getting regulators\n"); + + v4l2_i2c_subdev_init(&dw9719->sd, client, &dw9719_ops); + dw9719->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + dw9719->sd.internal_ops = &dw9719_internal_ops; + + ret = dw9719_init_controls(dw9719); + if (ret) + return ret; + + ret = media_entity_pads_init(&dw9719->sd.entity, 0, NULL); + if (ret < 0) + goto err_free_ctrl_handler; + + dw9719->sd.entity.function = MEDIA_ENT_F_LENS; + + /* + * We need the driver to work in the event that pm runtime is disable in + * the kernel, so power up and verify the chip now. In the event that + * runtime pm is disabled this will leave the chip on, so that the lens + * will work. + */ + + ret = dw9719_power_up(dw9719); + if (ret) + goto err_cleanup_media; + + ret = dw9719_detect(dw9719); + if (ret) + goto err_powerdown; + + pm_runtime_set_active(&client->dev); + pm_runtime_get_noresume(&client->dev); + pm_runtime_enable(&client->dev); + + ret = v4l2_async_register_subdev(&dw9719->sd); + if (ret < 0) + goto err_pm_runtime; + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return ret; + +err_pm_runtime: + pm_runtime_disable(&client->dev); + pm_runtime_put_noidle(&client->dev); +err_powerdown: + dw9719_power_down(dw9719); +err_cleanup_media: + media_entity_cleanup(&dw9719->sd.entity); +err_free_ctrl_handler: + v4l2_ctrl_handler_free(&dw9719->ctrls.handler); + + return ret; +} + +static void dw9719_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9719_device *dw9719 = container_of(sd, struct dw9719_device, + sd); + + pm_runtime_disable(&client->dev); + v4l2_async_unregister_subdev(sd); + v4l2_ctrl_handler_free(&dw9719->ctrls.handler); + media_entity_cleanup(&dw9719->sd.entity); +} + +static const struct i2c_device_id dw9719_id_table[] = { + { "dw9719" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, dw9719_id_table); + +static const struct dev_pm_ops dw9719_pm_ops = { + SET_RUNTIME_PM_OPS(dw9719_suspend, dw9719_resume, NULL) +}; + +static struct i2c_driver dw9719_i2c_driver = { + .driver = { + .name = "dw9719", + .pm = &dw9719_pm_ops, + }, + .probe_new = dw9719_probe, + .remove = dw9719_remove, + .id_table = dw9719_id_table, +}; +module_i2c_driver(dw9719_i2c_driver); + +MODULE_AUTHOR("Daniel Scally "); +MODULE_DESCRIPTION("DW9719 VCM Driver"); +MODULE_LICENSE("GPL"); -- 2.39.0 From dec3b71065ccf948d1d4830e46adad41ac134195 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 4 May 2022 23:21:45 +0100 Subject: [PATCH] media: ipu3-cio2: Move functionality from .complete() to .bound() Creating links and registering subdev nodes during the .complete() callback has the unfortunate effect of preventing all cameras that connect to a notifier from working if any one of their drivers fails to probe. Moving the functionality from .complete() to .bound() allows those camera sensor drivers that did probe correctly to work regardless. Signed-off-by: Daniel Scally Patchset: cameras --- drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 65 +++++++------------ 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c index 390bd5ea3472..76339fb842bf 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c @@ -1383,7 +1383,10 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, { struct cio2_device *cio2 = to_cio2_device(notifier); struct sensor_async_subdev *s_asd = to_sensor_asd(asd); + struct device *dev = &cio2->pci_dev->dev; struct cio2_queue *q; + unsigned int pad; + int ret; if (cio2->queue[s_asd->csi2.port].sensor) return -EBUSY; @@ -1394,7 +1397,26 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, q->sensor = sd; q->csi_rx_base = cio2->base + CIO2_REG_PIPE_BASE(q->csi2.port); - return 0; + for (pad = 0; pad < q->sensor->entity.num_pads; pad++) + if (q->sensor->entity.pads[pad].flags & + MEDIA_PAD_FL_SOURCE) + break; + + if (pad == q->sensor->entity.num_pads) { + dev_err(dev, "failed to find src pad for %s\n", + q->sensor->name); + return -ENXIO; + } + + ret = media_create_pad_link(&q->sensor->entity, pad, &q->subdev.entity, + CIO2_PAD_SINK, 0); + if (ret) { + dev_err(dev, "failed to create link for %s\n", + q->sensor->name); + return ret; + } + + return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); } /* The .unbind callback */ @@ -1408,50 +1430,9 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, cio2->queue[s_asd->csi2.port].sensor = NULL; } -/* .complete() is called after all subdevices have been located */ -static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) -{ - struct cio2_device *cio2 = to_cio2_device(notifier); - struct device *dev = &cio2->pci_dev->dev; - struct sensor_async_subdev *s_asd; - struct v4l2_async_subdev *asd; - struct cio2_queue *q; - unsigned int pad; - int ret; - - list_for_each_entry(asd, &cio2->notifier.asd_list, asd_list) { - s_asd = to_sensor_asd(asd); - q = &cio2->queue[s_asd->csi2.port]; - - for (pad = 0; pad < q->sensor->entity.num_pads; pad++) - if (q->sensor->entity.pads[pad].flags & - MEDIA_PAD_FL_SOURCE) - break; - - if (pad == q->sensor->entity.num_pads) { - dev_err(dev, "failed to find src pad for %s\n", - q->sensor->name); - return -ENXIO; - } - - ret = media_create_pad_link( - &q->sensor->entity, pad, - &q->subdev.entity, CIO2_PAD_SINK, - 0); - if (ret) { - dev_err(dev, "failed to create link for %s\n", - q->sensor->name); - return ret; - } - } - - return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); -} - static const struct v4l2_async_notifier_operations cio2_async_ops = { .bound = cio2_notifier_bound, .unbind = cio2_notifier_unbind, - .complete = cio2_notifier_complete, }; static int cio2_parse_firmware(struct cio2_device *cio2) -- 2.39.0 From 1020ddb552417b2af335b0ad73efafad56cdd039 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 2 Jun 2022 22:15:56 +0100 Subject: [PATCH] media: ipu3-cio2: Re-add .complete() to ipu3-cio2 Removing the .complete() callback had some unintended consequences. Because the VCM driver is not directly linked to the ipu3-cio2 driver .bound() never gets called for it, which means its devnode is never created if it probes late. Because .complete() waits for any sub-notifiers to also be complete it is captured in that call. Signed-off-by: Daniel Scally Patchset: cameras --- drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c index 76339fb842bf..eb7aa269ec2b 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c @@ -1430,9 +1430,18 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, cio2->queue[s_asd->csi2.port].sensor = NULL; } +/* .complete() is called after all subdevices have been located */ +static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct cio2_device *cio2 = to_cio2_device(notifier); + + return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); +} + static const struct v4l2_async_notifier_operations cio2_async_ops = { .bound = cio2_notifier_bound, .unbind = cio2_notifier_unbind, + .complete = cio2_notifier_complete, }; static int cio2_parse_firmware(struct cio2_device *cio2) -- 2.39.0 From f27e3ae91e4d86ceb751029090de793847022949 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 15 Jul 2022 23:48:00 +0200 Subject: [PATCH] drivers/media/i2c: Fix DW9719 dependencies It should depend on VIDEO_DEV instead of VIDEO_V4L2. Signed-off-by: Maximilian Luz Patchset: cameras --- drivers/media/i2c/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 98d081efeef7..c67966cafe10 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -823,7 +823,7 @@ config VIDEO_DW9714 config VIDEO_DW9719 tristate "DW9719 lens voice coil support" - depends on I2C && VIDEO_V4L2 + depends on I2C && VIDEO_DEV select MEDIA_CONTROLLER select VIDEO_V4L2_SUBDEV_API select V4L2_ASYNC -- 2.39.0 From aaff0863024578f7f6fe91573c68778ad0aec98e Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sat, 29 May 2021 17:47:38 +1000 Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 override This patch is the work of Thomas Gleixner and is copied from: https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin setup that is missing in the laptops ACPI table. This patch was used for validation of the issue, and is not a proper fix, but is probably a better temporary hack than continuing to probe the Legacy PIC and run with the PIC in an unknown state. Patchset: amd-gpio --- arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index 907cc98b1938..0116d27b29ea 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -1234,6 +1235,17 @@ static void __init mp_config_acpi_legacy_irqs(void) } } +static const struct dmi_system_id surface_quirk[] __initconst = { + { + .ident = "Microsoft Surface Laptop 4 (AMD)", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, + {} +}; + /* * Parse IOAPIC related entries in MADT * returns 0 on success, < 0 on error @@ -1289,6 +1301,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, acpi_gbl_FADT.sci_interrupt); + if (dmi_check_system(surface_quirk)) { + pr_warn("Surface hack: Override irq 7\n"); + mp_override_legacy_irq(7, 3, 3, 7); + } + /* Fill in identity legacy mappings where no override */ mp_config_acpi_legacy_irqs(); -- 2.39.0 From 9d4e2bb0f9856ce57029c89895b38ae5949502f5 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Thu, 3 Jun 2021 14:04:26 +0200 Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override quirk The 13" version of the Surface Laptop 4 has the same problem as the 15" version, but uses a different SKU. Add that SKU to the quirk as well. Patchset: amd-gpio --- arch/x86/kernel/acpi/boot.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index 0116d27b29ea..af102c6f8e5b 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -1237,12 +1237,19 @@ static void __init mp_config_acpi_legacy_irqs(void) static const struct dmi_system_id surface_quirk[] __initconst = { { - .ident = "Microsoft Surface Laptop 4 (AMD)", + .ident = "Microsoft Surface Laptop 4 (AMD 15\")", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") }, }, + { + .ident = "Microsoft Surface Laptop 4 (AMD 13\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") + }, + }, {} }; -- 2.39.0 From dcf0efd363e301c409bfccb2ea3119e5d3fa3e52 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 | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index e9b8e8305e23..944276934e7e 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -432,6 +432,14 @@ static ssize_t caps_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RO(caps); +static struct attribute *acpi_tad_attrs[] = { + &dev_attr_caps.attr, + NULL, +}; +static const struct attribute_group acpi_tad_attr_group = { + .attrs = acpi_tad_attrs, +}; + static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -480,15 +488,14 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(ac_status); -static struct attribute *acpi_tad_attrs[] = { - &dev_attr_caps.attr, +static struct attribute *acpi_tad_ac_attrs[] = { &dev_attr_ac_alarm.attr, &dev_attr_ac_policy.attr, &dev_attr_ac_status.attr, NULL, }; -static const struct attribute_group acpi_tad_attr_group = { - .attrs = acpi_tad_attrs, +static const struct attribute_group acpi_tad_ac_attr_group = { + .attrs = acpi_tad_ac_attrs, }; static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, @@ -563,13 +570,18 @@ static int acpi_tad_remove(struct platform_device *pdev) pm_runtime_get_sync(dev); + if (dd->capabilities & ACPI_TAD_AC_WAKE) + sysfs_remove_group(&dev->kobj, &acpi_tad_ac_attr_group); + if (dd->capabilities & ACPI_TAD_DC_WAKE) sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); - acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); - acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); + if (dd->capabilities & ACPI_TAD_AC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); + } if (dd->capabilities & ACPI_TAD_DC_WAKE) { acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); @@ -604,11 +616,6 @@ static int acpi_tad_probe(struct platform_device *pdev) return -ENODEV; } - if (!acpi_has_method(handle, "_PRW")) { - dev_info(dev, "Missing _PRW\n"); - return -ENODEV; - } - dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); if (!dd) return -ENOMEM; @@ -637,6 +644,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.39.0