GNU Linux-libre 6.8.7-gnu
[releases.git] / drivers / pci / pcie / ptm.c
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);