diff --git a/src/devices/Kconfig b/src/devices/Kconfig index a731f44aad..5168819519 100644 --- a/src/devices/Kconfig +++ b/src/devices/Kconfig @@ -163,3 +163,17 @@ config AGP_PLUGIN_SUPPORT config CARDBUS_PLUGIN_SUPPORT bool default y + +config PCIEXP_COMMON_CLOCK + prompt "Enable PCIe Common Clock" + bool + default n + help + Detect and enable Common Clock on PCIe links. + +config PCIEXP_ASPM + prompt "Enable PCIe ASPM" + bool + default n + help + Detect and enable ASPM on PCIe links. diff --git a/src/devices/pciexp_device.c b/src/devices/pciexp_device.c index 5d339423ff..36f3e6a455 100644 --- a/src/devices/pciexp_device.c +++ b/src/devices/pciexp_device.c @@ -19,31 +19,197 @@ */ #include +#include #include #include #include #include +#if CONFIG_PCIEXP_COMMON_CLOCK +/* + * Re-train a PCIe link + */ +#define PCIE_TRAIN_RETRY 10000 +static int pciexp_retrain_link(device_t dev, unsigned cap) +{ + unsigned try = PCIE_TRAIN_RETRY; + u16 lnk; + + /* 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 */ + while (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; +} + +/* + * 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(device_t root, unsigned root_cap, + device_t endp, unsigned endp_cap) +{ + u16 root_scc, endp_scc, lnkctl; + + /* 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); + } +} +#endif /* CONFIG_PCIEXP_COMMON_CLOCK */ + +#if CONFIG_PCIEXP_ASPM +/* + * 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(device_t root, unsigned root_cap, + device_t endp, unsigned 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. + * + * Returns APMC value: + * -1 = Error + * 0 = no ASPM + * 1 = L0s Enabled + * 2 = L1 Enabled + * 3 = L0s and L1 Enabled + */ +static enum aspm_type pciexp_enable_aspm(device_t root, unsigned root_cap, + device_t endp, unsigned 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; + + /* 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]); + return apmc; +} +#endif /* CONFIG_PCIEXP_ASPM */ + static void pciexp_tune_dev(device_t dev) { - unsigned int cap; -#if CONFIG_PCIE_TUNING - u32 reg32; -#endif + device_t root = dev->bus->dev; + unsigned int root_cap, cap; cap = pci_find_capability(dev, PCI_CAP_ID_PCIE); if (!cap) return; -#if CONFIG_PCIE_TUNING - printk(BIOS_DEBUG, "PCIe: tuning %s\n", dev_path(dev)); + root_cap = pci_find_capability(root, PCI_CAP_ID_PCIE); + if (!root_cap) + return; - // TODO make this depending on ASPM. +#if CONFIG_PCIEXP_COMMON_CLOCK + /* Check for and enable Common Clock */ + pciexp_enable_common_clock(root, root_cap, dev, cap); +#endif - /* Enable ASPM role based error reporting. */ - reg32 = pci_read_config32(dev, cap + PCI_EXP_DEVCAP); - reg32 |= PCI_EXP_DEVCAP_RBER; - pci_write_config32(dev, cap + PCI_EXP_DEVCAP, reg32); +#if CONFIG_PCIEXP_ASPM + /* Check for and enable ASPM */ + enum aspm_type apmc = pciexp_enable_aspm(root, root_cap, dev, cap); + + if (apmc != PCIE_ASPM_NONE) { + /* Enable ASPM role based error reporting. */ + u32 reg32 = pci_read_config32(dev, cap + PCI_EXP_DEVCAP); + reg32 |= PCI_EXP_DEVCAP_RBER; + pci_write_config32(dev, cap + PCI_EXP_DEVCAP, reg32); + } #endif } diff --git a/src/include/device/pci_def.h b/src/include/device/pci_def.h index a5aa3a1c3b..58a73218fe 100644 --- a/src/include/device/pci_def.h +++ b/src/include/device/pci_def.h @@ -371,8 +371,15 @@ #define PCI_EXP_DEVSTA_AUXPD 0x10 /* AUX Power Detected */ #define PCI_EXP_DEVSTA_TRPND 0x20 /* Transactions Pending */ #define PCI_EXP_LNKCAP 12 /* Link Capabilities */ +#define PCI_EXP_LNKCAP_ASPMS 0xc00 /* ASPM Support */ +#define PCI_EXP_LNKCAP_L0SEL 0x7000 /* L0s Exit Latency */ +#define PCI_EXP_LNKCAP_L1EL 0x38000 /* L1 Exit Latency */ #define PCI_EXP_LNKCTL 16 /* Link Control */ +#define PCI_EXP_LNKCTL_RL 0x20 /* Retrain Link */ +#define PCI_EXP_LNKCTL_CCC 0x40 /* Common Clock COnfiguration */ #define PCI_EXP_LNKSTA 18 /* Link Status */ +#define PCI_EXP_LNKSTA_LT 0x800 /* Link Training */ +#define PCI_EXP_LNKSTA_SLC 0x1000 /* Slot Clock Configuration */ #define PCI_EXP_SLTCAP 20 /* Slot Capabilities */ #define PCI_EXP_SLTCTL 24 /* Slot Control */ #define PCI_EXP_SLTSTA 26 /* Slot Status */ diff --git a/src/include/device/pciexp.h b/src/include/device/pciexp.h index 409f211a82..87a5002c5e 100644 --- a/src/include/device/pciexp.h +++ b/src/include/device/pciexp.h @@ -2,6 +2,13 @@ #define DEVICE_PCIEXP_H /* (c) 2005 Linux Networx GPL see COPYING for details */ +enum aspm_type { + PCIE_ASPM_NONE = 0, + PCIE_ASPM_L0S = 1, + PCIE_ASPM_L1 = 2, + PCIE_ASPM_BOTH = 3, +}; + unsigned int pciexp_scan_bus(struct bus *bus, unsigned int min_devfn, unsigned int max_devfn, unsigned int max); unsigned int pciexp_scan_bridge(device_t dev, unsigned int max);