xref: /linux/drivers/pci/tph.c (revision f69767a1ada3ac74be2e1ac0795a05e1d1384eff)
1*f69767a1SWei Huang // SPDX-License-Identifier: GPL-2.0
2*f69767a1SWei Huang /*
3*f69767a1SWei Huang  * TPH (TLP Processing Hints) support
4*f69767a1SWei Huang  *
5*f69767a1SWei Huang  * Copyright (C) 2024 Advanced Micro Devices, Inc.
6*f69767a1SWei Huang  *     Eric Van Tassell <Eric.VanTassell@amd.com>
7*f69767a1SWei Huang  *     Wei Huang <wei.huang2@amd.com>
8*f69767a1SWei Huang  */
9*f69767a1SWei Huang #include <linux/pci.h>
10*f69767a1SWei Huang #include <linux/bitfield.h>
11*f69767a1SWei Huang #include <linux/pci-tph.h>
12*f69767a1SWei Huang 
13*f69767a1SWei Huang #include "pci.h"
14*f69767a1SWei Huang 
15*f69767a1SWei Huang /* System-wide TPH disabled */
16*f69767a1SWei Huang static bool pci_tph_disabled;
17*f69767a1SWei Huang 
18*f69767a1SWei Huang static u8 get_st_modes(struct pci_dev *pdev)
19*f69767a1SWei Huang {
20*f69767a1SWei Huang 	u32 reg;
21*f69767a1SWei Huang 
22*f69767a1SWei Huang 	pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CAP, &reg);
23*f69767a1SWei Huang 	reg &= PCI_TPH_CAP_ST_NS | PCI_TPH_CAP_ST_IV | PCI_TPH_CAP_ST_DS;
24*f69767a1SWei Huang 
25*f69767a1SWei Huang 	return reg;
26*f69767a1SWei Huang }
27*f69767a1SWei Huang 
28*f69767a1SWei Huang /* Return device's Root Port completer capability */
29*f69767a1SWei Huang static u8 get_rp_completer_type(struct pci_dev *pdev)
30*f69767a1SWei Huang {
31*f69767a1SWei Huang 	struct pci_dev *rp;
32*f69767a1SWei Huang 	u32 reg;
33*f69767a1SWei Huang 	int ret;
34*f69767a1SWei Huang 
35*f69767a1SWei Huang 	rp = pcie_find_root_port(pdev);
36*f69767a1SWei Huang 	if (!rp)
37*f69767a1SWei Huang 		return 0;
38*f69767a1SWei Huang 
39*f69767a1SWei Huang 	ret = pcie_capability_read_dword(rp, PCI_EXP_DEVCAP2, &reg);
40*f69767a1SWei Huang 	if (ret)
41*f69767a1SWei Huang 		return 0;
42*f69767a1SWei Huang 
43*f69767a1SWei Huang 	return FIELD_GET(PCI_EXP_DEVCAP2_TPH_COMP_MASK, reg);
44*f69767a1SWei Huang }
45*f69767a1SWei Huang 
46*f69767a1SWei Huang /**
47*f69767a1SWei Huang  * pcie_disable_tph - Turn off TPH support for device
48*f69767a1SWei Huang  * @pdev: PCI device
49*f69767a1SWei Huang  *
50*f69767a1SWei Huang  * Return: none
51*f69767a1SWei Huang  */
52*f69767a1SWei Huang void pcie_disable_tph(struct pci_dev *pdev)
53*f69767a1SWei Huang {
54*f69767a1SWei Huang 	if (!pdev->tph_cap)
55*f69767a1SWei Huang 		return;
56*f69767a1SWei Huang 
57*f69767a1SWei Huang 	if (!pdev->tph_enabled)
58*f69767a1SWei Huang 		return;
59*f69767a1SWei Huang 
60*f69767a1SWei Huang 	pci_write_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, 0);
61*f69767a1SWei Huang 
62*f69767a1SWei Huang 	pdev->tph_mode = 0;
63*f69767a1SWei Huang 	pdev->tph_req_type = 0;
64*f69767a1SWei Huang 	pdev->tph_enabled = 0;
65*f69767a1SWei Huang }
66*f69767a1SWei Huang EXPORT_SYMBOL(pcie_disable_tph);
67*f69767a1SWei Huang 
68*f69767a1SWei Huang /**
69*f69767a1SWei Huang  * pcie_enable_tph - Enable TPH support for device using a specific ST mode
70*f69767a1SWei Huang  * @pdev: PCI device
71*f69767a1SWei Huang  * @mode: ST mode to enable. Current supported modes include:
72*f69767a1SWei Huang  *
73*f69767a1SWei Huang  *   - PCI_TPH_ST_NS_MODE: NO ST Mode
74*f69767a1SWei Huang  *   - PCI_TPH_ST_IV_MODE: Interrupt Vector Mode
75*f69767a1SWei Huang  *   - PCI_TPH_ST_DS_MODE: Device Specific Mode
76*f69767a1SWei Huang  *
77*f69767a1SWei Huang  * Check whether the mode is actually supported by the device before enabling
78*f69767a1SWei Huang  * and return an error if not. Additionally determine what types of requests,
79*f69767a1SWei Huang  * TPH or extended TPH, can be issued by the device based on its TPH requester
80*f69767a1SWei Huang  * capability and the Root Port's completer capability.
81*f69767a1SWei Huang  *
82*f69767a1SWei Huang  * Return: 0 on success, otherwise negative value (-errno)
83*f69767a1SWei Huang  */
84*f69767a1SWei Huang int pcie_enable_tph(struct pci_dev *pdev, int mode)
85*f69767a1SWei Huang {
86*f69767a1SWei Huang 	u32 reg;
87*f69767a1SWei Huang 	u8 dev_modes;
88*f69767a1SWei Huang 	u8 rp_req_type;
89*f69767a1SWei Huang 
90*f69767a1SWei Huang 	/* Honor "notph" kernel parameter */
91*f69767a1SWei Huang 	if (pci_tph_disabled)
92*f69767a1SWei Huang 		return -EINVAL;
93*f69767a1SWei Huang 
94*f69767a1SWei Huang 	if (!pdev->tph_cap)
95*f69767a1SWei Huang 		return -EINVAL;
96*f69767a1SWei Huang 
97*f69767a1SWei Huang 	if (pdev->tph_enabled)
98*f69767a1SWei Huang 		return -EBUSY;
99*f69767a1SWei Huang 
100*f69767a1SWei Huang 	/* Sanitize and check ST mode compatibility */
101*f69767a1SWei Huang 	mode &= PCI_TPH_CTRL_MODE_SEL_MASK;
102*f69767a1SWei Huang 	dev_modes = get_st_modes(pdev);
103*f69767a1SWei Huang 	if (!((1 << mode) & dev_modes))
104*f69767a1SWei Huang 		return -EINVAL;
105*f69767a1SWei Huang 
106*f69767a1SWei Huang 	pdev->tph_mode = mode;
107*f69767a1SWei Huang 
108*f69767a1SWei Huang 	/* Get req_type supported by device and its Root Port */
109*f69767a1SWei Huang 	pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CAP, &reg);
110*f69767a1SWei Huang 	if (FIELD_GET(PCI_TPH_CAP_EXT_TPH, reg))
111*f69767a1SWei Huang 		pdev->tph_req_type = PCI_TPH_REQ_EXT_TPH;
112*f69767a1SWei Huang 	else
113*f69767a1SWei Huang 		pdev->tph_req_type = PCI_TPH_REQ_TPH_ONLY;
114*f69767a1SWei Huang 
115*f69767a1SWei Huang 	rp_req_type = get_rp_completer_type(pdev);
116*f69767a1SWei Huang 
117*f69767a1SWei Huang 	/* Final req_type is the smallest value of two */
118*f69767a1SWei Huang 	pdev->tph_req_type = min(pdev->tph_req_type, rp_req_type);
119*f69767a1SWei Huang 
120*f69767a1SWei Huang 	if (pdev->tph_req_type == PCI_TPH_REQ_DISABLE)
121*f69767a1SWei Huang 		return -EINVAL;
122*f69767a1SWei Huang 
123*f69767a1SWei Huang 	/* Write them into TPH control register */
124*f69767a1SWei Huang 	pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, &reg);
125*f69767a1SWei Huang 
126*f69767a1SWei Huang 	reg &= ~PCI_TPH_CTRL_MODE_SEL_MASK;
127*f69767a1SWei Huang 	reg |= FIELD_PREP(PCI_TPH_CTRL_MODE_SEL_MASK, pdev->tph_mode);
128*f69767a1SWei Huang 
129*f69767a1SWei Huang 	reg &= ~PCI_TPH_CTRL_REQ_EN_MASK;
130*f69767a1SWei Huang 	reg |= FIELD_PREP(PCI_TPH_CTRL_REQ_EN_MASK, pdev->tph_req_type);
131*f69767a1SWei Huang 
132*f69767a1SWei Huang 	pci_write_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, reg);
133*f69767a1SWei Huang 
134*f69767a1SWei Huang 	pdev->tph_enabled = 1;
135*f69767a1SWei Huang 
136*f69767a1SWei Huang 	return 0;
137*f69767a1SWei Huang }
138*f69767a1SWei Huang EXPORT_SYMBOL(pcie_enable_tph);
139*f69767a1SWei Huang 
140*f69767a1SWei Huang void pci_restore_tph_state(struct pci_dev *pdev)
141*f69767a1SWei Huang {
142*f69767a1SWei Huang 	struct pci_cap_saved_state *save_state;
143*f69767a1SWei Huang 	u32 *cap;
144*f69767a1SWei Huang 
145*f69767a1SWei Huang 	if (!pdev->tph_cap)
146*f69767a1SWei Huang 		return;
147*f69767a1SWei Huang 
148*f69767a1SWei Huang 	if (!pdev->tph_enabled)
149*f69767a1SWei Huang 		return;
150*f69767a1SWei Huang 
151*f69767a1SWei Huang 	save_state = pci_find_saved_ext_cap(pdev, PCI_EXT_CAP_ID_TPH);
152*f69767a1SWei Huang 	if (!save_state)
153*f69767a1SWei Huang 		return;
154*f69767a1SWei Huang 
155*f69767a1SWei Huang 	/* Restore control register and all ST entries */
156*f69767a1SWei Huang 	cap = &save_state->cap.data[0];
157*f69767a1SWei Huang 	pci_write_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, *cap++);
158*f69767a1SWei Huang }
159*f69767a1SWei Huang 
160*f69767a1SWei Huang void pci_save_tph_state(struct pci_dev *pdev)
161*f69767a1SWei Huang {
162*f69767a1SWei Huang 	struct pci_cap_saved_state *save_state;
163*f69767a1SWei Huang 	u32 *cap;
164*f69767a1SWei Huang 
165*f69767a1SWei Huang 	if (!pdev->tph_cap)
166*f69767a1SWei Huang 		return;
167*f69767a1SWei Huang 
168*f69767a1SWei Huang 	if (!pdev->tph_enabled)
169*f69767a1SWei Huang 		return;
170*f69767a1SWei Huang 
171*f69767a1SWei Huang 	save_state = pci_find_saved_ext_cap(pdev, PCI_EXT_CAP_ID_TPH);
172*f69767a1SWei Huang 	if (!save_state)
173*f69767a1SWei Huang 		return;
174*f69767a1SWei Huang 
175*f69767a1SWei Huang 	/* Save control register */
176*f69767a1SWei Huang 	cap = &save_state->cap.data[0];
177*f69767a1SWei Huang 	pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, cap++);
178*f69767a1SWei Huang }
179*f69767a1SWei Huang 
180*f69767a1SWei Huang void pci_no_tph(void)
181*f69767a1SWei Huang {
182*f69767a1SWei Huang 	pci_tph_disabled = true;
183*f69767a1SWei Huang 
184*f69767a1SWei Huang 	pr_info("PCIe TPH is disabled\n");
185*f69767a1SWei Huang }
186*f69767a1SWei Huang 
187*f69767a1SWei Huang void pci_tph_init(struct pci_dev *pdev)
188*f69767a1SWei Huang {
189*f69767a1SWei Huang 	u32 save_size;
190*f69767a1SWei Huang 
191*f69767a1SWei Huang 	pdev->tph_cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_TPH);
192*f69767a1SWei Huang 	if (!pdev->tph_cap)
193*f69767a1SWei Huang 		return;
194*f69767a1SWei Huang 
195*f69767a1SWei Huang 	save_size = sizeof(u32);
196*f69767a1SWei Huang 	pci_add_ext_cap_save_buffer(pdev, PCI_EXT_CAP_ID_TPH, save_size);
197*f69767a1SWei Huang }
198