From c0254e6b6fbe7268fa47b2d4bd0a203423b2eec2 Mon Sep 17 00:00:00 2001 From: Aaron Durbin Date: Thu, 20 Jun 2013 01:20:30 -0500 Subject: lynxpoint: disable pcie devices based on config PCIe Root Ports should be disabled based on pin ownership and the strapping configuration. Implement this logic for LynxPoint. The chip_ops->enable_dev() path is no longer used. Instead the PCIe driver handles the enabling and disabling of devices. This allows for having an empty or incomplete device tree since those "allocated" devices do not travel through the chip_ops->enable_dev() path. The coalescing was tested to be working properly, however not all configurations were tested. Change-Id: I1e8bfe5e447b72ff8a4b04b650982d8c1ae0823c Signed-off-by: Aaron Durbin Reviewed-on: https://gerrit.chromium.org/gerrit/59424 Reviewed-by: Stefan Reinauer Reviewed-by: Duncan Laurie Reviewed-on: http://review.coreboot.org/4322 Tested-by: build bot (Jenkins) Reviewed-by: Ronald G. Minnich --- src/southbridge/intel/lynxpoint/pch.c | 4 +- src/southbridge/intel/lynxpoint/pch.h | 2 +- src/southbridge/intel/lynxpoint/pcie.c | 456 +++++++++++++++++++++++---------- 3 files changed, 318 insertions(+), 144 deletions(-) (limited to 'src/southbridge/intel/lynxpoint') diff --git a/src/southbridge/intel/lynxpoint/pch.c b/src/southbridge/intel/lynxpoint/pch.c index b4605f5c02..d073a4b9a7 100644 --- a/src/southbridge/intel/lynxpoint/pch.c +++ b/src/southbridge/intel/lynxpoint/pch.c @@ -293,9 +293,9 @@ void pch_enable(device_t dev) { u32 reg32; - /* PCH PCIe Root Ports get special handling */ + /* PCH PCIe Root Ports are handled in PCIe driver. */ if (PCI_SLOT(dev->path.pci.devfn) == PCH_PCIE_DEV_SLOT) - return pch_pcie_enable_dev(dev); + return; if (!dev->enabled) { printk(BIOS_DEBUG, "%s: Disabling device\n", dev_path(dev)); diff --git a/src/southbridge/intel/lynxpoint/pch.h b/src/southbridge/intel/lynxpoint/pch.h index 9acdd1f79c..d1fbbe75bd 100644 --- a/src/southbridge/intel/lynxpoint/pch.h +++ b/src/southbridge/intel/lynxpoint/pch.h @@ -166,7 +166,6 @@ void pch_disable_devfn(device_t dev); u32 pch_iobp_read(u32 address); void pch_iobp_write(u32 address, u32 data); void pch_iobp_update(u32 address, u32 andvalue, u32 orvalue); -void pch_pcie_enable_dev(device_t dev); #if CONFIG_ELOG void pch_log_state(void); #endif @@ -493,6 +492,7 @@ void set_gpio(int gpio_num, int value); #define RP6D 0x0180 /* 32bit */ #define RP6BA 0x0188 /* 64bit */ +#define RPC 0x0400 /* 32bit */ #define RPFN 0x0404 /* 32bit */ /* Root Port configuratinon space hide */ diff --git a/src/southbridge/intel/lynxpoint/pcie.c b/src/southbridge/intel/lynxpoint/pcie.c index 28f4b6ad15..98dfe6170e 100644 --- a/src/southbridge/intel/lynxpoint/pcie.c +++ b/src/southbridge/intel/lynxpoint/pcie.c @@ -25,188 +25,343 @@ #include #include "pch.h" -/* Check if any port in set X to X+3 is enabled */ -static int pch_pcie_check_set_enabled(device_t dev) +/* LynxPoint-LP has 6 root ports while non-LP has 8. */ +#define MAX_NUM_ROOT_PORTS 8 +#define H_NUM_ROOT_PORTS MAX_NUM_ROOT_PORTS +#define LP_NUM_ROOT_PORTS (MAX_NUM_ROOT_PORTS - 2) + +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; + int coalesce; + int gbe_port; + int num_ports; + device_t ports[MAX_NUM_ROOT_PORTS]; +}; + +static struct root_port_config rpc; + +static inline int max_root_ports(void) { - device_t port; - int port_func; - int dev_func = PCI_FUNC(dev->path.pci.devfn); - - printk(BIOS_DEBUG, "%s: check set enabled\n", dev_path(dev)); - - /* Go through static device tree list of devices - * because enumeration is still in progress */ - for (port = all_devices; port; port = port->next) { - /* Only care about PCIe root ports */ - if (PCI_SLOT(port->path.pci.devfn) != - PCI_SLOT(dev->path.pci.devfn)) - continue; + if (pch_is_lp()) + return LP_NUM_ROOT_PORTS; + else + return H_NUM_ROOT_PORTS; +} - /* Check if port is in range and enabled */ - port_func = PCI_FUNC(port->path.pci.devfn); - if (port_func >= dev_func && - port_func < (dev_func + 4) && - port->enabled) - return 1; - } +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); +} - /* None of the ports in this set are enabled */ - return 0; +/* 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; } -/* RPFN is a write-once register so keep a copy until it is written */ -static u32 new_rpfn; +static void root_port_config_update_gbe_port(void) +{ + /* Is the Gbe Port enabled? */ + if (!((rpc.strpfusecfg1 >> 19) & 1)) + return; + + if (pch_is_lp()) { + 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"); + } + } else { + /* Non-LP has 1:1 mapping with root ports. */ + rpc.gbe_port = ((rpc.strpfusecfg1 >> 16) & 0x7) + 1; + } +} -/* Swap function numbers assigned to two PCIe Root Ports */ -static void pch_pcie_function_swap(u8 old_fn, u8 new_fn) +static void root_port_init_config(device_t dev) { - u32 old_rpfn = new_rpfn; + int rp; - printk(BIOS_DEBUG, "PCH: Remap PCIe function %d to %d\n", - old_fn, new_fn); + if (root_port_is_first(dev)) { + rpc.orig_rpfn = RCBA32(RPFN); + rpc.new_rpfn = rpc.orig_rpfn; + rpc.num_ports = max_root_ports(); + rpc.gbe_port = -1; - new_rpfn &= ~(RPFN_FNMASK(old_fn) | RPFN_FNMASK(new_fn)); + rpc.pin_ownership = pci_read_config32(dev, 0x410); + root_port_config_update_gbe_port(); - /* Old function set to new function and disabled */ - new_rpfn |= RPFN_FNSET(old_fn, RPFN_FNGET(old_rpfn, new_fn)); - new_rpfn |= RPFN_FNSET(new_fn, RPFN_FNGET(old_rpfn, old_fn)); + if (dev->chip_info != NULL) { + struct southbridge_intel_lynxpoint_config *config; + + 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); + break; + case 5: + rpc.strpfusecfg2 = pci_read_config32(dev, 0xfc); + break; + case 6: + rpc.strpfusecfg3 = pci_read_config32(dev, 0xfc); + break; + default: + break; + } + + /* Cache pci device. */ + rpc.ports[rp - 1] = dev; } /* Update devicetree with new Root Port function number assignment */ -static void pch_pcie_devicetree_update(void) +static void pch_pcie_device_set_func(int index, int pci_func) { device_t dev; + unsigned new_devfn; - /* Update the function numbers in the static devicetree */ - for (dev = all_devices; dev; dev = dev->next) { - u8 new_devfn; + dev = rpc.ports[index]; - /* Only care about PCH PCIe root ports */ - if (PCI_SLOT(dev->path.pci.devfn) != - PCH_PCIE_DEV_SLOT) - continue; + /* 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_PCIE_DEV_SLOT, - RPFN_FNGET(new_rpfn, - PCI_FUNC(dev->path.pci.devfn))); + /* Determine the new devfn for this port */ + new_devfn = PCI_DEVFN(PCH_PCIE_DEV_SLOT, 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)); + 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; - } + dev->path.pci.devfn = new_devfn; } } -/* Special handling for PCIe Root Port devices */ -void pch_pcie_enable_dev(device_t dev) +static void root_port_commit_config(void) { - struct southbridge_intel_lynxpoint_config *config = dev->chip_info; - u32 reg32; + int i; - /* - * Save a copy of the Root Port Function Number map when - * starting to walk the list of PCIe Root Ports so it can - * be updated locally and written out when the last port - * has been processed. - */ - if (PCI_FUNC(dev->path.pci.devfn) == 0) { - new_rpfn = RCBA32(RPFN); - - /* - * Enable Root Port coalescing if the first port is disabled - * or the other devices will not be enumerated by the OS. - */ - if (!dev->enabled) - config->pcie_port_coalesce = 1; - - if (config->pcie_port_coalesce) - printk(BIOS_INFO, - "PCH: PCIe Root Port coalescing is enabled\n"); - } + /* If the first root port is disabled the coalesce ports. */ + if (!rpc.ports[0]->enabled) + rpc.coalesce = 1; - if (!dev->enabled) { - printk(BIOS_DEBUG, "%s: Disabling device\n", dev_path(dev)); + for (i = 0; i < rpc.num_ports; i++) { + device_t dev; + u32 reg32; - /* - * PCIE Power Savings for PantherPoint and CougarPoint/B1+ - * - * If PCIe 0-3 disabled set Function 0 0xE2[0] = 1 - * If PCIe 4-7 disabled set Function 4 0xE2[0] = 1 - * - * This check is done here instead of pcie driver - * because the pcie driver enable() handler is not - * called unless the device is enabled. - */ - if ((PCI_FUNC(dev->path.pci.devfn) == 0 || - PCI_FUNC(dev->path.pci.devfn) == 4)) { - /* Handle workaround for PPT and CPT/B1+ */ - if (!pch_pcie_check_set_enabled(dev)) { - u8 reg8 = pci_read_config8(dev, 0xe2); - reg8 |= 1; - pci_write_config8(dev, 0xe2, reg8); - } + dev = rpc.ports[i]; - /* - * Enable Clock Gating for shared PCIe resources - * before disabling this particular port. - */ - pci_write_config8(dev, 0xe1, 0x3c); + 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)); + /* Ensure memory, io, and bus master are all disabled */ reg32 = pci_read_config32(dev, PCI_COMMAND); reg32 &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY | PCI_COMMAND_IO); pci_write_config32(dev, PCI_COMMAND, reg32); - /* Do not claim downstream transactions for PCIe ports */ - new_rpfn |= RPFN_HIDE(PCI_FUNC(dev->path.pci.devfn)); - /* Disable this device if possible */ pch_disable_devfn(dev); - } else { - int fn; - - /* - * Check if there is a lower disabled port to swap with this - * port in order to maintain linear order starting at zero. - */ - if (config->pcie_port_coalesce) { - for (fn=0; fn < PCI_FUNC(dev->path.pci.devfn); fn++) { - if (!(new_rpfn & RPFN_HIDE(fn))) - continue; - - /* Swap places with this function */ - pch_pcie_function_swap( - PCI_FUNC(dev->path.pci.devfn), fn); - break; - } + } + + 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++; } - /* Enable SERR */ - reg32 = pci_read_config32(dev, PCI_COMMAND); - reg32 |= PCI_COMMAND_SERR; - pci_write_config32(dev, PCI_COMMAND, reg32); + /* 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++; + } } - /* - * When processing the last PCIe root port we can now - * update the Root Port Function Number and Hide register. - */ - if (PCI_FUNC(dev->path.pci.devfn) == 7) { - printk(BIOS_SPEW, "PCH: RPFN 0x%08x -> 0x%08x\n", - RCBA32(RPFN), new_rpfn); - RCBA32(RPFN) = new_rpfn; - - /* Update static devictree with new function numbers */ - if (config->pcie_port_coalesce) - pch_pcie_devicetree_update(); + 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; + int is_lp; + + /* 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; + } + + is_lp = pch_is_lp(); + + /* 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; + case 6: + if (is_lp) + break; + /* Root Port 6 is disabled for all lane configurations + * but config 00b (4x1 links). */ + if ((rpc.strpfusecfg2 >> 14) & 0x3) { + root_port_mark_disable(dev); + return; + } + break; + case 7: + if (is_lp) + break; + /* Root Port 3 is disabled in config 11b (1x4 links). */ + if (((rpc.strpfusecfg2 >> 14) & 0x3) == 0x3) { + root_port_mark_disable(dev); + return; + } + break; + case 8: + if (is_lp) + break; + /* Root Port 8 is disabled in configs 11b (1x4 links) + * and 10b (2x2 links). */ + if ((rpc.strpfusecfg2 >> 14) & 0x2) { + root_port_mark_disable(dev); + return; + } + break; + } + + /* Check Pin Ownership. */ + if (is_lp) { + 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; + } + } else { + switch (rp) { + case 1: + /* Bits 4 and 0 are Root Port 1 ownership. */ + if ((rpc.pin_ownership & 0x11) == 0) { + root_port_mark_disable(dev); + return; + } + break; + case 2: + /* Bits 5 and 2 are Root Port 2 ownership. */ + if ((rpc.pin_ownership & 0x24) == 0) { + root_port_mark_disable(dev); + return; + } + break; + } } } @@ -374,6 +529,11 @@ static void pci_init(struct device *dev) 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; @@ -415,8 +575,22 @@ static void pci_init(struct device *dev) 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 */ - pch_pcie_pm_early(dev); + if (dev->enabled) + pch_pcie_pm_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) -- cgit v1.2.3