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 129bb04a0cSJonathan Yong static void pci_ptm_info(struct pci_dev *dev) 139bb04a0cSJonathan Yong { 148b2ec318SBjorn Helgaas char clock_desc[8]; 158b2ec318SBjorn Helgaas 168b2ec318SBjorn Helgaas switch (dev->ptm_granularity) { 178b2ec318SBjorn Helgaas case 0: 188b2ec318SBjorn Helgaas snprintf(clock_desc, sizeof(clock_desc), "unknown"); 198b2ec318SBjorn Helgaas break; 208b2ec318SBjorn Helgaas case 255: 218b2ec318SBjorn Helgaas snprintf(clock_desc, sizeof(clock_desc), ">254ns"); 228b2ec318SBjorn Helgaas break; 238b2ec318SBjorn Helgaas default: 24127a7709SBjorn Helgaas snprintf(clock_desc, sizeof(clock_desc), "%uns", 258b2ec318SBjorn Helgaas dev->ptm_granularity); 268b2ec318SBjorn Helgaas break; 278b2ec318SBjorn Helgaas } 287506dc79SFrederick Lawler pci_info(dev, "PTM enabled%s, %s granularity\n", 298b2ec318SBjorn Helgaas dev->ptm_root ? " (root)" : "", clock_desc); 309bb04a0cSJonathan Yong } 319bb04a0cSJonathan Yong 32*a697f072SDavid E. Box void pci_disable_ptm(struct pci_dev *dev) 33*a697f072SDavid E. Box { 34*a697f072SDavid E. Box int ptm; 35*a697f072SDavid E. Box u16 ctrl; 36*a697f072SDavid E. Box 37*a697f072SDavid E. Box if (!pci_is_pcie(dev)) 38*a697f072SDavid E. Box return; 39*a697f072SDavid E. Box 40*a697f072SDavid E. Box ptm = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_PTM); 41*a697f072SDavid E. Box if (!ptm) 42*a697f072SDavid E. Box return; 43*a697f072SDavid E. Box 44*a697f072SDavid E. Box pci_read_config_word(dev, ptm + PCI_PTM_CTRL, &ctrl); 45*a697f072SDavid E. Box ctrl &= ~(PCI_PTM_CTRL_ENABLE | PCI_PTM_CTRL_ROOT); 46*a697f072SDavid E. Box pci_write_config_word(dev, ptm + PCI_PTM_CTRL, ctrl); 47*a697f072SDavid E. Box } 48*a697f072SDavid E. Box 4939850ed5SDavid E. Box void pci_save_ptm_state(struct pci_dev *dev) 5039850ed5SDavid E. Box { 5139850ed5SDavid E. Box int ptm; 5239850ed5SDavid E. Box struct pci_cap_saved_state *save_state; 5339850ed5SDavid E. Box u16 *cap; 5439850ed5SDavid E. Box 5539850ed5SDavid E. Box if (!pci_is_pcie(dev)) 5639850ed5SDavid E. Box return; 5739850ed5SDavid E. Box 5839850ed5SDavid E. Box ptm = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_PTM); 5939850ed5SDavid E. Box if (!ptm) 6039850ed5SDavid E. Box return; 6139850ed5SDavid E. Box 6239850ed5SDavid E. Box save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_PTM); 6339850ed5SDavid E. Box if (!save_state) { 6439850ed5SDavid E. Box pci_err(dev, "no suspend buffer for PTM\n"); 6539850ed5SDavid E. Box return; 6639850ed5SDavid E. Box } 6739850ed5SDavid E. Box 6839850ed5SDavid E. Box cap = (u16 *)&save_state->cap.data[0]; 6939850ed5SDavid E. Box pci_read_config_word(dev, ptm + PCI_PTM_CTRL, cap); 7039850ed5SDavid E. Box } 7139850ed5SDavid E. Box 7239850ed5SDavid E. Box void pci_restore_ptm_state(struct pci_dev *dev) 7339850ed5SDavid E. Box { 7439850ed5SDavid E. Box struct pci_cap_saved_state *save_state; 7539850ed5SDavid E. Box int ptm; 7639850ed5SDavid E. Box u16 *cap; 7739850ed5SDavid E. Box 7839850ed5SDavid E. Box if (!pci_is_pcie(dev)) 7939850ed5SDavid E. Box return; 8039850ed5SDavid E. Box 8139850ed5SDavid E. Box save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_PTM); 8239850ed5SDavid E. Box ptm = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_PTM); 8339850ed5SDavid E. Box if (!save_state || !ptm) 8439850ed5SDavid E. Box return; 8539850ed5SDavid E. Box 8639850ed5SDavid E. Box cap = (u16 *)&save_state->cap.data[0]; 8739850ed5SDavid E. Box pci_write_config_word(dev, ptm + PCI_PTM_CTRL, *cap); 8839850ed5SDavid E. Box } 8939850ed5SDavid E. Box 909bb04a0cSJonathan Yong void pci_ptm_init(struct pci_dev *dev) 919bb04a0cSJonathan Yong { 929bb04a0cSJonathan Yong int pos; 939bb04a0cSJonathan Yong u32 cap, ctrl; 948b2ec318SBjorn Helgaas u8 local_clock; 959bb04a0cSJonathan Yong struct pci_dev *ups; 969bb04a0cSJonathan Yong 979bb04a0cSJonathan Yong if (!pci_is_pcie(dev)) 989bb04a0cSJonathan Yong return; 999bb04a0cSJonathan Yong 1009bb04a0cSJonathan Yong /* 1019bb04a0cSJonathan Yong * Enable PTM only on interior devices (root ports, switch ports, 1029bb04a0cSJonathan Yong * etc.) on the assumption that it causes no link traffic until an 1039bb04a0cSJonathan Yong * endpoint enables it. 1049bb04a0cSJonathan Yong */ 1059bb04a0cSJonathan Yong if ((pci_pcie_type(dev) == PCI_EXP_TYPE_ENDPOINT || 1069bb04a0cSJonathan Yong pci_pcie_type(dev) == PCI_EXP_TYPE_RC_END)) 1079bb04a0cSJonathan Yong return; 1089bb04a0cSJonathan Yong 1097b38fd97SBjorn Helgaas /* 1107b38fd97SBjorn Helgaas * Switch Downstream Ports are not permitted to have a PTM 1117b38fd97SBjorn Helgaas * capability; their PTM behavior is controlled by the Upstream 1127b38fd97SBjorn Helgaas * Port (PCIe r5.0, sec 7.9.16). 1137b38fd97SBjorn Helgaas */ 1147b38fd97SBjorn Helgaas ups = pci_upstream_bridge(dev); 1157b38fd97SBjorn Helgaas if (pci_pcie_type(dev) == PCI_EXP_TYPE_DOWNSTREAM && 1167b38fd97SBjorn Helgaas ups && ups->ptm_enabled) { 1177b38fd97SBjorn Helgaas dev->ptm_granularity = ups->ptm_granularity; 1187b38fd97SBjorn Helgaas dev->ptm_enabled = 1; 1197b38fd97SBjorn Helgaas return; 1207b38fd97SBjorn Helgaas } 1217b38fd97SBjorn Helgaas 1227b38fd97SBjorn Helgaas pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_PTM); 1237b38fd97SBjorn Helgaas if (!pos) 1247b38fd97SBjorn Helgaas return; 1257b38fd97SBjorn Helgaas 12639850ed5SDavid E. Box pci_add_ext_cap_save_buffer(dev, PCI_EXT_CAP_ID_PTM, sizeof(u16)); 12739850ed5SDavid E. Box 1289bb04a0cSJonathan Yong pci_read_config_dword(dev, pos + PCI_PTM_CAP, &cap); 1298b2ec318SBjorn Helgaas local_clock = (cap & PCI_PTM_GRANULARITY_MASK) >> 8; 1309bb04a0cSJonathan Yong 1319bb04a0cSJonathan Yong /* 1329bb04a0cSJonathan Yong * There's no point in enabling PTM unless it's enabled in the 1339bb04a0cSJonathan Yong * upstream device or this device can be a PTM Root itself. Per 1349bb04a0cSJonathan Yong * the spec recommendation (PCIe r3.1, sec 7.32.3), select the 1359bb04a0cSJonathan Yong * furthest upstream Time Source as the PTM Root. 1369bb04a0cSJonathan Yong */ 1379bb04a0cSJonathan Yong if (ups && ups->ptm_enabled) { 1389bb04a0cSJonathan Yong ctrl = PCI_PTM_CTRL_ENABLE; 1398b2ec318SBjorn Helgaas if (ups->ptm_granularity == 0) 1408b2ec318SBjorn Helgaas dev->ptm_granularity = 0; 1418b2ec318SBjorn Helgaas else if (ups->ptm_granularity > local_clock) 1428b2ec318SBjorn Helgaas dev->ptm_granularity = ups->ptm_granularity; 1439bb04a0cSJonathan Yong } else { 1449bb04a0cSJonathan Yong if (cap & PCI_PTM_CAP_ROOT) { 1459bb04a0cSJonathan Yong ctrl = PCI_PTM_CTRL_ENABLE | PCI_PTM_CTRL_ROOT; 1469bb04a0cSJonathan Yong dev->ptm_root = 1; 1478b2ec318SBjorn Helgaas dev->ptm_granularity = local_clock; 1489bb04a0cSJonathan Yong } else 1499bb04a0cSJonathan Yong return; 1509bb04a0cSJonathan Yong } 1519bb04a0cSJonathan Yong 1528b2ec318SBjorn Helgaas ctrl |= dev->ptm_granularity << 8; 1539bb04a0cSJonathan Yong pci_write_config_dword(dev, pos + PCI_PTM_CTRL, ctrl); 1549bb04a0cSJonathan Yong dev->ptm_enabled = 1; 1559bb04a0cSJonathan Yong 1569bb04a0cSJonathan Yong pci_ptm_info(dev); 1579bb04a0cSJonathan Yong } 158eec097d4SBjorn Helgaas 159eec097d4SBjorn Helgaas int pci_enable_ptm(struct pci_dev *dev, u8 *granularity) 160eec097d4SBjorn Helgaas { 161eec097d4SBjorn Helgaas int pos; 162eec097d4SBjorn Helgaas u32 cap, ctrl; 163eec097d4SBjorn Helgaas struct pci_dev *ups; 164eec097d4SBjorn Helgaas 165eec097d4SBjorn Helgaas if (!pci_is_pcie(dev)) 166eec097d4SBjorn Helgaas return -EINVAL; 167eec097d4SBjorn Helgaas 168eec097d4SBjorn Helgaas pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_PTM); 169eec097d4SBjorn Helgaas if (!pos) 170eec097d4SBjorn Helgaas return -EINVAL; 171eec097d4SBjorn Helgaas 172eec097d4SBjorn Helgaas pci_read_config_dword(dev, pos + PCI_PTM_CAP, &cap); 173eec097d4SBjorn Helgaas if (!(cap & PCI_PTM_CAP_REQ)) 174eec097d4SBjorn Helgaas return -EINVAL; 175eec097d4SBjorn Helgaas 176eec097d4SBjorn Helgaas /* 177eec097d4SBjorn Helgaas * For a PCIe Endpoint, PTM is only useful if the endpoint can 178eec097d4SBjorn Helgaas * issue PTM requests to upstream devices that have PTM enabled. 179eec097d4SBjorn Helgaas * 180eec097d4SBjorn Helgaas * For Root Complex Integrated Endpoints, there is no upstream 181eec097d4SBjorn Helgaas * device, so there must be some implementation-specific way to 182eec097d4SBjorn Helgaas * associate the endpoint with a time source. 183eec097d4SBjorn Helgaas */ 184eec097d4SBjorn Helgaas if (pci_pcie_type(dev) == PCI_EXP_TYPE_ENDPOINT) { 185eec097d4SBjorn Helgaas ups = pci_upstream_bridge(dev); 186eec097d4SBjorn Helgaas if (!ups || !ups->ptm_enabled) 187eec097d4SBjorn Helgaas return -EINVAL; 1888b2ec318SBjorn Helgaas 1898b2ec318SBjorn Helgaas dev->ptm_granularity = ups->ptm_granularity; 190eec097d4SBjorn Helgaas } else if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_END) { 1918b2ec318SBjorn Helgaas dev->ptm_granularity = 0; 192eec097d4SBjorn Helgaas } else 193eec097d4SBjorn Helgaas return -EINVAL; 194eec097d4SBjorn Helgaas 195eec097d4SBjorn Helgaas ctrl = PCI_PTM_CTRL_ENABLE; 1968b2ec318SBjorn Helgaas ctrl |= dev->ptm_granularity << 8; 197eec097d4SBjorn Helgaas pci_write_config_dword(dev, pos + PCI_PTM_CTRL, ctrl); 198eec097d4SBjorn Helgaas dev->ptm_enabled = 1; 199eec097d4SBjorn Helgaas 200eec097d4SBjorn Helgaas pci_ptm_info(dev); 201eec097d4SBjorn Helgaas 202eec097d4SBjorn Helgaas if (granularity) 2038b2ec318SBjorn Helgaas *granularity = dev->ptm_granularity; 204eec097d4SBjorn Helgaas return 0; 205eec097d4SBjorn Helgaas } 206eec097d4SBjorn Helgaas EXPORT_SYMBOL(pci_enable_ptm); 207