xref: /openbmc/linux/drivers/pci/pcie/ptm.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
18cfab3cfSBjorn Helgaas // SPDX-License-Identifier: GPL-2.0
29bb04a0cSJonathan Yong /*
39bb04a0cSJonathan Yong  * PCI Express Precision Time Measurement
49bb04a0cSJonathan Yong  * Copyright (c) 2016, Intel Corporation.
59bb04a0cSJonathan Yong  */
69bb04a0cSJonathan Yong 
79bb04a0cSJonathan Yong #include <linux/module.h>
89bb04a0cSJonathan Yong #include <linux/init.h>
99bb04a0cSJonathan Yong #include <linux/pci.h>
109bb04a0cSJonathan Yong #include "../pci.h"
119bb04a0cSJonathan Yong 
12e243c173SBjorn Helgaas /*
13e243c173SBjorn Helgaas  * If the next upstream device supports PTM, return it; otherwise return
14e243c173SBjorn Helgaas  * NULL.  PTM Messages are local, so both link partners must support it.
15e243c173SBjorn Helgaas  */
pci_upstream_ptm(struct pci_dev * dev)16e243c173SBjorn Helgaas static struct pci_dev *pci_upstream_ptm(struct pci_dev *dev)
17e243c173SBjorn Helgaas {
18e243c173SBjorn Helgaas 	struct pci_dev *ups = pci_upstream_bridge(dev);
19e243c173SBjorn Helgaas 
20e243c173SBjorn Helgaas 	/*
21e243c173SBjorn Helgaas 	 * Switch Downstream Ports are not permitted to have a PTM
22e243c173SBjorn Helgaas 	 * capability; their PTM behavior is controlled by the Upstream
23e243c173SBjorn Helgaas 	 * Port (PCIe r5.0, sec 7.9.16), so if the upstream bridge is a
24e243c173SBjorn Helgaas 	 * Switch Downstream Port, look up one more level.
25e243c173SBjorn Helgaas 	 */
26e243c173SBjorn Helgaas 	if (ups && pci_pcie_type(ups) == PCI_EXP_TYPE_DOWNSTREAM)
27e243c173SBjorn Helgaas 		ups = pci_upstream_bridge(ups);
28e243c173SBjorn Helgaas 
29e243c173SBjorn Helgaas 	if (ups && ups->ptm_cap)
30e243c173SBjorn Helgaas 		return ups;
31e243c173SBjorn Helgaas 
32e243c173SBjorn Helgaas 	return NULL;
33e243c173SBjorn Helgaas }
34e243c173SBjorn Helgaas 
35118b9dfdSBjorn Helgaas /*
36118b9dfdSBjorn Helgaas  * Find the PTM Capability (if present) and extract the information we need
37118b9dfdSBjorn Helgaas  * to use it.
38118b9dfdSBjorn Helgaas  */
pci_ptm_init(struct pci_dev * dev)399bb04a0cSJonathan Yong void pci_ptm_init(struct pci_dev *dev)
409bb04a0cSJonathan Yong {
41a47126ecSBjorn Helgaas 	u16 ptm;
42118b9dfdSBjorn Helgaas 	u32 cap;
439bb04a0cSJonathan Yong 	struct pci_dev *ups;
449bb04a0cSJonathan Yong 
459bb04a0cSJonathan Yong 	if (!pci_is_pcie(dev))
469bb04a0cSJonathan Yong 		return;
479bb04a0cSJonathan Yong 
48a47126ecSBjorn Helgaas 	ptm = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_PTM);
49a47126ecSBjorn Helgaas 	if (!ptm)
507b38fd97SBjorn Helgaas 		return;
517b38fd97SBjorn Helgaas 
52a47126ecSBjorn Helgaas 	dev->ptm_cap = ptm;
532b89c22fSBjorn Helgaas 	pci_add_ext_cap_save_buffer(dev, PCI_EXT_CAP_ID_PTM, sizeof(u32));
5439850ed5SDavid E. Box 
55a47126ecSBjorn Helgaas 	pci_read_config_dword(dev, ptm + PCI_PTM_CAP, &cap);
56118b9dfdSBjorn Helgaas 	dev->ptm_granularity = (cap & PCI_PTM_GRANULARITY_MASK) >> 8;
579bb04a0cSJonathan Yong 
589bb04a0cSJonathan Yong 	/*
59118b9dfdSBjorn Helgaas 	 * Per the spec recommendation (PCIe r6.0, sec 7.9.15.3), select the
60118b9dfdSBjorn Helgaas 	 * furthest upstream Time Source as the PTM Root.  For Endpoints,
61118b9dfdSBjorn Helgaas 	 * "the Effective Granularity is the maximum Local Clock Granularity
62118b9dfdSBjorn Helgaas 	 * reported by the PTM Root and all intervening PTM Time Sources."
639bb04a0cSJonathan Yong 	 */
64e243c173SBjorn Helgaas 	ups = pci_upstream_ptm(dev);
65118b9dfdSBjorn Helgaas 	if (ups) {
668b2ec318SBjorn Helgaas 		if (ups->ptm_granularity == 0)
678b2ec318SBjorn Helgaas 			dev->ptm_granularity = 0;
68118b9dfdSBjorn Helgaas 		else if (ups->ptm_granularity > dev->ptm_granularity)
698b2ec318SBjorn Helgaas 			dev->ptm_granularity = ups->ptm_granularity;
70118b9dfdSBjorn Helgaas 	} else if (cap & PCI_PTM_CAP_ROOT) {
719bb04a0cSJonathan Yong 		dev->ptm_root = 1;
72118b9dfdSBjorn Helgaas 	} else if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_END) {
73118b9dfdSBjorn Helgaas 
74118b9dfdSBjorn Helgaas 		/*
75118b9dfdSBjorn Helgaas 		 * Per sec 7.9.15.3, this should be the Local Clock
76118b9dfdSBjorn Helgaas 		 * Granularity of the associated Time Source.  But it
77118b9dfdSBjorn Helgaas 		 * doesn't say how to find that Time Source.
78118b9dfdSBjorn Helgaas 		 */
79118b9dfdSBjorn Helgaas 		dev->ptm_granularity = 0;
809bb04a0cSJonathan Yong 	}
819bb04a0cSJonathan Yong 
82118b9dfdSBjorn Helgaas 	if (pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT ||
83118b9dfdSBjorn Helgaas 	    pci_pcie_type(dev) == PCI_EXP_TYPE_UPSTREAM)
84118b9dfdSBjorn Helgaas 		pci_enable_ptm(dev, NULL);
859bb04a0cSJonathan Yong }
86eec097d4SBjorn Helgaas 
pci_save_ptm_state(struct pci_dev * dev)87*8b367e75SBjorn Helgaas void pci_save_ptm_state(struct pci_dev *dev)
88*8b367e75SBjorn Helgaas {
89*8b367e75SBjorn Helgaas 	u16 ptm = dev->ptm_cap;
90*8b367e75SBjorn Helgaas 	struct pci_cap_saved_state *save_state;
91*8b367e75SBjorn Helgaas 	u32 *cap;
92*8b367e75SBjorn Helgaas 
93*8b367e75SBjorn Helgaas 	if (!ptm)
94*8b367e75SBjorn Helgaas 		return;
95*8b367e75SBjorn Helgaas 
96*8b367e75SBjorn Helgaas 	save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_PTM);
97*8b367e75SBjorn Helgaas 	if (!save_state)
98*8b367e75SBjorn Helgaas 		return;
99*8b367e75SBjorn Helgaas 
100*8b367e75SBjorn Helgaas 	cap = (u32 *)&save_state->cap.data[0];
101*8b367e75SBjorn Helgaas 	pci_read_config_dword(dev, ptm + PCI_PTM_CTRL, cap);
102*8b367e75SBjorn Helgaas }
103*8b367e75SBjorn Helgaas 
pci_restore_ptm_state(struct pci_dev * dev)104*8b367e75SBjorn Helgaas void pci_restore_ptm_state(struct pci_dev *dev)
105*8b367e75SBjorn Helgaas {
106*8b367e75SBjorn Helgaas 	u16 ptm = dev->ptm_cap;
107*8b367e75SBjorn Helgaas 	struct pci_cap_saved_state *save_state;
108*8b367e75SBjorn Helgaas 	u32 *cap;
109*8b367e75SBjorn Helgaas 
110*8b367e75SBjorn Helgaas 	if (!ptm)
111*8b367e75SBjorn Helgaas 		return;
112*8b367e75SBjorn Helgaas 
113*8b367e75SBjorn Helgaas 	save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_PTM);
114*8b367e75SBjorn Helgaas 	if (!save_state)
115*8b367e75SBjorn Helgaas 		return;
116*8b367e75SBjorn Helgaas 
117*8b367e75SBjorn Helgaas 	cap = (u32 *)&save_state->cap.data[0];
118*8b367e75SBjorn Helgaas 	pci_write_config_dword(dev, ptm + PCI_PTM_CTRL, *cap);
119*8b367e75SBjorn Helgaas }
120*8b367e75SBjorn Helgaas 
121e8bdc5eaSBjorn Helgaas /* Enable PTM in the Control register if possible */
__pci_enable_ptm(struct pci_dev * dev)122e8bdc5eaSBjorn Helgaas static int __pci_enable_ptm(struct pci_dev *dev)
123eec097d4SBjorn Helgaas {
124118b9dfdSBjorn Helgaas 	u16 ptm = dev->ptm_cap;
125eec097d4SBjorn Helgaas 	struct pci_dev *ups;
126118b9dfdSBjorn Helgaas 	u32 ctrl;
127eec097d4SBjorn Helgaas 
128a47126ecSBjorn Helgaas 	if (!ptm)
129eec097d4SBjorn Helgaas 		return -EINVAL;
130eec097d4SBjorn Helgaas 
131eec097d4SBjorn Helgaas 	/*
132118b9dfdSBjorn Helgaas 	 * A device uses local PTM Messages to request time information
133118b9dfdSBjorn Helgaas 	 * from a PTM Root that's farther upstream.  Every device along the
134118b9dfdSBjorn Helgaas 	 * path must support PTM and have it enabled so it can handle the
135118b9dfdSBjorn Helgaas 	 * messages.  Therefore, if this device is not a PTM Root, the
136118b9dfdSBjorn Helgaas 	 * upstream link partner must have PTM enabled before we can enable
137118b9dfdSBjorn Helgaas 	 * PTM.
138eec097d4SBjorn Helgaas 	 */
139118b9dfdSBjorn Helgaas 	if (!dev->ptm_root) {
140e243c173SBjorn Helgaas 		ups = pci_upstream_ptm(dev);
141eec097d4SBjorn Helgaas 		if (!ups || !ups->ptm_enabled)
142eec097d4SBjorn Helgaas 			return -EINVAL;
143118b9dfdSBjorn Helgaas 	}
144eec097d4SBjorn Helgaas 
1452b89c22fSBjorn Helgaas 	pci_read_config_dword(dev, ptm + PCI_PTM_CTRL, &ctrl);
1462b89c22fSBjorn Helgaas 
1472b89c22fSBjorn Helgaas 	ctrl |= PCI_PTM_CTRL_ENABLE;
1482b89c22fSBjorn Helgaas 	ctrl &= ~PCI_PTM_GRANULARITY_MASK;
1498b2ec318SBjorn Helgaas 	ctrl |= dev->ptm_granularity << 8;
150118b9dfdSBjorn Helgaas 	if (dev->ptm_root)
151118b9dfdSBjorn Helgaas 		ctrl |= PCI_PTM_CTRL_ROOT;
152118b9dfdSBjorn Helgaas 
153a47126ecSBjorn Helgaas 	pci_write_config_dword(dev, ptm + PCI_PTM_CTRL, ctrl);
154e8bdc5eaSBjorn Helgaas 	return 0;
155e8bdc5eaSBjorn Helgaas }
156eec097d4SBjorn Helgaas 
157e8bdc5eaSBjorn Helgaas /**
158e8bdc5eaSBjorn Helgaas  * pci_enable_ptm() - Enable Precision Time Measurement
159e8bdc5eaSBjorn Helgaas  * @dev: PCI device
160e8bdc5eaSBjorn Helgaas  * @granularity: pointer to return granularity
161e8bdc5eaSBjorn Helgaas  *
162e8bdc5eaSBjorn Helgaas  * Enable Precision Time Measurement for @dev.  If successful and
163e8bdc5eaSBjorn Helgaas  * @granularity is non-NULL, return the Effective Granularity.
164e8bdc5eaSBjorn Helgaas  *
165e8bdc5eaSBjorn Helgaas  * Return: zero if successful, or -EINVAL if @dev lacks a PTM Capability or
166e8bdc5eaSBjorn Helgaas  * is not a PTM Root and lacks an upstream path of PTM-enabled devices.
167e8bdc5eaSBjorn Helgaas  */
pci_enable_ptm(struct pci_dev * dev,u8 * granularity)168e8bdc5eaSBjorn Helgaas int pci_enable_ptm(struct pci_dev *dev, u8 *granularity)
169e8bdc5eaSBjorn Helgaas {
170e8bdc5eaSBjorn Helgaas 	int rc;
17191b12b2aSBjorn Helgaas 	char clock_desc[8];
172e8bdc5eaSBjorn Helgaas 
173e8bdc5eaSBjorn Helgaas 	rc = __pci_enable_ptm(dev);
174e8bdc5eaSBjorn Helgaas 	if (rc)
175e8bdc5eaSBjorn Helgaas 		return rc;
176e8bdc5eaSBjorn Helgaas 
177e8bdc5eaSBjorn Helgaas 	dev->ptm_enabled = 1;
178eec097d4SBjorn Helgaas 
179eec097d4SBjorn Helgaas 	if (granularity)
1808b2ec318SBjorn Helgaas 		*granularity = dev->ptm_granularity;
18191b12b2aSBjorn Helgaas 
18291b12b2aSBjorn Helgaas 	switch (dev->ptm_granularity) {
18391b12b2aSBjorn Helgaas 	case 0:
18491b12b2aSBjorn Helgaas 		snprintf(clock_desc, sizeof(clock_desc), "unknown");
18591b12b2aSBjorn Helgaas 		break;
18691b12b2aSBjorn Helgaas 	case 255:
18791b12b2aSBjorn Helgaas 		snprintf(clock_desc, sizeof(clock_desc), ">254ns");
18891b12b2aSBjorn Helgaas 		break;
18991b12b2aSBjorn Helgaas 	default:
19091b12b2aSBjorn Helgaas 		snprintf(clock_desc, sizeof(clock_desc), "%uns",
19191b12b2aSBjorn Helgaas 			 dev->ptm_granularity);
19291b12b2aSBjorn Helgaas 		break;
19391b12b2aSBjorn Helgaas 	}
19491b12b2aSBjorn Helgaas 	pci_info(dev, "PTM enabled%s, %s granularity\n",
19591b12b2aSBjorn Helgaas 		 dev->ptm_root ? " (root)" : "", clock_desc);
19691b12b2aSBjorn Helgaas 
197eec097d4SBjorn Helgaas 	return 0;
198eec097d4SBjorn Helgaas }
199eec097d4SBjorn Helgaas EXPORT_SYMBOL(pci_enable_ptm);
200014408cdSVinicius Costa Gomes 
__pci_disable_ptm(struct pci_dev * dev)201*8b367e75SBjorn Helgaas static void __pci_disable_ptm(struct pci_dev *dev)
202*8b367e75SBjorn Helgaas {
203*8b367e75SBjorn Helgaas 	u16 ptm = dev->ptm_cap;
204*8b367e75SBjorn Helgaas 	u32 ctrl;
205*8b367e75SBjorn Helgaas 
206*8b367e75SBjorn Helgaas 	if (!ptm)
207*8b367e75SBjorn Helgaas 		return;
208*8b367e75SBjorn Helgaas 
209*8b367e75SBjorn Helgaas 	pci_read_config_dword(dev, ptm + PCI_PTM_CTRL, &ctrl);
210*8b367e75SBjorn Helgaas 	ctrl &= ~(PCI_PTM_CTRL_ENABLE | PCI_PTM_CTRL_ROOT);
211*8b367e75SBjorn Helgaas 	pci_write_config_dword(dev, ptm + PCI_PTM_CTRL, ctrl);
212*8b367e75SBjorn Helgaas }
213*8b367e75SBjorn Helgaas 
214*8b367e75SBjorn Helgaas /**
215*8b367e75SBjorn Helgaas  * pci_disable_ptm() - Disable Precision Time Measurement
216*8b367e75SBjorn Helgaas  * @dev: PCI device
217*8b367e75SBjorn Helgaas  *
218*8b367e75SBjorn Helgaas  * Disable Precision Time Measurement for @dev.
219*8b367e75SBjorn Helgaas  */
pci_disable_ptm(struct pci_dev * dev)220*8b367e75SBjorn Helgaas void pci_disable_ptm(struct pci_dev *dev)
221*8b367e75SBjorn Helgaas {
222*8b367e75SBjorn Helgaas 	if (dev->ptm_enabled) {
223*8b367e75SBjorn Helgaas 		__pci_disable_ptm(dev);
224*8b367e75SBjorn Helgaas 		dev->ptm_enabled = 0;
225*8b367e75SBjorn Helgaas 	}
226*8b367e75SBjorn Helgaas }
227*8b367e75SBjorn Helgaas EXPORT_SYMBOL(pci_disable_ptm);
228*8b367e75SBjorn Helgaas 
229e8bdc5eaSBjorn Helgaas /*
230e8bdc5eaSBjorn Helgaas  * Disable PTM, but preserve dev->ptm_enabled so we silently re-enable it on
231e8bdc5eaSBjorn Helgaas  * resume if necessary.
232e8bdc5eaSBjorn Helgaas  */
pci_suspend_ptm(struct pci_dev * dev)233e8bdc5eaSBjorn Helgaas void pci_suspend_ptm(struct pci_dev *dev)
234e8bdc5eaSBjorn Helgaas {
235e8bdc5eaSBjorn Helgaas 	if (dev->ptm_enabled)
236e8bdc5eaSBjorn Helgaas 		__pci_disable_ptm(dev);
237e8bdc5eaSBjorn Helgaas }
238e8bdc5eaSBjorn Helgaas 
239e8bdc5eaSBjorn Helgaas /* If PTM was enabled before suspend, re-enable it when resuming */
pci_resume_ptm(struct pci_dev * dev)240e8bdc5eaSBjorn Helgaas void pci_resume_ptm(struct pci_dev *dev)
241e8bdc5eaSBjorn Helgaas {
242e8bdc5eaSBjorn Helgaas 	if (dev->ptm_enabled)
243e8bdc5eaSBjorn Helgaas 		__pci_enable_ptm(dev);
244e8bdc5eaSBjorn Helgaas }
245e8bdc5eaSBjorn Helgaas 
pcie_ptm_enabled(struct pci_dev * dev)246014408cdSVinicius Costa Gomes bool pcie_ptm_enabled(struct pci_dev *dev)
247014408cdSVinicius Costa Gomes {
248014408cdSVinicius Costa Gomes 	if (!dev)
249014408cdSVinicius Costa Gomes 		return false;
250014408cdSVinicius Costa Gomes 
251014408cdSVinicius Costa Gomes 	return dev->ptm_enabled;
252014408cdSVinicius Costa Gomes }
253014408cdSVinicius Costa Gomes EXPORT_SYMBOL(pcie_ptm_enabled);
254