xref: /kernel/linux/linux-6.6/drivers/pci/pcie/ptm.c (revision 62306a36)
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 */
16static 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 */
39void 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
87void 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
104void 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 */
122static 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 */
168int 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}
199EXPORT_SYMBOL(pci_enable_ptm);
200
201static 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 */
220void 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}
227EXPORT_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 */
233void 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 */
240void pci_resume_ptm(struct pci_dev *dev)
241{
242	if (dev->ptm_enabled)
243		__pci_enable_ptm(dev);
244}
245
246bool pcie_ptm_enabled(struct pci_dev *dev)
247{
248	if (!dev)
249		return false;
250
251	return dev->ptm_enabled;
252}
253EXPORT_SYMBOL(pcie_ptm_enabled);
254