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