xref: /linux/drivers/pci/pcie/ptm.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * PCI Express Precision Time Measurement
4  * Copyright (c) 2016, Intel Corporation.
5  */
6 
7 #include <linux/bitfield.h>
8 #include <linux/module.h>
9 #include <linux/init.h>
10 #include <linux/pci.h>
11 #include "../pci.h"
12 
13 /*
14  * If the next upstream device supports PTM, return it; otherwise return
15  * NULL.  PTM Messages are local, so both link partners must support it.
16  */
17 static struct pci_dev *pci_upstream_ptm(struct pci_dev *dev)
18 {
19 	struct pci_dev *ups = pci_upstream_bridge(dev);
20 
21 	/*
22 	 * Switch Downstream Ports are not permitted to have a PTM
23 	 * capability; their PTM behavior is controlled by the Upstream
24 	 * Port (PCIe r5.0, sec 7.9.16), so if the upstream bridge is a
25 	 * Switch Downstream Port, look up one more level.
26 	 */
27 	if (ups && pci_pcie_type(ups) == PCI_EXP_TYPE_DOWNSTREAM)
28 		ups = pci_upstream_bridge(ups);
29 
30 	if (ups && ups->ptm_cap)
31 		return ups;
32 
33 	return NULL;
34 }
35 
36 /*
37  * Find the PTM Capability (if present) and extract the information we need
38  * to use it.
39  */
40 void pci_ptm_init(struct pci_dev *dev)
41 {
42 	u16 ptm;
43 	u32 cap;
44 	struct pci_dev *ups;
45 
46 	if (!pci_is_pcie(dev))
47 		return;
48 
49 	ptm = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_PTM);
50 	if (!ptm)
51 		return;
52 
53 	dev->ptm_cap = ptm;
54 	pci_add_ext_cap_save_buffer(dev, PCI_EXT_CAP_ID_PTM, sizeof(u32));
55 
56 	pci_read_config_dword(dev, ptm + PCI_PTM_CAP, &cap);
57 	dev->ptm_granularity = FIELD_GET(PCI_PTM_GRANULARITY_MASK, cap);
58 
59 	/*
60 	 * Per the spec recommendation (PCIe r6.0, sec 7.9.15.3), select the
61 	 * furthest upstream Time Source as the PTM Root.  For Endpoints,
62 	 * "the Effective Granularity is the maximum Local Clock Granularity
63 	 * reported by the PTM Root and all intervening PTM Time Sources."
64 	 */
65 	ups = pci_upstream_ptm(dev);
66 	if (ups) {
67 		if (ups->ptm_granularity == 0)
68 			dev->ptm_granularity = 0;
69 		else if (ups->ptm_granularity > dev->ptm_granularity)
70 			dev->ptm_granularity = ups->ptm_granularity;
71 	} else if (cap & PCI_PTM_CAP_ROOT) {
72 		dev->ptm_root = 1;
73 	} else if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_END) {
74 
75 		/*
76 		 * Per sec 7.9.15.3, this should be the Local Clock
77 		 * Granularity of the associated Time Source.  But it
78 		 * doesn't say how to find that Time Source.
79 		 */
80 		dev->ptm_granularity = 0;
81 	}
82 
83 	if (pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT ||
84 	    pci_pcie_type(dev) == PCI_EXP_TYPE_UPSTREAM)
85 		pci_enable_ptm(dev, NULL);
86 }
87 
88 void pci_save_ptm_state(struct pci_dev *dev)
89 {
90 	u16 ptm = dev->ptm_cap;
91 	struct pci_cap_saved_state *save_state;
92 	u32 *cap;
93 
94 	if (!ptm)
95 		return;
96 
97 	save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_PTM);
98 	if (!save_state)
99 		return;
100 
101 	cap = (u32 *)&save_state->cap.data[0];
102 	pci_read_config_dword(dev, ptm + PCI_PTM_CTRL, cap);
103 }
104 
105 void pci_restore_ptm_state(struct pci_dev *dev)
106 {
107 	u16 ptm = dev->ptm_cap;
108 	struct pci_cap_saved_state *save_state;
109 	u32 *cap;
110 
111 	if (!ptm)
112 		return;
113 
114 	save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_PTM);
115 	if (!save_state)
116 		return;
117 
118 	cap = (u32 *)&save_state->cap.data[0];
119 	pci_write_config_dword(dev, ptm + PCI_PTM_CTRL, *cap);
120 }
121 
122 /* Enable PTM in the Control register if possible */
123 static int __pci_enable_ptm(struct pci_dev *dev)
124 {
125 	u16 ptm = dev->ptm_cap;
126 	struct pci_dev *ups;
127 	u32 ctrl;
128 
129 	if (!ptm)
130 		return -EINVAL;
131 
132 	/*
133 	 * A device uses local PTM Messages to request time information
134 	 * from a PTM Root that's farther upstream.  Every device along the
135 	 * path must support PTM and have it enabled so it can handle the
136 	 * messages.  Therefore, if this device is not a PTM Root, the
137 	 * upstream link partner must have PTM enabled before we can enable
138 	 * PTM.
139 	 */
140 	if (!dev->ptm_root) {
141 		ups = pci_upstream_ptm(dev);
142 		if (!ups || !ups->ptm_enabled)
143 			return -EINVAL;
144 	}
145 
146 	pci_read_config_dword(dev, ptm + PCI_PTM_CTRL, &ctrl);
147 
148 	ctrl |= PCI_PTM_CTRL_ENABLE;
149 	ctrl &= ~PCI_PTM_GRANULARITY_MASK;
150 	ctrl |= FIELD_PREP(PCI_PTM_GRANULARITY_MASK, dev->ptm_granularity);
151 	if (dev->ptm_root)
152 		ctrl |= PCI_PTM_CTRL_ROOT;
153 
154 	pci_write_config_dword(dev, ptm + PCI_PTM_CTRL, ctrl);
155 	return 0;
156 }
157 
158 /**
159  * pci_enable_ptm() - Enable Precision Time Measurement
160  * @dev: PCI device
161  * @granularity: pointer to return granularity
162  *
163  * Enable Precision Time Measurement for @dev.  If successful and
164  * @granularity is non-NULL, return the Effective Granularity.
165  *
166  * Return: zero if successful, or -EINVAL if @dev lacks a PTM Capability or
167  * is not a PTM Root and lacks an upstream path of PTM-enabled devices.
168  */
169 int pci_enable_ptm(struct pci_dev *dev, u8 *granularity)
170 {
171 	int rc;
172 	char clock_desc[8];
173 
174 	rc = __pci_enable_ptm(dev);
175 	if (rc)
176 		return rc;
177 
178 	dev->ptm_enabled = 1;
179 
180 	if (granularity)
181 		*granularity = dev->ptm_granularity;
182 
183 	switch (dev->ptm_granularity) {
184 	case 0:
185 		snprintf(clock_desc, sizeof(clock_desc), "unknown");
186 		break;
187 	case 255:
188 		snprintf(clock_desc, sizeof(clock_desc), ">254ns");
189 		break;
190 	default:
191 		snprintf(clock_desc, sizeof(clock_desc), "%uns",
192 			 dev->ptm_granularity);
193 		break;
194 	}
195 	pci_info(dev, "PTM enabled%s, %s granularity\n",
196 		 dev->ptm_root ? " (root)" : "", clock_desc);
197 
198 	return 0;
199 }
200 EXPORT_SYMBOL(pci_enable_ptm);
201 
202 static void __pci_disable_ptm(struct pci_dev *dev)
203 {
204 	u16 ptm = dev->ptm_cap;
205 	u32 ctrl;
206 
207 	if (!ptm)
208 		return;
209 
210 	pci_read_config_dword(dev, ptm + PCI_PTM_CTRL, &ctrl);
211 	ctrl &= ~(PCI_PTM_CTRL_ENABLE | PCI_PTM_CTRL_ROOT);
212 	pci_write_config_dword(dev, ptm + PCI_PTM_CTRL, ctrl);
213 }
214 
215 /**
216  * pci_disable_ptm() - Disable Precision Time Measurement
217  * @dev: PCI device
218  *
219  * Disable Precision Time Measurement for @dev.
220  */
221 void pci_disable_ptm(struct pci_dev *dev)
222 {
223 	if (dev->ptm_enabled) {
224 		__pci_disable_ptm(dev);
225 		dev->ptm_enabled = 0;
226 	}
227 }
228 EXPORT_SYMBOL(pci_disable_ptm);
229 
230 /*
231  * Disable PTM, but preserve dev->ptm_enabled so we silently re-enable it on
232  * resume if necessary.
233  */
234 void pci_suspend_ptm(struct pci_dev *dev)
235 {
236 	if (dev->ptm_enabled)
237 		__pci_disable_ptm(dev);
238 }
239 
240 /* If PTM was enabled before suspend, re-enable it when resuming */
241 void pci_resume_ptm(struct pci_dev *dev)
242 {
243 	if (dev->ptm_enabled)
244 		__pci_enable_ptm(dev);
245 }
246 
247 bool pcie_ptm_enabled(struct pci_dev *dev)
248 {
249 	if (!dev)
250 		return false;
251 
252 	return dev->ptm_enabled;
253 }
254 EXPORT_SYMBOL(pcie_ptm_enabled);
255