c83c958775
The Common Clock Configuration (CCC) is a PCIe feature for cases where the upstream and downstream device of a link share the same reference clock. After a change in this setting a link re-training is mandatory to make it effective. On recent Intel platforms (tested on Elkhart Lake) the FSP code which is executed before coreboot performs the PCI scan already enumerates all PCI buses for its internal uses. While this is done, all the PCI express features of a link are configured, which includes CCC. If the link supports common clock, FSP performs the link re-training already. When the execution flow is returned to coreboot, the same link treatment is applied again (coded in 'pciexp_tune_dev()') and CCC is enabled a second time, just a few milliseconds after FSP did this already. Because enabling CCC requires a link re-training, there are two link re-trainings on the PCIe link within a few milliseconds (one from the FSP code and one from coreboot) which can lead to issues with a connected PCIe device on this link. In particular, link issues were discovered with a Pericom PCIe switch (PI7C9X2G608) on mc_ehl1 where the link has stalled for a while after the second re-training. This in turn leads to non-initialized PCI devices on the bus after coreboot has finished. This patch checks if CCC is already enabled on a link and does not perform the steps to enable it again in coreboot which safes a link re-training (and thus execution time) and a potential link stability issue. Test=Check log output on mc_ehl1 which shows the following lines: [DEBUG] PCI: pci_scan_bus for bus 09 [DEBUG] PCI: 09:00.0 [8086/1533] enabled [INFO ] PCIe: Common Clock Configuration already enabled Change-Id: I747fa406a120a215de189d7252f160c8ea2e3716 Signed-off-by: Werner Zeh <werner.zeh@siemens.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/73310 Reviewed-by: Subrata Banik <subratabanik@google.com> Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
766 lines
22 KiB
C
766 lines
22 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
|
|
#include <console/console.h>
|
|
#include <commonlib/helpers.h>
|
|
#include <delay.h>
|
|
#include <device/device.h>
|
|
#include <device/pci.h>
|
|
#include <device/pci_ids.h>
|
|
#include <device/pci_ops.h>
|
|
#include <device/pciexp.h>
|
|
|
|
static unsigned int ext_cap_id(unsigned int cap)
|
|
{
|
|
return cap & 0xffff;
|
|
}
|
|
|
|
static unsigned int ext_cap_next_offset(unsigned int cap)
|
|
{
|
|
return cap >> 20 & 0xffc;
|
|
}
|
|
|
|
static unsigned int find_ext_cap_offset(const struct device *dev, unsigned int cap_id,
|
|
unsigned int offset)
|
|
{
|
|
unsigned int this_cap_offset = offset;
|
|
|
|
while (this_cap_offset >= PCIE_EXT_CAP_OFFSET) {
|
|
const unsigned int this_cap = pci_read_config32(dev, this_cap_offset);
|
|
|
|
/* Bail out when this request is unsupported */
|
|
if (this_cap == 0xffffffff)
|
|
break;
|
|
|
|
if (ext_cap_id(this_cap) == cap_id)
|
|
return this_cap_offset;
|
|
|
|
this_cap_offset = ext_cap_next_offset(this_cap);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Search for an extended capability with the ID `cap`.
|
|
*
|
|
* Returns the offset of the first matching extended
|
|
* capability if found, or 0 otherwise.
|
|
*
|
|
* A new search is started with `offset == 0`.
|
|
* To continue a search, the prior return value
|
|
* should be passed as `offset`.
|
|
*/
|
|
unsigned int pciexp_find_extended_cap(const struct device *dev, unsigned int cap,
|
|
unsigned int offset)
|
|
{
|
|
unsigned int next_cap_offset;
|
|
|
|
if (offset)
|
|
next_cap_offset = ext_cap_next_offset(pci_read_config32(dev, offset));
|
|
else
|
|
next_cap_offset = PCIE_EXT_CAP_OFFSET;
|
|
|
|
return find_ext_cap_offset(dev, cap, next_cap_offset);
|
|
}
|
|
|
|
/*
|
|
* Search for a vendor-specific extended capability,
|
|
* with the vendor-specific ID `cap`.
|
|
*
|
|
* Returns the offset of the vendor-specific header,
|
|
* i.e. the offset of the extended capability + 4,
|
|
* or 0 if none is found.
|
|
*
|
|
* A new search is started with `offset == 0`.
|
|
* To continue a search, the prior return value
|
|
* should be passed as `offset`.
|
|
*/
|
|
unsigned int pciexp_find_ext_vendor_cap(const struct device *dev, unsigned int cap,
|
|
unsigned int offset)
|
|
{
|
|
/* Reconstruct capability offset from vendor-specific header offset. */
|
|
if (offset >= 4)
|
|
offset -= 4;
|
|
|
|
for (;;) {
|
|
offset = pciexp_find_extended_cap(dev, PCI_EXT_CAP_ID_VNDR, offset);
|
|
if (!offset)
|
|
return 0;
|
|
|
|
const unsigned int vndr_cap = pci_read_config32(dev, offset + 4);
|
|
if ((vndr_cap & 0xffff) == cap)
|
|
return offset + 4;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find a PCIe device with a given serial number, and a given VID if applicable
|
|
*
|
|
* @param serial The serial number of the device.
|
|
* @param vid Vendor ID of the device, may be 0 if not applicable.
|
|
* @param from Pointer to the device structure, used as a starting point in
|
|
* the linked list of all_devices, which can be 0 to start at the
|
|
* head of the list (i.e. all_devices).
|
|
* @return Pointer to the device struct.
|
|
*/
|
|
struct device *pcie_find_dsn(const uint64_t serial, const uint16_t vid,
|
|
struct device *from)
|
|
{
|
|
union dsn {
|
|
struct {
|
|
uint32_t dsn_low;
|
|
uint32_t dsn_high;
|
|
};
|
|
uint64_t dsn;
|
|
} dsn;
|
|
unsigned int cap;
|
|
uint16_t vendor_id;
|
|
|
|
if (!from)
|
|
from = all_devices;
|
|
else
|
|
from = from->next;
|
|
|
|
while (from) {
|
|
if (from->path.type == DEVICE_PATH_PCI) {
|
|
cap = pciexp_find_extended_cap(from, PCI_EXT_CAP_ID_DSN, 0);
|
|
/*
|
|
* For PCIe device, find extended capability for serial number.
|
|
* The capability header is 4 bytes, followed by lower 4 bytes
|
|
* of serial number, then higher 4 byes of serial number.
|
|
*/
|
|
if (cap != 0) {
|
|
dsn.dsn_low = pci_read_config32(from, cap + 4);
|
|
dsn.dsn_high = pci_read_config32(from, cap + 8);
|
|
vendor_id = pci_read_config16(from, PCI_VENDOR_ID);
|
|
if ((dsn.dsn == serial) && (vid == 0 || vendor_id == vid))
|
|
return from;
|
|
}
|
|
}
|
|
|
|
from = from->next;
|
|
}
|
|
|
|
return from;
|
|
}
|
|
|
|
/*
|
|
* Re-train a PCIe link
|
|
*/
|
|
#define PCIE_TRAIN_RETRY 10000
|
|
static int pciexp_retrain_link(struct device *dev, unsigned int cap)
|
|
{
|
|
unsigned int try;
|
|
u16 lnk;
|
|
|
|
/*
|
|
* Implementation note (page 633) in PCIe Specification 3.0 suggests
|
|
* polling the Link Training bit in the Link Status register until the
|
|
* value returned is 0 before setting the Retrain Link bit to 1.
|
|
* This is meant to avoid a race condition when using the
|
|
* Retrain Link mechanism.
|
|
*/
|
|
for (try = PCIE_TRAIN_RETRY; try > 0; try--) {
|
|
lnk = pci_read_config16(dev, cap + PCI_EXP_LNKSTA);
|
|
if (!(lnk & PCI_EXP_LNKSTA_LT))
|
|
break;
|
|
udelay(100);
|
|
}
|
|
if (try == 0) {
|
|
printk(BIOS_ERR, "%s: Link Retrain timeout\n", dev_path(dev));
|
|
return -1;
|
|
}
|
|
|
|
/* Start link retraining */
|
|
lnk = pci_read_config16(dev, cap + PCI_EXP_LNKCTL);
|
|
lnk |= PCI_EXP_LNKCTL_RL;
|
|
pci_write_config16(dev, cap + PCI_EXP_LNKCTL, lnk);
|
|
|
|
/* Wait for training to complete */
|
|
for (try = PCIE_TRAIN_RETRY; try > 0; try--) {
|
|
lnk = pci_read_config16(dev, cap + PCI_EXP_LNKSTA);
|
|
if (!(lnk & PCI_EXP_LNKSTA_LT))
|
|
return 0;
|
|
udelay(100);
|
|
}
|
|
|
|
printk(BIOS_ERR, "%s: Link Retrain timeout\n", dev_path(dev));
|
|
return -1;
|
|
}
|
|
|
|
static bool pciexp_is_ccc_active(struct device *root, unsigned int root_cap,
|
|
struct device *endp, unsigned int endp_cap)
|
|
{
|
|
u16 root_ccc, endp_ccc;
|
|
|
|
root_ccc = pci_read_config16(root, root_cap + PCI_EXP_LNKCTL) & PCI_EXP_LNKCTL_CCC;
|
|
endp_ccc = pci_read_config16(endp, endp_cap + PCI_EXP_LNKCTL) & PCI_EXP_LNKCTL_CCC;
|
|
if (root_ccc && endp_ccc) {
|
|
printk(BIOS_INFO, "PCIe: Common Clock Configuration already enabled\n");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Check the Slot Clock Configuration for root port and endpoint
|
|
* and enable Common Clock Configuration if possible. If CCC is
|
|
* enabled the link must be retrained.
|
|
*/
|
|
static void pciexp_enable_common_clock(struct device *root, unsigned int root_cap,
|
|
struct device *endp, unsigned int endp_cap)
|
|
{
|
|
u16 root_scc, endp_scc, lnkctl;
|
|
|
|
/* No need to enable common clock if it is already active. */
|
|
if (pciexp_is_ccc_active(root, root_cap, endp, endp_cap))
|
|
return;
|
|
|
|
/* Get Slot Clock Configuration for root port */
|
|
root_scc = pci_read_config16(root, root_cap + PCI_EXP_LNKSTA);
|
|
root_scc &= PCI_EXP_LNKSTA_SLC;
|
|
|
|
/* Get Slot Clock Configuration for endpoint */
|
|
endp_scc = pci_read_config16(endp, endp_cap + PCI_EXP_LNKSTA);
|
|
endp_scc &= PCI_EXP_LNKSTA_SLC;
|
|
|
|
/* Enable Common Clock Configuration and retrain */
|
|
if (root_scc && endp_scc) {
|
|
printk(BIOS_INFO, "Enabling Common Clock Configuration\n");
|
|
|
|
/* Set in endpoint */
|
|
lnkctl = pci_read_config16(endp, endp_cap + PCI_EXP_LNKCTL);
|
|
lnkctl |= PCI_EXP_LNKCTL_CCC;
|
|
pci_write_config16(endp, endp_cap + PCI_EXP_LNKCTL, lnkctl);
|
|
|
|
/* Set in root port */
|
|
lnkctl = pci_read_config16(root, root_cap + PCI_EXP_LNKCTL);
|
|
lnkctl |= PCI_EXP_LNKCTL_CCC;
|
|
pci_write_config16(root, root_cap + PCI_EXP_LNKCTL, lnkctl);
|
|
|
|
/* Retrain link if CCC was enabled */
|
|
pciexp_retrain_link(root, root_cap);
|
|
}
|
|
}
|
|
|
|
static void pciexp_enable_clock_power_pm(struct device *endp, unsigned int endp_cap)
|
|
{
|
|
/* check if per port clk req is supported in device */
|
|
u32 endp_ca;
|
|
u16 lnkctl;
|
|
endp_ca = pci_read_config32(endp, endp_cap + PCI_EXP_LNKCAP);
|
|
if ((endp_ca & PCI_EXP_CLK_PM) == 0) {
|
|
printk(BIOS_INFO, "PCIE CLK PM is not supported by endpoint\n");
|
|
return;
|
|
}
|
|
lnkctl = pci_read_config16(endp, endp_cap + PCI_EXP_LNKCTL);
|
|
lnkctl = lnkctl | PCI_EXP_EN_CLK_PM;
|
|
pci_write_config16(endp, endp_cap + PCI_EXP_LNKCTL, lnkctl);
|
|
}
|
|
|
|
static bool _pciexp_ltr_supported(struct device *dev, unsigned int cap)
|
|
{
|
|
return pci_read_config16(dev, cap + PCI_EXP_DEVCAP2) & PCI_EXP_DEVCAP2_LTR;
|
|
}
|
|
|
|
static bool _pciexp_ltr_enabled(struct device *dev, unsigned int cap)
|
|
{
|
|
return pci_read_config16(dev, cap + PCI_EXP_DEVCTL2) & PCI_EXP_DEV2_LTR;
|
|
}
|
|
|
|
static bool _pciexp_enable_ltr(struct device *parent, unsigned int parent_cap,
|
|
struct device *dev, unsigned int cap)
|
|
{
|
|
if (!_pciexp_ltr_supported(dev, cap)) {
|
|
printk(BIOS_DEBUG, "%s: No LTR support\n", dev_path(dev));
|
|
return false;
|
|
}
|
|
|
|
if (_pciexp_ltr_enabled(dev, cap))
|
|
return true;
|
|
|
|
if (parent &&
|
|
(!_pciexp_ltr_supported(parent, parent_cap) ||
|
|
!_pciexp_ltr_enabled(parent, parent_cap)))
|
|
return false;
|
|
|
|
pci_or_config16(dev, cap + PCI_EXP_DEVCTL2, PCI_EXP_DEV2_LTR);
|
|
printk(BIOS_INFO, "%s: Enabled LTR\n", dev_path(dev));
|
|
return true;
|
|
}
|
|
|
|
static void pciexp_enable_ltr(struct device *dev)
|
|
{
|
|
const unsigned int cap = pci_find_capability(dev, PCI_CAP_ID_PCIE);
|
|
if (!cap)
|
|
return;
|
|
|
|
/*
|
|
* If we have get_ltr_max_latencies(), treat `dev` as the root.
|
|
* If not, let _pciexp_enable_ltr() query the parent's state.
|
|
*/
|
|
struct device *parent = NULL;
|
|
unsigned int parent_cap = 0;
|
|
if (!dev->ops->ops_pci || !dev->ops->ops_pci->get_ltr_max_latencies) {
|
|
parent = dev->bus->dev;
|
|
if (parent->path.type != DEVICE_PATH_PCI)
|
|
return;
|
|
parent_cap = pci_find_capability(parent, PCI_CAP_ID_PCIE);
|
|
if (!parent_cap)
|
|
return;
|
|
}
|
|
|
|
(void)_pciexp_enable_ltr(parent, parent_cap, dev, cap);
|
|
}
|
|
|
|
bool pciexp_get_ltr_max_latencies(struct device *dev, u16 *max_snoop, u16 *max_nosnoop)
|
|
{
|
|
/* Walk the hierarchy up to find get_ltr_max_latencies(). */
|
|
do {
|
|
if (dev->ops->ops_pci && dev->ops->ops_pci->get_ltr_max_latencies)
|
|
break;
|
|
if (dev->bus->dev == dev || dev->bus->dev->path.type != DEVICE_PATH_PCI)
|
|
return false;
|
|
dev = dev->bus->dev;
|
|
} while (true);
|
|
|
|
dev->ops->ops_pci->get_ltr_max_latencies(max_snoop, max_nosnoop);
|
|
return true;
|
|
}
|
|
|
|
static void pciexp_configure_ltr(struct device *parent, unsigned int parent_cap,
|
|
struct device *dev, unsigned int cap)
|
|
{
|
|
if (!_pciexp_enable_ltr(parent, parent_cap, dev, cap))
|
|
return;
|
|
|
|
const unsigned int ltr_cap = pciexp_find_extended_cap(dev, PCIE_EXT_CAP_LTR_ID, 0);
|
|
if (!ltr_cap)
|
|
return;
|
|
|
|
u16 max_snoop, max_nosnoop;
|
|
if (!pciexp_get_ltr_max_latencies(dev, &max_snoop, &max_nosnoop))
|
|
return;
|
|
|
|
pci_write_config16(dev, ltr_cap + PCI_LTR_MAX_SNOOP, max_snoop);
|
|
pci_write_config16(dev, ltr_cap + PCI_LTR_MAX_NOSNOOP, max_nosnoop);
|
|
printk(BIOS_INFO, "%s: Programmed LTR max latencies\n", dev_path(dev));
|
|
}
|
|
|
|
static unsigned char pciexp_L1_substate_cal(struct device *dev, unsigned int endp_cap,
|
|
unsigned int *data)
|
|
{
|
|
unsigned char mult[4] = {2, 10, 100, 0};
|
|
|
|
unsigned int L1SubStateSupport = *data & 0xf;
|
|
unsigned int comm_mode_rst_time = (*data >> 8) & 0xff;
|
|
unsigned int power_on_scale = (*data >> 16) & 0x3;
|
|
unsigned int power_on_value = (*data >> 19) & 0x1f;
|
|
|
|
unsigned int endp_data = pci_read_config32(dev, endp_cap + 4);
|
|
unsigned int endp_L1SubStateSupport = endp_data & 0xf;
|
|
unsigned int endp_comm_mode_restore_time = (endp_data >> 8) & 0xff;
|
|
unsigned int endp_power_on_scale = (endp_data >> 16) & 0x3;
|
|
unsigned int endp_power_on_value = (endp_data >> 19) & 0x1f;
|
|
|
|
L1SubStateSupport &= endp_L1SubStateSupport;
|
|
|
|
if (L1SubStateSupport == 0)
|
|
return 0;
|
|
|
|
if (power_on_value * mult[power_on_scale] <
|
|
endp_power_on_value * mult[endp_power_on_scale]) {
|
|
power_on_value = endp_power_on_value;
|
|
power_on_scale = endp_power_on_scale;
|
|
}
|
|
if (comm_mode_rst_time < endp_comm_mode_restore_time)
|
|
comm_mode_rst_time = endp_comm_mode_restore_time;
|
|
|
|
*data = (comm_mode_rst_time << 8) | (power_on_scale << 16)
|
|
| (power_on_value << 19) | L1SubStateSupport;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void pciexp_L1_substate_commit(struct device *root, struct device *dev,
|
|
unsigned int root_cap, unsigned int end_cap)
|
|
{
|
|
struct device *dev_t;
|
|
unsigned char L1_ss_ok;
|
|
unsigned int rp_L1_support = pci_read_config32(root, root_cap + 4);
|
|
unsigned int L1SubStateSupport;
|
|
unsigned int comm_mode_rst_time;
|
|
unsigned int power_on_scale;
|
|
unsigned int endp_power_on_value;
|
|
|
|
for (dev_t = dev; dev_t; dev_t = dev_t->sibling) {
|
|
/*
|
|
* rp_L1_support is init'd above from root port.
|
|
* it needs coordination with endpoints to reach in common.
|
|
* if certain endpoint doesn't support L1 Sub-State, abort
|
|
* this feature enabling.
|
|
*/
|
|
L1_ss_ok = pciexp_L1_substate_cal(dev_t, end_cap,
|
|
&rp_L1_support);
|
|
if (!L1_ss_ok)
|
|
return;
|
|
}
|
|
|
|
L1SubStateSupport = rp_L1_support & 0xf;
|
|
comm_mode_rst_time = (rp_L1_support >> 8) & 0xff;
|
|
power_on_scale = (rp_L1_support >> 16) & 0x3;
|
|
endp_power_on_value = (rp_L1_support >> 19) & 0x1f;
|
|
|
|
printk(BIOS_INFO, "L1 Sub-State supported from root port %d\n",
|
|
root->path.pci.devfn >> 3);
|
|
printk(BIOS_INFO, "L1 Sub-State Support = 0x%x\n", L1SubStateSupport);
|
|
printk(BIOS_INFO, "CommonModeRestoreTime = 0x%x\n", comm_mode_rst_time);
|
|
printk(BIOS_INFO, "Power On Value = 0x%x, Power On Scale = 0x%x\n",
|
|
endp_power_on_value, power_on_scale);
|
|
|
|
pci_update_config32(root, root_cap + 0x08, ~0xff00,
|
|
(comm_mode_rst_time << 8));
|
|
|
|
pci_update_config32(root, root_cap + 0x0c, 0xffffff04,
|
|
(endp_power_on_value << 3) | (power_on_scale));
|
|
|
|
/* TODO: 0xa0, 2 are values that work on some chipsets but really
|
|
* should be determined dynamically by looking at downstream devices.
|
|
*/
|
|
pci_update_config32(root, root_cap + 0x08,
|
|
~(ASPM_LTR_L12_THRESHOLD_VALUE_MASK |
|
|
ASPM_LTR_L12_THRESHOLD_SCALE_MASK),
|
|
(0xa0 << ASPM_LTR_L12_THRESHOLD_VALUE_OFFSET) |
|
|
(2 << ASPM_LTR_L12_THRESHOLD_SCALE_OFFSET));
|
|
|
|
pci_update_config32(root, root_cap + 0x08, ~0x1f,
|
|
L1SubStateSupport);
|
|
|
|
for (dev_t = dev; dev_t; dev_t = dev_t->sibling) {
|
|
pci_update_config32(dev_t, end_cap + 0x0c, 0xffffff04,
|
|
(endp_power_on_value << 3) | (power_on_scale));
|
|
|
|
pci_update_config32(dev_t, end_cap + 0x08,
|
|
~(ASPM_LTR_L12_THRESHOLD_VALUE_MASK |
|
|
ASPM_LTR_L12_THRESHOLD_SCALE_MASK),
|
|
(0xa0 << ASPM_LTR_L12_THRESHOLD_VALUE_OFFSET) |
|
|
(2 << ASPM_LTR_L12_THRESHOLD_SCALE_OFFSET));
|
|
|
|
pci_update_config32(dev_t, end_cap + 0x08, ~0x1f,
|
|
L1SubStateSupport);
|
|
}
|
|
}
|
|
|
|
static void pciexp_config_L1_sub_state(struct device *root, struct device *dev)
|
|
{
|
|
unsigned int root_cap, end_cap;
|
|
|
|
/* Do it for function 0 only */
|
|
if (dev->path.pci.devfn & 0x7)
|
|
return;
|
|
|
|
root_cap = pciexp_find_extended_cap(root, PCIE_EXT_CAP_L1SS_ID, 0);
|
|
if (!root_cap)
|
|
return;
|
|
|
|
end_cap = pciexp_find_extended_cap(dev, PCIE_EXT_CAP_L1SS_ID, 0);
|
|
if (!end_cap) {
|
|
if (dev->vendor != PCI_VID_INTEL)
|
|
return;
|
|
|
|
end_cap = pciexp_find_ext_vendor_cap(dev, 0xcafe, 0);
|
|
if (!end_cap)
|
|
return;
|
|
}
|
|
|
|
pciexp_L1_substate_commit(root, dev, root_cap, end_cap);
|
|
}
|
|
|
|
/*
|
|
* Determine the ASPM L0s or L1 exit latency for a link
|
|
* by checking both root port and endpoint and returning
|
|
* the highest latency value.
|
|
*/
|
|
static int pciexp_aspm_latency(struct device *root, unsigned int root_cap,
|
|
struct device *endp, unsigned int endp_cap,
|
|
enum aspm_type type)
|
|
{
|
|
int root_lat = 0, endp_lat = 0;
|
|
u32 root_lnkcap, endp_lnkcap;
|
|
|
|
root_lnkcap = pci_read_config32(root, root_cap + PCI_EXP_LNKCAP);
|
|
endp_lnkcap = pci_read_config32(endp, endp_cap + PCI_EXP_LNKCAP);
|
|
|
|
/* Make sure the link supports this ASPM type by checking
|
|
* capability bits 11:10 with aspm_type offset by 1 */
|
|
if (!(root_lnkcap & (1 << (type + 9))) ||
|
|
!(endp_lnkcap & (1 << (type + 9))))
|
|
return -1;
|
|
|
|
/* Find the one with higher latency */
|
|
switch (type) {
|
|
case PCIE_ASPM_L0S:
|
|
root_lat = (root_lnkcap & PCI_EXP_LNKCAP_L0SEL) >> 12;
|
|
endp_lat = (endp_lnkcap & PCI_EXP_LNKCAP_L0SEL) >> 12;
|
|
break;
|
|
case PCIE_ASPM_L1:
|
|
root_lat = (root_lnkcap & PCI_EXP_LNKCAP_L1EL) >> 15;
|
|
endp_lat = (endp_lnkcap & PCI_EXP_LNKCAP_L1EL) >> 15;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return (endp_lat > root_lat) ? endp_lat : root_lat;
|
|
}
|
|
|
|
/*
|
|
* Enable ASPM on PCIe root port and endpoint.
|
|
*/
|
|
static void pciexp_enable_aspm(struct device *root, unsigned int root_cap,
|
|
struct device *endp, unsigned int endp_cap)
|
|
{
|
|
const char *aspm_type_str[] = { "None", "L0s", "L1", "L0s and L1" };
|
|
enum aspm_type apmc = PCIE_ASPM_NONE;
|
|
int exit_latency, ok_latency;
|
|
u16 lnkctl;
|
|
u32 devcap;
|
|
|
|
if (endp->disable_pcie_aspm)
|
|
return;
|
|
|
|
/* Get endpoint device capabilities for acceptable limits */
|
|
devcap = pci_read_config32(endp, endp_cap + PCI_EXP_DEVCAP);
|
|
|
|
/* Enable L0s if it is within endpoint acceptable limit */
|
|
ok_latency = (devcap & PCI_EXP_DEVCAP_L0S) >> 6;
|
|
exit_latency = pciexp_aspm_latency(root, root_cap, endp, endp_cap,
|
|
PCIE_ASPM_L0S);
|
|
if (exit_latency >= 0 && exit_latency <= ok_latency)
|
|
apmc |= PCIE_ASPM_L0S;
|
|
|
|
/* Enable L1 if it is within endpoint acceptable limit */
|
|
ok_latency = (devcap & PCI_EXP_DEVCAP_L1) >> 9;
|
|
exit_latency = pciexp_aspm_latency(root, root_cap, endp, endp_cap,
|
|
PCIE_ASPM_L1);
|
|
if (exit_latency >= 0 && exit_latency <= ok_latency)
|
|
apmc |= PCIE_ASPM_L1;
|
|
|
|
if (apmc != PCIE_ASPM_NONE) {
|
|
/* Set APMC in root port first */
|
|
lnkctl = pci_read_config16(root, root_cap + PCI_EXP_LNKCTL);
|
|
lnkctl |= apmc;
|
|
pci_write_config16(root, root_cap + PCI_EXP_LNKCTL, lnkctl);
|
|
|
|
/* Set APMC in endpoint device next */
|
|
lnkctl = pci_read_config16(endp, endp_cap + PCI_EXP_LNKCTL);
|
|
lnkctl |= apmc;
|
|
pci_write_config16(endp, endp_cap + PCI_EXP_LNKCTL, lnkctl);
|
|
}
|
|
|
|
printk(BIOS_INFO, "ASPM: Enabled %s\n", aspm_type_str[apmc]);
|
|
}
|
|
|
|
/*
|
|
* Set max payload size of endpoint in accordance with max payload size of root port.
|
|
*/
|
|
static void pciexp_set_max_payload_size(struct device *root, unsigned int root_cap,
|
|
struct device *endp, unsigned int endp_cap)
|
|
{
|
|
unsigned int endp_max_payload, root_max_payload, max_payload;
|
|
u16 endp_devctl, root_devctl;
|
|
u32 endp_devcap, root_devcap;
|
|
|
|
/* Get max payload size supported by endpoint */
|
|
endp_devcap = pci_read_config32(endp, endp_cap + PCI_EXP_DEVCAP);
|
|
endp_max_payload = endp_devcap & PCI_EXP_DEVCAP_PAYLOAD;
|
|
|
|
/* Get max payload size supported by root port */
|
|
root_devcap = pci_read_config32(root, root_cap + PCI_EXP_DEVCAP);
|
|
root_max_payload = root_devcap & PCI_EXP_DEVCAP_PAYLOAD;
|
|
|
|
/* Set max payload to smaller of the reported device capability. */
|
|
max_payload = MIN(endp_max_payload, root_max_payload);
|
|
if (max_payload > 5) {
|
|
/* Values 6 and 7 are reserved in PCIe 3.0 specs. */
|
|
printk(BIOS_ERR, "PCIe: Max_Payload_Size field restricted from %d to 5\n",
|
|
max_payload);
|
|
max_payload = 5;
|
|
}
|
|
|
|
endp_devctl = pci_read_config16(endp, endp_cap + PCI_EXP_DEVCTL);
|
|
endp_devctl &= ~PCI_EXP_DEVCTL_PAYLOAD;
|
|
endp_devctl |= max_payload << 5;
|
|
pci_write_config16(endp, endp_cap + PCI_EXP_DEVCTL, endp_devctl);
|
|
|
|
root_devctl = pci_read_config16(root, root_cap + PCI_EXP_DEVCTL);
|
|
root_devctl &= ~PCI_EXP_DEVCTL_PAYLOAD;
|
|
root_devctl |= max_payload << 5;
|
|
pci_write_config16(root, root_cap + PCI_EXP_DEVCTL, root_devctl);
|
|
|
|
printk(BIOS_INFO, "PCIe: Max_Payload_Size adjusted to %d\n", (1 << (max_payload + 7)));
|
|
}
|
|
|
|
/*
|
|
* Clear Lane Error State at the end of PCIe link training.
|
|
* Lane error status is cleared if PCIEXP_LANE_ERR_STAT_CLEAR is set.
|
|
* Lane error is normal during link training, so we need to clear it.
|
|
* At this moment, link has been used, but for a very short duration.
|
|
*/
|
|
static void clear_lane_error_status(struct device *dev)
|
|
{
|
|
u32 reg32;
|
|
u16 pos;
|
|
|
|
pos = pciexp_find_extended_cap(dev, PCI_EXP_SEC_CAP_ID, 0);
|
|
if (pos == 0)
|
|
return;
|
|
|
|
reg32 = pci_read_config32(dev, pos + PCI_EXP_SEC_LANE_ERR_STATUS);
|
|
if (reg32 == 0)
|
|
return;
|
|
|
|
printk(BIOS_DEBUG, "%s: Clear Lane Error Status.\n", dev_path(dev));
|
|
printk(BIOS_DEBUG, "LaneErrStat:0x%x\n", reg32);
|
|
pci_write_config32(dev, pos + PCI_EXP_SEC_LANE_ERR_STATUS, reg32);
|
|
}
|
|
|
|
static void pciexp_tune_dev(struct device *dev)
|
|
{
|
|
struct device *root = dev->bus->dev;
|
|
unsigned int root_cap, cap;
|
|
|
|
cap = pci_find_capability(dev, PCI_CAP_ID_PCIE);
|
|
if (!cap)
|
|
return;
|
|
|
|
root_cap = pci_find_capability(root, PCI_CAP_ID_PCIE);
|
|
if (!root_cap)
|
|
return;
|
|
|
|
/* Check for and enable Common Clock */
|
|
if (CONFIG(PCIEXP_COMMON_CLOCK))
|
|
pciexp_enable_common_clock(root, root_cap, dev, cap);
|
|
|
|
/* Check if per port CLK req is supported by endpoint*/
|
|
if (CONFIG(PCIEXP_CLK_PM))
|
|
pciexp_enable_clock_power_pm(dev, cap);
|
|
|
|
/* Enable L1 Sub-State when both root port and endpoint support */
|
|
if (CONFIG(PCIEXP_L1_SUB_STATE))
|
|
pciexp_config_L1_sub_state(root, dev);
|
|
|
|
/* Check for and enable ASPM */
|
|
if (CONFIG(PCIEXP_ASPM))
|
|
pciexp_enable_aspm(root, root_cap, dev, cap);
|
|
|
|
/* Clear PCIe Lane Error Status */
|
|
if (CONFIG(PCIEXP_LANE_ERR_STAT_CLEAR))
|
|
clear_lane_error_status(root);
|
|
|
|
/* Adjust Max_Payload_Size of link ends. */
|
|
pciexp_set_max_payload_size(root, root_cap, dev, cap);
|
|
|
|
pciexp_configure_ltr(root, root_cap, dev, cap);
|
|
}
|
|
|
|
void pciexp_scan_bus(struct bus *bus, unsigned int min_devfn,
|
|
unsigned int max_devfn)
|
|
{
|
|
struct device *child;
|
|
|
|
pciexp_enable_ltr(bus->dev);
|
|
|
|
pci_scan_bus(bus, min_devfn, max_devfn);
|
|
|
|
for (child = bus->children; child; child = child->sibling) {
|
|
if (child->path.type != DEVICE_PATH_PCI)
|
|
continue;
|
|
if ((child->path.pci.devfn < min_devfn) ||
|
|
(child->path.pci.devfn > max_devfn)) {
|
|
continue;
|
|
}
|
|
pciexp_tune_dev(child);
|
|
}
|
|
}
|
|
|
|
void pciexp_scan_bridge(struct device *dev)
|
|
{
|
|
do_pci_scan_bridge(dev, pciexp_scan_bus);
|
|
}
|
|
|
|
/** Default device operations for PCI Express bridges */
|
|
static struct pci_operations pciexp_bus_ops_pci = {
|
|
.set_subsystem = 0,
|
|
};
|
|
|
|
struct device_operations default_pciexp_ops_bus = {
|
|
.read_resources = pci_bus_read_resources,
|
|
.set_resources = pci_dev_set_resources,
|
|
.enable_resources = pci_bus_enable_resources,
|
|
.scan_bus = pciexp_scan_bridge,
|
|
.reset_bus = pci_bus_reset,
|
|
.ops_pci = &pciexp_bus_ops_pci,
|
|
};
|
|
|
|
static void pciexp_hotplug_dummy_read_resources(struct device *dev)
|
|
{
|
|
struct resource *resource;
|
|
|
|
/* Add extra memory space */
|
|
resource = new_resource(dev, 0x10);
|
|
resource->size = CONFIG_PCIEXP_HOTPLUG_MEM;
|
|
resource->align = 12;
|
|
resource->gran = 12;
|
|
resource->limit = 0xffffffff;
|
|
resource->flags |= IORESOURCE_MEM;
|
|
|
|
/* Add extra prefetchable memory space */
|
|
resource = new_resource(dev, 0x14);
|
|
resource->size = CONFIG_PCIEXP_HOTPLUG_PREFETCH_MEM;
|
|
resource->align = 12;
|
|
resource->gran = 12;
|
|
resource->limit = 0xffffffffffffffff;
|
|
resource->flags |= IORESOURCE_MEM | IORESOURCE_PREFETCH;
|
|
|
|
/* Set resource flag requesting allocation above 4G boundary. */
|
|
if (CONFIG(PCIEXP_HOTPLUG_PREFETCH_MEM_ABOVE_4G))
|
|
resource->flags |= IORESOURCE_ABOVE_4G;
|
|
|
|
/* Add extra I/O space */
|
|
resource = new_resource(dev, 0x18);
|
|
resource->size = CONFIG_PCIEXP_HOTPLUG_IO;
|
|
resource->align = 12;
|
|
resource->gran = 12;
|
|
resource->limit = 0xffff;
|
|
resource->flags |= IORESOURCE_IO;
|
|
}
|
|
|
|
static struct device_operations pciexp_hotplug_dummy_ops = {
|
|
.read_resources = pciexp_hotplug_dummy_read_resources,
|
|
.set_resources = noop_set_resources,
|
|
};
|
|
|
|
void pciexp_hotplug_scan_bridge(struct device *dev)
|
|
{
|
|
dev->hotplug_port = 1;
|
|
dev->hotplug_buses = CONFIG_PCIEXP_HOTPLUG_BUSES;
|
|
|
|
/* Normal PCIe Scan */
|
|
pciexp_scan_bridge(dev);
|
|
|
|
/* Add dummy slot to preserve resources, must happen after bus scan */
|
|
struct device *dummy;
|
|
struct device_path dummy_path = { .type = DEVICE_PATH_NONE };
|
|
dummy = alloc_dev(dev->link_list, &dummy_path);
|
|
dummy->ops = &pciexp_hotplug_dummy_ops;
|
|
}
|
|
|
|
struct device_operations default_pciexp_hotplug_ops_bus = {
|
|
.read_resources = pci_bus_read_resources,
|
|
.set_resources = pci_dev_set_resources,
|
|
.enable_resources = pci_bus_enable_resources,
|
|
.scan_bus = pciexp_hotplug_scan_bridge,
|
|
.reset_bus = pci_bus_reset,
|
|
.ops_pci = &pciexp_bus_ops_pci,
|
|
};
|