| // SPDX-License-Identifier: GPL-2.0-only |
| // NXP Wireless LAN device driver: PCIE and platform specific quirks |
| |
| #include <linux/dmi.h> |
| |
| #include "pcie_quirks.h" |
| |
| /* quirk table based on DMI matching */ |
| static const struct dmi_system_id mwifiex_quirk_table[] = { |
| { |
| .ident = "Surface Pro 4", |
| .matches = { |
| DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), |
| DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), |
| }, |
| .driver_data = (void *)QUIRK_FW_RST_D3COLD, |
| }, |
| { |
| .ident = "Surface Pro 5", |
| .matches = { |
| /* match for SKU here due to generic product name "Surface Pro" */ |
| DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), |
| DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), |
| }, |
| .driver_data = (void *)QUIRK_FW_RST_D3COLD, |
| }, |
| { |
| .ident = "Surface Pro 5 (LTE)", |
| .matches = { |
| /* match for SKU here due to generic product name "Surface Pro" */ |
| DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), |
| DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), |
| }, |
| .driver_data = (void *)QUIRK_FW_RST_D3COLD, |
| }, |
| { |
| .ident = "Surface Pro 6", |
| .matches = { |
| DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), |
| DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), |
| }, |
| .driver_data = (void *)QUIRK_FW_RST_D3COLD, |
| }, |
| { |
| .ident = "Surface Book 1", |
| .matches = { |
| DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), |
| DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), |
| }, |
| .driver_data = (void *)QUIRK_FW_RST_D3COLD, |
| }, |
| { |
| .ident = "Surface Book 2", |
| .matches = { |
| DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), |
| DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), |
| }, |
| .driver_data = (void *)QUIRK_FW_RST_D3COLD, |
| }, |
| { |
| .ident = "Surface Laptop 1", |
| .matches = { |
| DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), |
| DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), |
| }, |
| .driver_data = (void *)QUIRK_FW_RST_D3COLD, |
| }, |
| { |
| .ident = "Surface Laptop 2", |
| .matches = { |
| DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), |
| DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), |
| }, |
| .driver_data = (void *)QUIRK_FW_RST_D3COLD, |
| }, |
| {} |
| }; |
| |
| void mwifiex_initialize_quirks(struct pcie_service_card *card) |
| { |
| struct pci_dev *pdev = card->dev; |
| const struct dmi_system_id *dmi_id; |
| |
| dmi_id = dmi_first_match(mwifiex_quirk_table); |
| if (dmi_id) |
| card->quirks = (uintptr_t)dmi_id->driver_data; |
| |
| if (!card->quirks) |
| dev_info(&pdev->dev, "no quirks enabled\n"); |
| if (card->quirks & QUIRK_FW_RST_D3COLD) |
| dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); |
| } |
| |
| static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) |
| { |
| dev_info(&pdev->dev, "putting into D3cold...\n"); |
| |
| pci_save_state(pdev); |
| if (pci_is_enabled(pdev)) |
| pci_disable_device(pdev); |
| pci_set_power_state(pdev, PCI_D3cold); |
| } |
| |
| static int mwifiex_pcie_set_power_d0(struct pci_dev *pdev) |
| { |
| int ret; |
| |
| dev_info(&pdev->dev, "putting into D0...\n"); |
| |
| pci_set_power_state(pdev, PCI_D0); |
| ret = pci_enable_device(pdev); |
| if (ret) { |
| dev_err(&pdev->dev, "pci_enable_device failed\n"); |
| return ret; |
| } |
| pci_restore_state(pdev); |
| |
| return 0; |
| } |
| |
| int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) |
| { |
| struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); |
| int ret; |
| |
| /* Power-cycle (put into D3cold then D0) */ |
| dev_info(&pdev->dev, "Using reset_d3cold quirk to perform FW reset\n"); |
| |
| /* We need to perform power-cycle also for bridge of wifi because |
| * on some devices (e.g. Surface Book 1), the OS for some reasons |
| * can't know the real power state of the bridge. |
| * When tried to power-cycle only wifi, the reset failed with the |
| * following dmesg log: |
| * "Cannot transition to power state D0 for parent in D3hot". |
| */ |
| mwifiex_pcie_set_power_d3cold(pdev); |
| mwifiex_pcie_set_power_d3cold(parent_pdev); |
| |
| ret = mwifiex_pcie_set_power_d0(parent_pdev); |
| if (ret) |
| return ret; |
| ret = mwifiex_pcie_set_power_d0(pdev); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |