diff options
Diffstat (limited to 'src/southbridge/intel/bd82x6x/pch.c')
-rw-r--r-- | src/southbridge/intel/bd82x6x/pch.c | 196 |
1 files changed, 185 insertions, 11 deletions
diff --git a/src/southbridge/intel/bd82x6x/pch.c b/src/southbridge/intel/bd82x6x/pch.c index e83c2e6478..5f440e61cd 100644 --- a/src/southbridge/intel/bd82x6x/pch.c +++ b/src/southbridge/intel/bd82x6x/pch.c @@ -2,6 +2,7 @@ * This file is part of the coreboot project. * * Copyright (C) 2008-2009 coresystems GmbH + * Copyright (C) 2012 The Chromium OS Authors. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -25,6 +26,7 @@ #include "pch.h" static int pch_revision_id = -1; +static int pch_type = -1; int pch_silicon_revision(void) { @@ -35,6 +37,40 @@ int pch_silicon_revision(void) return pch_revision_id; } +int pch_silicon_type(void) +{ + if (pch_type < 0) + pch_type = pci_read_config8( + dev_find_slot(0, PCI_DEVFN(0x1f, 0)), + PCI_DEVICE_ID + 1); + return pch_type; +} + +int pch_silicon_supported(int type, int rev) +{ + int cur_type = pch_silicon_type(); + int cur_rev = pch_silicon_revision(); + + switch (type) { + case PCH_TYPE_CPT: + /* CougarPoint minimum revision */ + if (cur_type == PCH_TYPE_CPT && cur_rev >= rev) + return 1; + /* PantherPoint any revision */ + if (cur_type == PCH_TYPE_PPT) + return 1; + break; + + case PCH_TYPE_PPT: + /* PantherPoint minimum revision */ + if (cur_type == PCH_TYPE_PPT && cur_rev >= rev) + return 1; + break; + } + + return 0; +} + /* Set bit in Function Disble register to hide this device */ static void pch_hide_devfn(unsigned devfn) { @@ -119,7 +155,7 @@ void pch_iobp_update(u32 address, u32 andvalue, u32 orvalue) RCBA32(IOBPIRI) = address; /* READ OPCODE */ - if (pch_silicon_revision() >= PCH_STEP_B0) + if (pch_silicon_supported(PCH_TYPE_CPT, PCH_STEP_B0)) RCBA32(IOBPS) = IOBPS_RW_BX; else RCBA32(IOBPS) = IOBPS_READ_AX; @@ -142,7 +178,7 @@ void pch_iobp_update(u32 address, u32 andvalue, u32 orvalue) data |= orvalue; /* WRITE OPCODE */ - if (pch_silicon_revision() >= PCH_STEP_B0) + if (pch_silicon_supported(PCH_TYPE_CPT, PCH_STEP_B0)) RCBA32(IOBPS) = IOBPS_RW_BX; else RCBA32(IOBPS) = IOBPS_WRITE_AX; @@ -184,15 +220,85 @@ static int pch_pcie_check_set_enabled(device_t dev) return 0; } -void pch_enable(device_t dev) +/* RPFN is a write-once register so keep a copy until it is written */ +static u32 new_rpfn; + +/* Swap function numbers assigned to two PCIe Root Ports */ +static void pch_pcie_function_swap(u8 old_fn, u8 new_fn) +{ + u32 old_rpfn = new_rpfn; + + printk(BIOS_DEBUG, "PCH: Remap PCIe function %d to %d\n", + old_fn, new_fn); + + new_rpfn &= ~(RPFN_FNMASK(old_fn) | RPFN_FNMASK(new_fn)); + + /* 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)); +} + +/* Update devicetree with new Root Port function number assignment */ +static void pch_pcie_devicetree_update(void) +{ + device_t dev; + + /* Update the function numbers in the static devicetree */ + for (dev = all_devices; dev; dev = dev->next) { + u8 new_devfn; + + /* Only care about PCH PCIe root ports */ + if (PCI_SLOT(dev->path.pci.devfn) != + PCH_PCIE_DEV_SLOT) + continue; + + /* 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))); + + 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; + } + } +} + +/* Special handling for PCIe Root Port devices */ +static void pch_pcie_enable(device_t dev) { + struct southbridge_intel_bd82x6x_config *config = dev->chip_info; u32 reg32; - if (!dev->enabled) { - printk(BIOS_DEBUG, "%s: Disabling device\n", dev_path(dev)); + /* + * 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); /* - * PCIE Power Savings for stepping B1+: + * 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 (!dev->enabled) { + /* + * 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 @@ -201,21 +307,89 @@ void pch_enable(device_t dev) * because the pcie driver enable() handler is not * called unless the device is enabled. */ - if (pch_silicon_revision() >= PCH_STEP_B1 && - PCI_SLOT(dev->path.pci.devfn) == PCH_PCIE_DEV_SLOT && - (PCI_FUNC(dev->path.pci.devfn) == 0 || + if ((PCI_FUNC(dev->path.pci.devfn) == 0 || PCI_FUNC(dev->path.pci.devfn) == 4)) { - if (!pch_pcie_check_set_enabled(dev)) { + /* Handle workaround for PPT and CPT/B1+ */ + if (pch_silicon_supported(PCH_TYPE_CPT, PCH_STEP_B1) && + !pch_pcie_check_set_enabled(dev)) { u8 reg8 = pci_read_config8(dev, 0xe2); reg8 |= 1; pci_write_config8(dev, 0xe2, reg8); } + + /* + * Enable Clock Gating for shared PCIe resources + * before disabling this particular port. + */ + pci_write_config8(dev, 0xe1, 0x3c); } /* 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_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)); + + /* Hide this device if possible */ + pch_hide_devfn(dev->path.pci.devfn); + } 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; + } + } + + /* Enable SERR */ + reg32 = pci_read_config32(dev, PCI_COMMAND); + reg32 |= PCI_COMMAND_SERR; + pci_write_config32(dev, PCI_COMMAND, reg32); + } + + /* + * 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(); + } +} + +void pch_enable(device_t dev) +{ + u32 reg32; + + /* PCH PCIe Root Ports get special handling */ + if (PCI_SLOT(dev->path.pci.devfn) == PCH_PCIE_DEV_SLOT) + return pch_pcie_enable(dev); + + if (!dev->enabled) { + 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); /* Hide this device if possible */ |