1*f69767a1SWei Huang // SPDX-License-Identifier: GPL-2.0 2*f69767a1SWei Huang /* 3*f69767a1SWei Huang * TPH (TLP Processing Hints) support 4*f69767a1SWei Huang * 5*f69767a1SWei Huang * Copyright (C) 2024 Advanced Micro Devices, Inc. 6*f69767a1SWei Huang * Eric Van Tassell <Eric.VanTassell@amd.com> 7*f69767a1SWei Huang * Wei Huang <wei.huang2@amd.com> 8*f69767a1SWei Huang */ 9*f69767a1SWei Huang #include <linux/pci.h> 10*f69767a1SWei Huang #include <linux/bitfield.h> 11*f69767a1SWei Huang #include <linux/pci-tph.h> 12*f69767a1SWei Huang 13*f69767a1SWei Huang #include "pci.h" 14*f69767a1SWei Huang 15*f69767a1SWei Huang /* System-wide TPH disabled */ 16*f69767a1SWei Huang static bool pci_tph_disabled; 17*f69767a1SWei Huang 18*f69767a1SWei Huang static u8 get_st_modes(struct pci_dev *pdev) 19*f69767a1SWei Huang { 20*f69767a1SWei Huang u32 reg; 21*f69767a1SWei Huang 22*f69767a1SWei Huang pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CAP, ®); 23*f69767a1SWei Huang reg &= PCI_TPH_CAP_ST_NS | PCI_TPH_CAP_ST_IV | PCI_TPH_CAP_ST_DS; 24*f69767a1SWei Huang 25*f69767a1SWei Huang return reg; 26*f69767a1SWei Huang } 27*f69767a1SWei Huang 28*f69767a1SWei Huang /* Return device's Root Port completer capability */ 29*f69767a1SWei Huang static u8 get_rp_completer_type(struct pci_dev *pdev) 30*f69767a1SWei Huang { 31*f69767a1SWei Huang struct pci_dev *rp; 32*f69767a1SWei Huang u32 reg; 33*f69767a1SWei Huang int ret; 34*f69767a1SWei Huang 35*f69767a1SWei Huang rp = pcie_find_root_port(pdev); 36*f69767a1SWei Huang if (!rp) 37*f69767a1SWei Huang return 0; 38*f69767a1SWei Huang 39*f69767a1SWei Huang ret = pcie_capability_read_dword(rp, PCI_EXP_DEVCAP2, ®); 40*f69767a1SWei Huang if (ret) 41*f69767a1SWei Huang return 0; 42*f69767a1SWei Huang 43*f69767a1SWei Huang return FIELD_GET(PCI_EXP_DEVCAP2_TPH_COMP_MASK, reg); 44*f69767a1SWei Huang } 45*f69767a1SWei Huang 46*f69767a1SWei Huang /** 47*f69767a1SWei Huang * pcie_disable_tph - Turn off TPH support for device 48*f69767a1SWei Huang * @pdev: PCI device 49*f69767a1SWei Huang * 50*f69767a1SWei Huang * Return: none 51*f69767a1SWei Huang */ 52*f69767a1SWei Huang void pcie_disable_tph(struct pci_dev *pdev) 53*f69767a1SWei Huang { 54*f69767a1SWei Huang if (!pdev->tph_cap) 55*f69767a1SWei Huang return; 56*f69767a1SWei Huang 57*f69767a1SWei Huang if (!pdev->tph_enabled) 58*f69767a1SWei Huang return; 59*f69767a1SWei Huang 60*f69767a1SWei Huang pci_write_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, 0); 61*f69767a1SWei Huang 62*f69767a1SWei Huang pdev->tph_mode = 0; 63*f69767a1SWei Huang pdev->tph_req_type = 0; 64*f69767a1SWei Huang pdev->tph_enabled = 0; 65*f69767a1SWei Huang } 66*f69767a1SWei Huang EXPORT_SYMBOL(pcie_disable_tph); 67*f69767a1SWei Huang 68*f69767a1SWei Huang /** 69*f69767a1SWei Huang * pcie_enable_tph - Enable TPH support for device using a specific ST mode 70*f69767a1SWei Huang * @pdev: PCI device 71*f69767a1SWei Huang * @mode: ST mode to enable. Current supported modes include: 72*f69767a1SWei Huang * 73*f69767a1SWei Huang * - PCI_TPH_ST_NS_MODE: NO ST Mode 74*f69767a1SWei Huang * - PCI_TPH_ST_IV_MODE: Interrupt Vector Mode 75*f69767a1SWei Huang * - PCI_TPH_ST_DS_MODE: Device Specific Mode 76*f69767a1SWei Huang * 77*f69767a1SWei Huang * Check whether the mode is actually supported by the device before enabling 78*f69767a1SWei Huang * and return an error if not. Additionally determine what types of requests, 79*f69767a1SWei Huang * TPH or extended TPH, can be issued by the device based on its TPH requester 80*f69767a1SWei Huang * capability and the Root Port's completer capability. 81*f69767a1SWei Huang * 82*f69767a1SWei Huang * Return: 0 on success, otherwise negative value (-errno) 83*f69767a1SWei Huang */ 84*f69767a1SWei Huang int pcie_enable_tph(struct pci_dev *pdev, int mode) 85*f69767a1SWei Huang { 86*f69767a1SWei Huang u32 reg; 87*f69767a1SWei Huang u8 dev_modes; 88*f69767a1SWei Huang u8 rp_req_type; 89*f69767a1SWei Huang 90*f69767a1SWei Huang /* Honor "notph" kernel parameter */ 91*f69767a1SWei Huang if (pci_tph_disabled) 92*f69767a1SWei Huang return -EINVAL; 93*f69767a1SWei Huang 94*f69767a1SWei Huang if (!pdev->tph_cap) 95*f69767a1SWei Huang return -EINVAL; 96*f69767a1SWei Huang 97*f69767a1SWei Huang if (pdev->tph_enabled) 98*f69767a1SWei Huang return -EBUSY; 99*f69767a1SWei Huang 100*f69767a1SWei Huang /* Sanitize and check ST mode compatibility */ 101*f69767a1SWei Huang mode &= PCI_TPH_CTRL_MODE_SEL_MASK; 102*f69767a1SWei Huang dev_modes = get_st_modes(pdev); 103*f69767a1SWei Huang if (!((1 << mode) & dev_modes)) 104*f69767a1SWei Huang return -EINVAL; 105*f69767a1SWei Huang 106*f69767a1SWei Huang pdev->tph_mode = mode; 107*f69767a1SWei Huang 108*f69767a1SWei Huang /* Get req_type supported by device and its Root Port */ 109*f69767a1SWei Huang pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CAP, ®); 110*f69767a1SWei Huang if (FIELD_GET(PCI_TPH_CAP_EXT_TPH, reg)) 111*f69767a1SWei Huang pdev->tph_req_type = PCI_TPH_REQ_EXT_TPH; 112*f69767a1SWei Huang else 113*f69767a1SWei Huang pdev->tph_req_type = PCI_TPH_REQ_TPH_ONLY; 114*f69767a1SWei Huang 115*f69767a1SWei Huang rp_req_type = get_rp_completer_type(pdev); 116*f69767a1SWei Huang 117*f69767a1SWei Huang /* Final req_type is the smallest value of two */ 118*f69767a1SWei Huang pdev->tph_req_type = min(pdev->tph_req_type, rp_req_type); 119*f69767a1SWei Huang 120*f69767a1SWei Huang if (pdev->tph_req_type == PCI_TPH_REQ_DISABLE) 121*f69767a1SWei Huang return -EINVAL; 122*f69767a1SWei Huang 123*f69767a1SWei Huang /* Write them into TPH control register */ 124*f69767a1SWei Huang pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, ®); 125*f69767a1SWei Huang 126*f69767a1SWei Huang reg &= ~PCI_TPH_CTRL_MODE_SEL_MASK; 127*f69767a1SWei Huang reg |= FIELD_PREP(PCI_TPH_CTRL_MODE_SEL_MASK, pdev->tph_mode); 128*f69767a1SWei Huang 129*f69767a1SWei Huang reg &= ~PCI_TPH_CTRL_REQ_EN_MASK; 130*f69767a1SWei Huang reg |= FIELD_PREP(PCI_TPH_CTRL_REQ_EN_MASK, pdev->tph_req_type); 131*f69767a1SWei Huang 132*f69767a1SWei Huang pci_write_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, reg); 133*f69767a1SWei Huang 134*f69767a1SWei Huang pdev->tph_enabled = 1; 135*f69767a1SWei Huang 136*f69767a1SWei Huang return 0; 137*f69767a1SWei Huang } 138*f69767a1SWei Huang EXPORT_SYMBOL(pcie_enable_tph); 139*f69767a1SWei Huang 140*f69767a1SWei Huang void pci_restore_tph_state(struct pci_dev *pdev) 141*f69767a1SWei Huang { 142*f69767a1SWei Huang struct pci_cap_saved_state *save_state; 143*f69767a1SWei Huang u32 *cap; 144*f69767a1SWei Huang 145*f69767a1SWei Huang if (!pdev->tph_cap) 146*f69767a1SWei Huang return; 147*f69767a1SWei Huang 148*f69767a1SWei Huang if (!pdev->tph_enabled) 149*f69767a1SWei Huang return; 150*f69767a1SWei Huang 151*f69767a1SWei Huang save_state = pci_find_saved_ext_cap(pdev, PCI_EXT_CAP_ID_TPH); 152*f69767a1SWei Huang if (!save_state) 153*f69767a1SWei Huang return; 154*f69767a1SWei Huang 155*f69767a1SWei Huang /* Restore control register and all ST entries */ 156*f69767a1SWei Huang cap = &save_state->cap.data[0]; 157*f69767a1SWei Huang pci_write_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, *cap++); 158*f69767a1SWei Huang } 159*f69767a1SWei Huang 160*f69767a1SWei Huang void pci_save_tph_state(struct pci_dev *pdev) 161*f69767a1SWei Huang { 162*f69767a1SWei Huang struct pci_cap_saved_state *save_state; 163*f69767a1SWei Huang u32 *cap; 164*f69767a1SWei Huang 165*f69767a1SWei Huang if (!pdev->tph_cap) 166*f69767a1SWei Huang return; 167*f69767a1SWei Huang 168*f69767a1SWei Huang if (!pdev->tph_enabled) 169*f69767a1SWei Huang return; 170*f69767a1SWei Huang 171*f69767a1SWei Huang save_state = pci_find_saved_ext_cap(pdev, PCI_EXT_CAP_ID_TPH); 172*f69767a1SWei Huang if (!save_state) 173*f69767a1SWei Huang return; 174*f69767a1SWei Huang 175*f69767a1SWei Huang /* Save control register */ 176*f69767a1SWei Huang cap = &save_state->cap.data[0]; 177*f69767a1SWei Huang pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, cap++); 178*f69767a1SWei Huang } 179*f69767a1SWei Huang 180*f69767a1SWei Huang void pci_no_tph(void) 181*f69767a1SWei Huang { 182*f69767a1SWei Huang pci_tph_disabled = true; 183*f69767a1SWei Huang 184*f69767a1SWei Huang pr_info("PCIe TPH is disabled\n"); 185*f69767a1SWei Huang } 186*f69767a1SWei Huang 187*f69767a1SWei Huang void pci_tph_init(struct pci_dev *pdev) 188*f69767a1SWei Huang { 189*f69767a1SWei Huang u32 save_size; 190*f69767a1SWei Huang 191*f69767a1SWei Huang pdev->tph_cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_TPH); 192*f69767a1SWei Huang if (!pdev->tph_cap) 193*f69767a1SWei Huang return; 194*f69767a1SWei Huang 195*f69767a1SWei Huang save_size = sizeof(u32); 196*f69767a1SWei Huang pci_add_ext_cap_save_buffer(pdev, PCI_EXT_CAP_ID_TPH, save_size); 197*f69767a1SWei Huang } 198