a73b93157f
It encourages users from writing to the FSF without giving an address. Linux also prefers to drop that and their checkpatch.pl (that we imported) looks out for that. This is the result of util/scripts/no-fsf-addresses.sh with no further editing. Change-Id: Ie96faea295fe001911d77dbc51e9a6789558fbd6 Signed-off-by: Patrick Georgi <pgeorgi@chromium.org> Reviewed-on: http://review.coreboot.org/11888 Tested-by: build bot (Jenkins) Reviewed-by: Alexandru Gagniuc <mr.nuke.me@gmail.com> Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
455 lines
13 KiB
C
455 lines
13 KiB
C
/*
|
|
* This file is part of the coreboot project.
|
|
*
|
|
* Copyright (C) 2005 Linux Networx
|
|
* (Written by Eric Biederman <ebiederman@lnxi.com> for Linux Networx)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; version 2 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#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 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
|
|
*/
|
|
#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_CLK_PM
|
|
static void pciexp_enable_clock_power_pm(device_t endp, unsigned 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");
|
|
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);
|
|
}
|
|
#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 ((cap) && (root->ops->ops_pci != NULL) &&
|
|
(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);
|
|
if(!cap) {
|
|
printk(BIOS_INFO, "Failed to enable LTR for dev = %s\n",
|
|
dev_path(dev));
|
|
return;
|
|
}
|
|
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, ~0x1f,
|
|
L1SubStateSupport);
|
|
|
|
for (dev_t = dev; dev_t; dev_t = dev_t->sibling) {
|
|
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, ~0x1f,
|
|
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
|
|
* 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)
|
|
{
|
|
device_t 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;
|
|
|
|
#if CONFIG_PCIEXP_COMMON_CLOCK
|
|
/* Check for and enable Common Clock */
|
|
pciexp_enable_common_clock(root, root_cap, dev, cap);
|
|
#endif
|
|
|
|
#if CONFIG_PCIEXP_CLK_PM
|
|
/* Check if per port CLK req is supported by endpoint*/
|
|
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);
|
|
|
|
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
|
|
}
|
|
|
|
void pciexp_scan_bus(struct bus *bus, unsigned int min_devfn,
|
|
unsigned int max_devfn)
|
|
{
|
|
device_t child;
|
|
|
|
pci_scan_bus(bus, min_devfn, max_devfn);
|
|
|
|
for (child = bus->children; child; child = child->sibling) {
|
|
if ((child->path.pci.devfn < min_devfn) ||
|
|
(child->path.pci.devfn > max_devfn)) {
|
|
continue;
|
|
}
|
|
pciexp_tune_dev(child);
|
|
}
|
|
}
|
|
|
|
void pciexp_scan_bridge(device_t 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,
|
|
.init = 0,
|
|
.scan_bus = pciexp_scan_bridge,
|
|
.enable = 0,
|
|
.reset_bus = pci_bus_reset,
|
|
.ops_pci = &pciexp_bus_ops_pci,
|
|
};
|