diff --git a/src/device/Kconfig b/src/device/Kconfig index 7f43888838..8ddb58cef0 100644 --- a/src/device/Kconfig +++ b/src/device/Kconfig @@ -271,6 +271,14 @@ config EARLY_PCI_BRIDGE This option enables static configuration for a single pre-defined PCI bridge function on bus 0. +config PCIEXP_L1_SUB_STATE + prompt "Enable PCIe ASPM L1 SubState" + bool + depends on PCIEXP_PLUGIN_SUPPORT && MMCONF_SUPPORT + default n + help + Detect and enable ASPM on PCIe links. + if EARLY_PCI_BRIDGE config EARLY_PCI_BRIDGE_DEVICE diff --git a/src/device/pciexp_device.c b/src/device/pciexp_device.c index edb103d861..2bc991cd4b 100644 --- a/src/device/pciexp_device.c +++ b/src/device/pciexp_device.c @@ -25,6 +25,31 @@ #include #include +#if IS_ENABLED(CONFIG_MMCONF_SUPPORT) +unsigned int pciexp_find_extended_cap(device_t dev, unsigned int cap) +{ + unsigned int this_cap_offset, next_cap_offset; + unsigned int this_cap, cafe; + + this_cap_offset = PCIE_EXT_CAP_OFFSET; + do { + this_cap = pci_mmio_read_config32(dev, this_cap_offset); + next_cap_offset = this_cap >> 20; + this_cap &= 0xffff; + cafe = pci_mmio_read_config32(dev, this_cap_offset + 4); + cafe &= 0xffff; + if (this_cap == cap) + return this_cap_offset; + else if (cafe == cap) + return this_cap_offset + 4; + else + this_cap_offset = next_cap_offset; + } while (next_cap_offset != 0); + + return 0; +} +#endif + #if CONFIG_PCIEXP_COMMON_CLOCK /* * Re-train a PCIe link @@ -107,6 +132,160 @@ static void pciexp_enable_clock_power_pm(device_t endp, unsigned endp_cap) } #endif /* CONFIG_PCIEXP_CLK_PM */ +#if IS_ENABLED(CONFIG_PCIEXP_L1_SUB_STATE) && IS_ENABLED(CONFIG_MMCONF_SUPPORT) +static void pcie_update_cfg(device_t dev, int reg, u32 mask, u32 or) +{ + u32 reg32; + + reg32 = pci_mmio_read_config32(dev, reg); + reg32 &= mask; + reg32 |= or; + pci_mmio_write_config32(dev, reg, reg32); +} + +static void pciexp_config_max_latency(device_t root, device_t dev) +{ + unsigned int cap; + cap = pciexp_find_extended_cap(dev, PCIE_EXT_CAP_LTR_ID); + if (root->ops->ops_pci->set_L1_ss_latency != NULL) + root->ops->ops_pci->set_L1_ss_latency(dev, cap + 4); +} + +static void pciexp_enable_ltr(device_t dev) +{ + unsigned int cap; + cap = pci_find_capability(dev, PCI_CAP_ID_PCIE); + + pcie_update_cfg(dev, cap + 0x28, ~(1 << 10), 1 << 10); +} + +static unsigned char pciexp_L1_substate_cal(device_t 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_mmio_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(device_t root, device_t dev, + unsigned int root_cap, unsigned int end_cap) +{ + device_t dev_t; + unsigned char L1_ss_ok; + unsigned int rp_L1_support = pci_mmio_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); + + pciexp_enable_ltr(root); + + pcie_update_cfg(root, root_cap + 0x08, ~0xff00, + (comm_mode_rst_time << 8)); + + pcie_update_cfg(root, root_cap + 0x0c , 0xffffff04, + (endp_power_on_value << 3) | (power_on_scale)); + + pcie_update_cfg(root, root_cap + 0x08, ~0xe3ff0000, + (1 << 21) | (1 << 23) | (1 << 30)); + + pcie_update_cfg(root, root_cap + 0x08, ~0xf, + L1SubStateSupport); + + for (dev_t = dev; dev_t; dev_t = dev_t->sibling) { + pcie_update_cfg(dev_t, end_cap + 0x08, ~0xff00, + (comm_mode_rst_time << 8)); + + pcie_update_cfg(dev_t, end_cap + 0x0c , 0xffffff04, + (endp_power_on_value << 3) | (power_on_scale)); + + pcie_update_cfg(dev_t, end_cap + 0x08, ~0xe3ff0000, + (1 << 21) | (1 << 23) | (1 << 30)); + + pcie_update_cfg(dev_t, end_cap + 0x08, ~0xf, + L1SubStateSupport); + + pciexp_enable_ltr(dev_t); + + pciexp_config_max_latency(root, dev_t); + } +} + +static void pciexp_config_L1_sub_state(device_t root, device_t 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); + if (!root_cap) + return; + + end_cap = pciexp_find_extended_cap(dev, PCIE_EXT_CAP_L1SS_ID); + if (!end_cap) { + end_cap = pciexp_find_extended_cap(dev, 0xcafe); + if (!end_cap) + return; + } + + pciexp_L1_substate_commit(root, dev, root_cap, end_cap); +} +#endif /* CONFIG_PCIEXP_L1_SUB_STATE */ + #if CONFIG_PCIEXP_ASPM /* * Determine the ASPM L0s or L1 exit latency for a link @@ -222,6 +401,11 @@ static void pciexp_tune_dev(device_t dev) pciexp_enable_clock_power_pm(dev, cap); #endif +#if CONFIG_PCIEXP_L1_SUB_STATE + /* Enable L1 Sub-State when both root port and endpoint support */ + pciexp_config_L1_sub_state(root, dev); +#endif /* CONFIG_PCIEXP_L1_SUB_STATE */ + #if CONFIG_PCIEXP_ASPM /* Check for and enable ASPM */ enum aspm_type apmc = pciexp_enable_aspm(root, root_cap, dev, cap); diff --git a/src/include/device/pci.h b/src/include/device/pci.h index 0670da4822..4e712f9f7b 100644 --- a/src/include/device/pci.h +++ b/src/include/device/pci.h @@ -33,6 +33,7 @@ struct pci_operations { /* set the Subsystem IDs for the PCI device */ void (*set_subsystem)(device_t dev, unsigned vendor, unsigned device); + void (*set_L1_ss_latency)(device_t dev, unsigned int off); }; /* Common pci bus operations */ diff --git a/src/include/device/pci_def.h b/src/include/device/pci_def.h index 403978584d..c49e4ebf36 100644 --- a/src/include/device/pci_def.h +++ b/src/include/device/pci_def.h @@ -405,6 +405,12 @@ #define PCI_EXT_CAP_ID_DSN 3 #define PCI_EXT_CAP_ID_PWR 4 +/* Extended Capability lists*/ +#define PCIE_EXT_CAP_OFFSET 0x100 +#define PCIE_EXT_CAP_AER_ID 0x0001 +#define PCIE_EXT_CAP_L1SS_ID 0x001E +#define PCIE_EXT_CAP_LTR_ID 0x0018 + /* Advanced Error Reporting */ #define PCI_ERR_UNCOR_STATUS 4 /* Uncorrectable Error Status */ #define PCI_ERR_UNC_TRAIN 0x00000001 /* Training */ diff --git a/src/include/device/pciexp.h b/src/include/device/pciexp.h index 87a5002c5e..1146557858 100644 --- a/src/include/device/pciexp.h +++ b/src/include/device/pciexp.h @@ -15,4 +15,5 @@ unsigned int pciexp_scan_bridge(device_t dev, unsigned int max); extern struct device_operations default_pciexp_ops_bus; +unsigned int pciexp_find_extended_cap(device_t dev, unsigned int cap); #endif /* DEVICE_PCIEXP_H */