diff options
Diffstat (limited to 'src/soc/intel/skylake/pcie.c')
-rw-r--r-- | src/soc/intel/skylake/pcie.c | 710 |
1 files changed, 710 insertions, 0 deletions
diff --git a/src/soc/intel/skylake/pcie.c b/src/soc/intel/skylake/pcie.c new file mode 100644 index 0000000000..895df932a0 --- /dev/null +++ b/src/soc/intel/skylake/pcie.c @@ -0,0 +1,710 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2008-2009 coresystems GmbH + * Copyright (C) 2014 Google Inc. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <console/console.h> +#include <device/device.h> +#include <device/pci.h> +#include <device/pciexp.h> +#include <device/pci_def.h> +#include <device/pci_ids.h> +#include <soc/gpio.h> +#include <soc/lpc.h> +#include <soc/iobp.h> +#include <soc/pch.h> +#include <soc/pci_devs.h> +#include <soc/rcba.h> +#include <soc/intel/broadwell/chip.h> +#include <soc/cpu.h> +#include <delay.h> + +static void pcie_update_cfg8(device_t dev, int reg, u8 mask, u8 or); +static void pcie_update_cfg(device_t dev, int reg, u32 mask, u32 or); + +/* Low Power variant has 6 root ports. */ +#define NUM_ROOT_PORTS 6 + +struct root_port_config { + /* RPFN is a write-once register so keep a copy until it is written */ + u32 orig_rpfn; + u32 new_rpfn; + u32 pin_ownership; + u32 strpfusecfg1; + u32 strpfusecfg2; + u32 strpfusecfg3; + u32 b0d28f0_32c; + u32 b0d28f4_32c; + u32 b0d28f5_32c; + int coalesce; + int gbe_port; + int num_ports; + device_t ports[NUM_ROOT_PORTS]; +}; + +static struct root_port_config rpc; + +static inline int root_port_is_first(device_t dev) +{ + return PCI_FUNC(dev->path.pci.devfn) == 0; +} + +static inline int root_port_is_last(device_t dev) +{ + return PCI_FUNC(dev->path.pci.devfn) == (rpc.num_ports - 1); +} + +/* Root ports are numbered 1..N in the documentation. */ +static inline int root_port_number(device_t dev) +{ + return PCI_FUNC(dev->path.pci.devfn) + 1; +} + +static void root_port_config_update_gbe_port(void) +{ + /* Is the Gbe Port enabled? */ + if (!((rpc.strpfusecfg1 >> 19) & 1)) + return; + + switch ((rpc.strpfusecfg1 >> 16) & 0x7) { + case 0: + rpc.gbe_port = 3; + break; + case 1: + rpc.gbe_port = 4; + break; + case 2: + case 3: + case 4: + case 5: + /* Lanes 0-4 of Root Port 5. */ + rpc.gbe_port = 5; + break; + default: + printk(BIOS_DEBUG, "Invalid GbE Port Selection.\n"); + } +} + +static void pcie_iosf_port_grant_count(device_t dev) +{ + u8 update_val; + u32 rpcd = (pci_read_config32(dev, 0xfc) > 14) & 0x3; + + switch (rpcd) { + case 1: + case 3: + update_val = 0x02; + break; + case 2: + update_val = 0x22; + break; + default: + update_val = 0x00; + break; + } + + RCBA32(0x103C) = (RCBA32(0x103C) & (~0xff)) | update_val; +} + +static void root_port_init_config(device_t dev) +{ + int rp; + u32 data; + u8 resp, id; + + if (root_port_is_first(dev)) { + rpc.orig_rpfn = RCBA32(RPFN); + rpc.new_rpfn = rpc.orig_rpfn; + rpc.num_ports = NUM_ROOT_PORTS; + rpc.gbe_port = -1; + /* RP0 f5[3:0] = 0101b*/ + pcie_update_cfg8(dev, 0xf5, ~0xa, 0x5); + + pcie_iosf_port_grant_count(dev); + + rpc.pin_ownership = pci_read_config32(dev, 0x410); + root_port_config_update_gbe_port(); + + pcie_update_cfg8(dev, 0xe2, ~(3 << 4), (3 << 4)); + if (dev->chip_info != NULL) { + config_t *config = dev->chip_info; + rpc.coalesce = config->pcie_port_coalesce; + } + } + + rp = root_port_number(dev); + if (rp > rpc.num_ports) { + printk(BIOS_ERR, "Found Root Port %d, expecting %d\n", + rp, rpc.num_ports); + return; + } + + /* Read the fuse configuration and pin ownership. */ + switch (rp) { + case 1: + rpc.strpfusecfg1 = pci_read_config32(dev, 0xfc); + rpc.b0d28f0_32c = pci_read_config32(dev, 0x32c); + break; + case 5: + rpc.strpfusecfg2 = pci_read_config32(dev, 0xfc); + rpc.b0d28f4_32c = pci_read_config32(dev, 0x32c); + break; + case 6: + rpc.b0d28f5_32c = pci_read_config32(dev, 0x32c); + rpc.strpfusecfg3 = pci_read_config32(dev, 0xfc); + break; + default: + break; + } + + pcie_update_cfg(dev, 0x418, 0, 0x02000430); + + if (root_port_is_first(dev)) { + /* + * set RP0 PCICFG E2h[5:4] = 11b and E1h[6] = 1 + * before configuring ASPM + */ + id = 0xe0 + (u8)(RCBA32(RPFN) & 0x07); + pch_iobp_exec(0xE00000E0, IOBP_PCICFG_READ, id, &data, &resp); + data |= ((0x30 << 16) | (0x40 << 8)); + pch_iobp_exec(0xE00000E0, IOBP_PCICFG_WRITE, id, &data, &resp); + } + + /* Cache pci device. */ + rpc.ports[rp - 1] = dev; +} + +/* Update devicetree with new Root Port function number assignment */ +static void pch_pcie_device_set_func(int index, int pci_func) +{ + device_t dev; + unsigned new_devfn; + + dev = rpc.ports[index]; + + /* Set the new PCI function field for this Root Port. */ + rpc.new_rpfn &= ~RPFN_FNMASK(index); + rpc.new_rpfn |= RPFN_FNSET(index, pci_func); + + /* Determine the new devfn for this port */ + new_devfn = PCI_DEVFN(PCH_DEV_SLOT_PCIE, pci_func); + + if (dev->path.pci.devfn != new_devfn) { + printk(BIOS_DEBUG, + "PCH: PCIe map %02x.%1x -> %02x.%1x\n", + PCI_SLOT(dev->path.pci.devfn), + PCI_FUNC(dev->path.pci.devfn), + PCI_SLOT(new_devfn), PCI_FUNC(new_devfn)); + + dev->path.pci.devfn = new_devfn; + } +} + +static void pcie_enable_clock_gating(void) +{ + int i; + int enabled_ports = 0; + int is_broadwell = !!(cpu_family_model() == BROADWELL_FAMILY_ULT); + + for (i = 0; i < rpc.num_ports; i++) { + device_t dev; + int rp; + + dev = rpc.ports[i]; + rp = root_port_number(dev); + + if (!dev->enabled) { + /* Configure shared resource clock gating. */ + if (rp == 1 || rp == 5 || rp == 6) + pcie_update_cfg8(dev, 0xe1, 0xc3, 0x3c); + + pcie_update_cfg8(dev, 0xe2, ~(3 << 4), (3 << 4)); + pcie_update_cfg(dev, 0x420, ~(1 << 31), (1 << 31)); + + /* Per-Port CLKREQ# handling. */ + if (gpio_is_native(18 + rp - 1)) + pcie_update_cfg(dev, 0x420, ~0, (3 << 29)); + + /* Enable static clock gating. */ + if (rp == 1 && !rpc.ports[1]->enabled && + !rpc.ports[2]->enabled && !rpc.ports[3]->enabled) { + pcie_update_cfg8(dev, 0xe2, ~1, 1); + pcie_update_cfg8(dev, 0xe1, 0x7f, 0x80); + } else if (rp == 5 || rp == 6) { + pcie_update_cfg8(dev, 0xe2, ~1, 1); + pcie_update_cfg8(dev, 0xe1, 0x7f, 0x80); + } + continue; + } + + enabled_ports++; + + /* Enable dynamic clock gating. */ + pcie_update_cfg8(dev, 0xe1, 0xfc, 0x03); + pcie_update_cfg8(dev, 0xe2, ~(1 << 6), (1 << 6)); + pcie_update_cfg8(dev, 0xe8, ~(3 << 2), (2 << 2)); + + /* Update PECR1 register. */ + pcie_update_cfg8(dev, 0xe8, ~0, 3); + if (is_broadwell) { + pcie_update_cfg(dev, 0x324, ~((1 << 5) | (1 << 14)), + ((1 << 5) | (1 << 14))); + } else { + pcie_update_cfg(dev, 0x324, ~(1 << 5), (1 << 5)); + } + /* Per-Port CLKREQ# handling. */ + if (gpio_is_native(18 + rp - 1)) + /* + * In addition to D28Fx PCICFG 420h[30:29] = 11b, + * set 420h[17] = 0b and 420[0] = 1b for L1 SubState. + */ + pcie_update_cfg(dev, 0x420, ~0x20000, (3 << 29) | 1); + + /* Configure shared resource clock gating. */ + if (rp == 1 || rp == 5 || rp == 6) + pcie_update_cfg8(dev, 0xe1, 0xc3, 0x3c); + + /* CLKREQ# VR Idle Enable */ + RCBA32_OR(0x2b1c, (1 << (16 + i))); + } + + if (!enabled_ports) + pcie_update_cfg8(rpc.ports[0], 0xe1, ~(1 << 6), (1 << 6)); +} + +static void root_port_commit_config(void) +{ + int i; + + /* If the first root port is disabled the coalesce ports. */ + if (!rpc.ports[0]->enabled) + rpc.coalesce = 1; + + /* Perform clock gating configuration. */ + pcie_enable_clock_gating(); + + for (i = 0; i < rpc.num_ports; i++) { + device_t dev; + u32 reg32; + int n = 0; + + dev = rpc.ports[i]; + + if (dev == NULL) { + printk(BIOS_ERR, "Root Port %d device is NULL?\n", i+1); + continue; + } + + if (dev->enabled) + continue; + + printk(BIOS_DEBUG, "%s: Disabling device\n", dev_path(dev)); + + /* 8.2 Configuration of PCI Express Root Ports */ + pcie_update_cfg(dev, 0x338, ~(1 << 26), 1 << 26); + + do { + reg32 = pci_read_config32(dev, 0x328); + n++; + if (((reg32 & 0xff000000) == 0x01000000) || (n > 50)) + break; + udelay(100); + } while (1); + + if (n > 50) + printk(BIOS_DEBUG, "%s: Timeout waiting for 328h\n", + dev_path(dev)); + + pcie_update_cfg(dev, 0x408, ~(1 << 27), 1 << 27); + + /* Disable this device if possible */ + pch_disable_devfn(dev); + } + + if (rpc.coalesce) { + int current_func; + + /* For all Root Ports N enabled ports get assigned the lower + * PCI function number. The disabled ones get upper PCI + * function numbers. */ + current_func = 0; + for (i = 0; i < rpc.num_ports; i++) { + if (!rpc.ports[i]->enabled) + continue; + pch_pcie_device_set_func(i, current_func); + current_func++; + } + + /* Allocate the disabled devices' PCI function number. */ + for (i = 0; i < rpc.num_ports; i++) { + if (rpc.ports[i]->enabled) + continue; + pch_pcie_device_set_func(i, current_func); + current_func++; + } + } + + printk(BIOS_SPEW, "PCH: RPFN 0x%08x -> 0x%08x\n", + rpc.orig_rpfn, rpc.new_rpfn); + RCBA32(RPFN) = rpc.new_rpfn; +} + +static void root_port_mark_disable(device_t dev) +{ + /* Mark device as disabled. */ + dev->enabled = 0; + /* Mark device to be hidden. */ + rpc.new_rpfn |= RPFN_HIDE(PCI_FUNC(dev->path.pci.devfn)); +} + +static void root_port_check_disable(device_t dev) +{ + int rp; + + /* Device already disabled. */ + if (!dev->enabled) { + root_port_mark_disable(dev); + return; + } + + rp = root_port_number(dev); + + /* Is the GbE port mapped to this Root Port? */ + if (rp == rpc.gbe_port) { + root_port_mark_disable(dev); + return; + } + + /* Check Root Port Configuration. */ + switch (rp) { + case 2: + /* Root Port 2 is disabled for all lane configurations + * but config 00b (4x1 links). */ + if ((rpc.strpfusecfg1 >> 14) & 0x3) { + root_port_mark_disable(dev); + return; + } + break; + case 3: + /* Root Port 3 is disabled in config 11b (1x4 links). */ + if (((rpc.strpfusecfg1 >> 14) & 0x3) == 0x3) { + root_port_mark_disable(dev); + return; + } + break; + case 4: + /* Root Port 4 is disabled in configs 11b (1x4 links) + * and 10b (2x2 links). */ + if ((rpc.strpfusecfg1 >> 14) & 0x2) { + root_port_mark_disable(dev); + return; + } + break; + } + + /* Check Pin Ownership. */ + switch (rp) { + case 1: + /* Bit 0 is Root Port 1 ownership. */ + if ((rpc.pin_ownership & 0x1) == 0) { + root_port_mark_disable(dev); + return; + } + break; + case 2: + /* Bit 2 is Root Port 2 ownership. */ + if ((rpc.pin_ownership & 0x4) == 0) { + root_port_mark_disable(dev); + return; + } + break; + case 6: + /* Bits 7:4 are Root Port 6 pin-lane ownership. */ + if ((rpc.pin_ownership & 0xf0) == 0) { + root_port_mark_disable(dev); + return; + } + break; + } +} + +static void pcie_update_cfg8(device_t dev, int reg, u8 mask, u8 or) +{ + u8 reg8; + + reg8 = pci_read_config8(dev, reg); + reg8 &= mask; + reg8 |= or; + pci_write_config8(dev, reg, reg8); +} + +static void pcie_update_cfg(device_t dev, int reg, u32 mask, u32 or) +{ + u32 reg32; + + reg32 = pci_read_config32(dev, reg); + reg32 &= mask; + reg32 |= or; + pci_write_config32(dev, reg, reg32); +} + +static void pcie_add_0x0202000_iobp(u32 reg) +{ + u32 reg32; + + reg32 = pch_iobp_read(reg); + reg32 += (0x2 << 16) | (0x2 << 8); + pch_iobp_write(reg, reg32); +} + +static void pch_pcie_early(struct device *dev) +{ + config_t *config = dev->chip_info; + int do_aspm = 0; + int rp = root_port_number(dev); + + switch (rp) { + case 1: + case 2: + case 3: + case 4: + /* + * Bits 31:28 of b0d28f0 0x32c register correspond to + * Root Ports 4:1. + */ + do_aspm = !!(rpc.b0d28f0_32c & (1 << (28 + rp - 1))); + break; + case 5: + /* + * Bit 28 of b0d28f4 0x32c register correspond to + * Root Ports 4:1. + */ + do_aspm = !!(rpc.b0d28f4_32c & (1 << 28)); + break; + case 6: + /* + * Bit 28 of b0d28f5 0x32c register correspond to + * Root Ports 4:1. + */ + do_aspm = !!(rpc.b0d28f5_32c & (1 << 28)); + break; + } + + /* Allow ASPM to be forced on in devicetree */ + if (config && (config->pcie_port_force_aspm & (1 << (rp - 1)))) + do_aspm = 1; + + printk(BIOS_DEBUG, "PCIe Root Port %d ASPM is %sabled\n", + rp, do_aspm ? "en" : "dis"); + + if (do_aspm) { + /* Set ASPM bits in MPC2 register. */ + pcie_update_cfg(dev, 0xd4, ~(0x3 << 2), (1 << 4) | (0x2 << 2)); + + /* Set unique clock exit latency in MPC register. */ + pcie_update_cfg(dev, 0xd8, ~(0x7 << 18), (0x7 << 18)); + + switch (rp) { + case 1: + pcie_add_0x0202000_iobp(0xe9002440); + break; + case 2: + pcie_add_0x0202000_iobp(0xe9002640); + break; + case 3: + pcie_add_0x0202000_iobp(0xe9000840); + break; + case 4: + pcie_add_0x0202000_iobp(0xe9000a40); + break; + case 5: + pcie_add_0x0202000_iobp(0xe9000c40); + pcie_add_0x0202000_iobp(0xe9000e40); + pcie_add_0x0202000_iobp(0xe9001040); + pcie_add_0x0202000_iobp(0xe9001240); + break; + case 6: + /* Update IOBP based on lane ownership. */ + if (rpc.pin_ownership & (1 << 4)) + pcie_add_0x0202000_iobp(0xea002040); + if (rpc.pin_ownership & (1 << 5)) + pcie_add_0x0202000_iobp(0xea002240); + if (rpc.pin_ownership & (1 << 6)) + pcie_add_0x0202000_iobp(0xea002440); + if (rpc.pin_ownership & (1 << 7)) + pcie_add_0x0202000_iobp(0xea002640); + break; + } + + pcie_update_cfg(dev, 0x338, ~(1 << 26), 0); + } + + /* Enable LTR in Root Port. Disable OBFF. */ + pcie_update_cfg(dev, 0x64, ~(1 << 11) & ~(3 << 18), (1 << 11)); + pcie_update_cfg(dev, 0x68, ~(1 << 10), (1 << 10)); + + pcie_update_cfg(dev, 0x318, ~(0xffff << 16), (0x1414 << 16)); + + /* Set L1 exit latency in LCAP register. */ + if (!do_aspm && (pci_read_config8(dev, 0xf5) & 0x1)) + pcie_update_cfg(dev, 0x4c, ~(0x7 << 15), (0x4 << 15)); + else + pcie_update_cfg(dev, 0x4c, ~(0x7 << 15), (0x2 << 15)); + + pcie_update_cfg(dev, 0x314, 0x0, 0x743a361b); + + /* Set Common Clock Exit Latency in MPC register. */ + pcie_update_cfg(dev, 0xd8, ~(0x7 << 15), (0x3 << 15)); + + pcie_update_cfg(dev, 0x33c, ~0x00ffffff, 0x854d74); + + /* Set Invalid Receive Range Check Enable in MPC register. */ + pcie_update_cfg(dev, 0xd8, ~0, (1 << 25)); + + pcie_update_cfg8(dev, 0xf5, 0x0f, 0); + + /* Set AER Extended Cap ID to 01h and Next Cap Pointer to 200h. */ + pcie_update_cfg(dev, 0x100, ~(1 << 29) & ~0xfffff, (1 << 29) | 0x10001); + + /* Set L1 Sub-State Cap ID to 1Eh and Next Cap Pointer to None. */ + pcie_update_cfg(dev, 0x200, ~0xffff, 0x001e); + + pcie_update_cfg(dev, 0x320, ~(3 << 20) & ~(7 << 6), + (1 << 20) | (3 << 6)); + /* Enable Relaxed Order from Root Port. */ + pcie_update_cfg(dev, 0x320, ~(3 << 23), (3 << 23)); + + if (rp == 1 || rp == 5 || rp == 6) + pcie_update_cfg8(dev, 0xf7, ~0xc, 0); + + /* Set EOI forwarding disable. */ + pcie_update_cfg(dev, 0xd4, ~0, (1 << 1)); + + /* Read and write back write-once capability registers. */ + pcie_update_cfg(dev, 0x34, ~0, 0); + pcie_update_cfg(dev, 0x40, ~0, 0); + pcie_update_cfg(dev, 0x80, ~0, 0); + pcie_update_cfg(dev, 0x90, ~0, 0); +} + +static void pch_pcie_init(struct device *dev) +{ + u16 reg16; + u32 reg32; + + printk(BIOS_DEBUG, "Initializing PCH PCIe bridge.\n"); + + /* Enable SERR */ + reg32 = pci_read_config32(dev, PCI_COMMAND); + reg32 |= PCI_COMMAND_SERR; + pci_write_config32(dev, PCI_COMMAND, reg32); + + /* Enable Bus Master */ + reg32 = pci_read_config32(dev, PCI_COMMAND); + reg32 |= PCI_COMMAND_MASTER; + pci_write_config32(dev, PCI_COMMAND, reg32); + + /* Set Cache Line Size to 0x10 */ + pci_write_config8(dev, 0x0c, 0x10); + + reg16 = pci_read_config16(dev, 0x3e); + reg16 &= ~(1 << 0); /* disable parity error response */ + reg16 |= (1 << 2); /* ISA enable */ + pci_write_config16(dev, 0x3e, reg16); + +#ifdef EVEN_MORE_DEBUG + reg32 = pci_read_config32(dev, 0x20); + printk(BIOS_SPEW, " MBL = 0x%08x\n", reg32); + reg32 = pci_read_config32(dev, 0x24); + printk(BIOS_SPEW, " PMBL = 0x%08x\n", reg32); + reg32 = pci_read_config32(dev, 0x28); + printk(BIOS_SPEW, " PMBU32 = 0x%08x\n", reg32); + reg32 = pci_read_config32(dev, 0x2c); + printk(BIOS_SPEW, " PMLU32 = 0x%08x\n", reg32); +#endif + + /* Clear errors in status registers */ + reg16 = pci_read_config16(dev, 0x06); + pci_write_config16(dev, 0x06, reg16); + reg16 = pci_read_config16(dev, 0x1e); + pci_write_config16(dev, 0x1e, reg16); +} + +static void pch_pcie_enable(device_t dev) +{ + /* Add this device to the root port config structure. */ + root_port_init_config(dev); + + /* Check to see if this Root Port should be disabled. */ + root_port_check_disable(dev); + + /* Power Management init before enumeration */ + if (dev->enabled) + pch_pcie_early(dev); + + /* + * When processing the last PCIe root port we can now + * update the Root Port Function Number and Hide register. + */ + if (root_port_is_last(dev)) + root_port_commit_config(); +} + +static void pcie_set_subsystem(device_t dev, unsigned vendor, unsigned device) +{ + /* NOTE: This is not the default position! */ + if (!vendor || !device) + pci_write_config32(dev, 0x94, pci_read_config32(dev, 0)); + else + pci_write_config32(dev, 0x94, (device << 16) | vendor); +} + +static void pcie_set_L1_ss_max_latency(device_t dev, unsigned int off) +{ + /* Set max snoop and non-snoop latency for Broadwell */ + pci_mmio_write_config32(dev, off, 0x10031003); +} + +static struct pci_operations pcie_ops = { + .set_subsystem = pcie_set_subsystem, + .set_L1_ss_latency = pcie_set_L1_ss_max_latency, +}; + +static struct device_operations device_ops = { + .read_resources = pci_bus_read_resources, + .set_resources = pci_dev_set_resources, + .enable_resources = pci_bus_enable_resources, + .init = pch_pcie_init, + .enable = pch_pcie_enable, + .scan_bus = pciexp_scan_bridge, + .ops_pci = &pcie_ops, +}; + +static const unsigned short pcie_device_ids[] = { + /* Lynxpoint-LP */ + 0x9c10, 0x9c12, 0x9c14, 0x9c16, 0x9c18, 0x9c1a, + /* WildcatPoint */ + 0x9c90, 0x9c92, 0x9c94, 0x9c96, 0x9c98, 0x9c9a, 0x2448, + 0 +}; + +static const struct pci_driver pch_pcie __pci_driver = { + .ops = &device_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .devices = pcie_device_ids, +}; |