diff options
author | Duncan Laurie <dlaurie@chromium.org> | 2011-10-25 14:15:11 -0700 |
---|---|---|
committer | Stefan Reinauer <stefan.reinauer@coreboot.org> | 2012-03-29 22:16:07 +0200 |
commit | 90dcdd43ee83b12ddd1cdafba7613971cbfa2117 (patch) | |
tree | a86c9e122215b93566e9f9174e8919f81b08a813 /src/devices/pciexp_device.c | |
parent | 22c0468d3927a370b9723e9e78714c2731d33a81 (diff) |
Add support for enabling PCIe Common Clock and ASPM
These are guarded by individual Kconfig entries. The deprecated
CONFIG_PCIE_TUNING defines have been removed in favor of using specific
config options.
This is the generic half, there is board-specific pieces
still to come that tune before and after ASPM is enabled.
Change-Id: I3fe46282eada67629e9eeeed07e487dff54f2729
Signed-off-by: Duncan Laurie <dlaurie@google.com>
Reviewed-on: http://review.coreboot.org/735
Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
Tested-by: build bot (Jenkins)
Diffstat (limited to 'src/devices/pciexp_device.c')
-rw-r--r-- | src/devices/pciexp_device.c | 188 |
1 files changed, 177 insertions, 11 deletions
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 <console/console.h> +#include <delay.h> #include <device/device.h> #include <device/pci.h> #include <device/pci_ids.h> #include <device/pciexp.h> +#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; + +#if CONFIG_PCIEXP_COMMON_CLOCK + /* Check for and enable Common Clock */ + pciexp_enable_common_clock(root, root_cap, dev, cap); +#endif - // TODO make this depending on ASPM. +#if CONFIG_PCIEXP_ASPM + /* Check for and enable ASPM */ + enum aspm_type apmc = pciexp_enable_aspm(root, root_cap, dev, cap); - /* 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 (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 } |