summaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
blob: dd6d21f1dbfd77ef385f1251f3317ce7dba7ea4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// 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;
}