255 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * PCI Express Precision Time Measurement4 * 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 return15 * NULL. PTM Messages are local, so both link partners must support it.16 */17static 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 PTM23 * capability; their PTM behavior is controlled by the Upstream24 * Port (PCIe r5.0, sec 7.9.16), so if the upstream bridge is a25 * 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 need38 * to use it.39 */40void 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 the61 * furthest upstream Time Source as the PTM Root. For Endpoints,62 * "the Effective Granularity is the maximum Local Clock Granularity63 * 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 Clock77 * Granularity of the associated Time Source. But it78 * 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 88void 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 105void 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 */123static 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 information134 * from a PTM Root that's farther upstream. Every device along the135 * path must support PTM and have it enabled so it can handle the136 * messages. Therefore, if this device is not a PTM Root, the137 * upstream link partner must have PTM enabled before we can enable138 * 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 Measurement160 * @dev: PCI device161 * @granularity: pointer to return granularity162 *163 * Enable Precision Time Measurement for @dev. If successful and164 * @granularity is non-NULL, return the Effective Granularity.165 *166 * Return: zero if successful, or -EINVAL if @dev lacks a PTM Capability or167 * is not a PTM Root and lacks an upstream path of PTM-enabled devices.168 */169int 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}200EXPORT_SYMBOL(pci_enable_ptm);201 202static 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 Measurement217 * @dev: PCI device218 *219 * Disable Precision Time Measurement for @dev.220 */221void 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}228EXPORT_SYMBOL(pci_disable_ptm);229 230/*231 * Disable PTM, but preserve dev->ptm_enabled so we silently re-enable it on232 * resume if necessary.233 */234void 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 */241void pci_resume_ptm(struct pci_dev *dev)242{243 if (dev->ptm_enabled)244 __pci_enable_ptm(dev);245}246 247bool pcie_ptm_enabled(struct pci_dev *dev)248{249 if (!dev)250 return false;251 252 return dev->ptm_enabled;253}254EXPORT_SYMBOL(pcie_ptm_enabled);255