xref: /linux/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c (revision e700ac213a0f793fb4f83098413303e3dd080892)
1 /*
2  * NXP Wireless LAN device driver: PCIE and platform specific quirks
3  *
4  * This software file (the "File") is distributed by NXP
5  * under the terms of the GNU General Public License Version 2, June 1991
6  * (the "License").  You may use, redistribute and/or modify this File in
7  * accordance with the terms and conditions of the License, a copy of which
8  * is available by writing to the Free Software Foundation, Inc.,
9  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the
10  * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
11  *
12  * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
13  * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
14  * ARE EXPRESSLY DISCLAIMED.  The License provides additional details about
15  * this warranty disclaimer.
16  */
17 
18 #include <linux/dmi.h>
19 
20 #include "pcie_quirks.h"
21 
22 /* quirk table based on DMI matching */
23 static const struct dmi_system_id mwifiex_quirk_table[] = {
24 	{
25 		.ident = "Surface Pro 4",
26 		.matches = {
27 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
28 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),
29 		},
30 		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
31 	},
32 	{
33 		.ident = "Surface Pro 5",
34 		.matches = {
35 			/* match for SKU here due to generic product name "Surface Pro" */
36 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
37 			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),
38 		},
39 		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
40 	},
41 	{
42 		.ident = "Surface Pro 5 (LTE)",
43 		.matches = {
44 			/* match for SKU here due to generic product name "Surface Pro" */
45 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
46 			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),
47 		},
48 		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
49 	},
50 	{
51 		.ident = "Surface Pro 6",
52 		.matches = {
53 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
54 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
55 		},
56 		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
57 	},
58 	{
59 		.ident = "Surface Book 1",
60 		.matches = {
61 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
62 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
63 		},
64 		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
65 	},
66 	{
67 		.ident = "Surface Book 2",
68 		.matches = {
69 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
70 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
71 		},
72 		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
73 	},
74 	{
75 		.ident = "Surface Laptop 1",
76 		.matches = {
77 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
78 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
79 		},
80 		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
81 	},
82 	{
83 		.ident = "Surface Laptop 2",
84 		.matches = {
85 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
86 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
87 		},
88 		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
89 	},
90 	{}
91 };
92 
93 void mwifiex_initialize_quirks(struct pcie_service_card *card)
94 {
95 	struct pci_dev *pdev = card->dev;
96 	const struct dmi_system_id *dmi_id;
97 
98 	dmi_id = dmi_first_match(mwifiex_quirk_table);
99 	if (dmi_id)
100 		card->quirks = (uintptr_t)dmi_id->driver_data;
101 
102 	if (!card->quirks)
103 		dev_info(&pdev->dev, "no quirks enabled\n");
104 	if (card->quirks & QUIRK_FW_RST_D3COLD)
105 		dev_info(&pdev->dev, "quirk reset_d3cold enabled\n");
106 }
107 
108 static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev)
109 {
110 	dev_info(&pdev->dev, "putting into D3cold...\n");
111 
112 	pci_save_state(pdev);
113 	if (pci_is_enabled(pdev))
114 		pci_disable_device(pdev);
115 	pci_set_power_state(pdev, PCI_D3cold);
116 }
117 
118 static int mwifiex_pcie_set_power_d0(struct pci_dev *pdev)
119 {
120 	int ret;
121 
122 	dev_info(&pdev->dev, "putting into D0...\n");
123 
124 	pci_set_power_state(pdev, PCI_D0);
125 	ret = pci_enable_device(pdev);
126 	if (ret) {
127 		dev_err(&pdev->dev, "pci_enable_device failed\n");
128 		return ret;
129 	}
130 	pci_restore_state(pdev);
131 
132 	return 0;
133 }
134 
135 int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev)
136 {
137 	struct pci_dev *parent_pdev = pci_upstream_bridge(pdev);
138 	int ret;
139 
140 	/* Power-cycle (put into D3cold then D0) */
141 	dev_info(&pdev->dev, "Using reset_d3cold quirk to perform FW reset\n");
142 
143 	/* We need to perform power-cycle also for bridge of wifi because
144 	 * on some devices (e.g. Surface Book 1), the OS for some reasons
145 	 * can't know the real power state of the bridge.
146 	 * When tried to power-cycle only wifi, the reset failed with the
147 	 * following dmesg log:
148 	 * "Cannot transition to power state D0 for parent in D3hot".
149 	 */
150 	mwifiex_pcie_set_power_d3cold(pdev);
151 	mwifiex_pcie_set_power_d3cold(parent_pdev);
152 
153 	ret = mwifiex_pcie_set_power_d0(parent_pdev);
154 	if (ret)
155 		return ret;
156 	ret = mwifiex_pcie_set_power_d0(pdev);
157 	if (ret)
158 		return ret;
159 
160 	return 0;
161 }
162