aboutsummaryrefslogtreecommitdiffstats
path: root/arch/ppc/syslib/pci_auto.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /arch/ppc/syslib/pci_auto.c
downloadlinux-dev-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.xz
linux-dev-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'arch/ppc/syslib/pci_auto.c')
-rw-r--r--arch/ppc/syslib/pci_auto.c517
1 files changed, 517 insertions, 0 deletions
diff --git a/arch/ppc/syslib/pci_auto.c b/arch/ppc/syslib/pci_auto.c
new file mode 100644
index 000000000000..d64207c2a972
--- /dev/null
+++ b/arch/ppc/syslib/pci_auto.c
@@ -0,0 +1,517 @@
+/*
+ * arch/ppc/syslib/pci_auto.c
+ *
+ * PCI autoconfiguration library
+ *
+ * Author: Matt Porter <mporter@mvista.com>
+ *
+ * 2001 (c) MontaVista, Software, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+
+/*
+ * The CardBus support is very preliminary. Preallocating space is
+ * the way to go but will require some change in card services to
+ * make it useful. Eventually this will ensure that we can put
+ * multiple CB bridges behind multiple P2P bridges. For now, at
+ * least it ensures that we place the CB bridge BAR and assigned
+ * initial bus numbers. I definitely need to do something about
+ * the lack of 16-bit I/O support. -MDP
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+
+#include <asm/pci-bridge.h>
+
+#define PCIAUTO_IDE_MODE_MASK 0x05
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(x...) printk(x)
+#else
+#define DBG(x...)
+#endif /* DEBUG */
+
+static int pciauto_upper_iospc;
+static int pciauto_upper_memspc;
+
+void __init pciauto_setup_bars(struct pci_controller *hose,
+ int current_bus,
+ int pci_devfn,
+ int bar_limit)
+{
+ int bar_response, bar_size, bar_value;
+ int bar, addr_mask;
+ int * upper_limit;
+ int found_mem64 = 0;
+
+ DBG("PCI Autoconfig: Found Bus %d, Device %d, Function %d\n",
+ current_bus, PCI_SLOT(pci_devfn), PCI_FUNC(pci_devfn) );
+
+ for (bar = PCI_BASE_ADDRESS_0; bar <= bar_limit; bar+=4) {
+ /* Tickle the BAR and get the response */
+ early_write_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ bar,
+ 0xffffffff);
+ early_read_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ bar,
+ &bar_response);
+
+ /* If BAR is not implemented go to the next BAR */
+ if (!bar_response)
+ continue;
+
+ /* Check the BAR type and set our address mask */
+ if (bar_response & PCI_BASE_ADDRESS_SPACE) {
+ addr_mask = PCI_BASE_ADDRESS_IO_MASK;
+ upper_limit = &pciauto_upper_iospc;
+ DBG("PCI Autoconfig: BAR 0x%x, I/O, ", bar);
+ } else {
+ if ( (bar_response & PCI_BASE_ADDRESS_MEM_TYPE_MASK) ==
+ PCI_BASE_ADDRESS_MEM_TYPE_64)
+ found_mem64 = 1;
+
+ addr_mask = PCI_BASE_ADDRESS_MEM_MASK;
+ upper_limit = &pciauto_upper_memspc;
+ DBG("PCI Autoconfig: BAR 0x%x, Mem ", bar);
+ }
+
+ /* Calculate requested size */
+ bar_size = ~(bar_response & addr_mask) + 1;
+
+ /* Allocate a base address */
+ bar_value = (*upper_limit - bar_size) & ~(bar_size - 1);
+
+ /* Write it out and update our limit */
+ early_write_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ bar,
+ bar_value);
+
+ *upper_limit = bar_value;
+
+ /*
+ * If we are a 64-bit decoder then increment to the
+ * upper 32 bits of the bar and force it to locate
+ * in the lower 4GB of memory.
+ */
+ if (found_mem64) {
+ bar += 4;
+ early_write_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ bar,
+ 0x00000000);
+ found_mem64 = 0;
+ }
+
+ DBG("size=0x%x, address=0x%x\n",
+ bar_size, bar_value);
+ }
+
+}
+
+void __init pciauto_prescan_setup_bridge(struct pci_controller *hose,
+ int current_bus,
+ int pci_devfn,
+ int sub_bus,
+ int *iosave,
+ int *memsave)
+{
+ /* Configure bus number registers */
+ early_write_config_byte(hose,
+ current_bus,
+ pci_devfn,
+ PCI_PRIMARY_BUS,
+ current_bus);
+ early_write_config_byte(hose,
+ current_bus,
+ pci_devfn,
+ PCI_SECONDARY_BUS,
+ sub_bus + 1);
+ early_write_config_byte(hose,
+ current_bus,
+ pci_devfn,
+ PCI_SUBORDINATE_BUS,
+ 0xff);
+
+ /* Round memory allocator to 1MB boundary */
+ pciauto_upper_memspc &= ~(0x100000 - 1);
+ *memsave = pciauto_upper_memspc;
+
+ /* Round I/O allocator to 4KB boundary */
+ pciauto_upper_iospc &= ~(0x1000 - 1);
+ *iosave = pciauto_upper_iospc;
+
+ /* Set up memory and I/O filter limits, assume 32-bit I/O space */
+ early_write_config_word(hose,
+ current_bus,
+ pci_devfn,
+ PCI_MEMORY_LIMIT,
+ ((pciauto_upper_memspc - 1) & 0xfff00000) >> 16);
+ early_write_config_byte(hose,
+ current_bus,
+ pci_devfn,
+ PCI_IO_LIMIT,
+ ((pciauto_upper_iospc - 1) & 0x0000f000) >> 8);
+ early_write_config_word(hose,
+ current_bus,
+ pci_devfn,
+ PCI_IO_LIMIT_UPPER16,
+ ((pciauto_upper_iospc - 1) & 0xffff0000) >> 16);
+
+ /* Zero upper 32 bits of prefetchable base/limit */
+ early_write_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ PCI_PREF_BASE_UPPER32,
+ 0);
+ early_write_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ PCI_PREF_LIMIT_UPPER32,
+ 0);
+}
+
+void __init pciauto_postscan_setup_bridge(struct pci_controller *hose,
+ int current_bus,
+ int pci_devfn,
+ int sub_bus,
+ int *iosave,
+ int *memsave)
+{
+ int cmdstat;
+
+ /* Configure bus number registers */
+ early_write_config_byte(hose,
+ current_bus,
+ pci_devfn,
+ PCI_SUBORDINATE_BUS,
+ sub_bus);
+
+ /*
+ * Round memory allocator to 1MB boundary.
+ * If no space used, allocate minimum.
+ */
+ pciauto_upper_memspc &= ~(0x100000 - 1);
+ if (*memsave == pciauto_upper_memspc)
+ pciauto_upper_memspc -= 0x00100000;
+
+ early_write_config_word(hose,
+ current_bus,
+ pci_devfn,
+ PCI_MEMORY_BASE,
+ pciauto_upper_memspc >> 16);
+
+ /* Allocate 1MB for pre-fretch */
+ early_write_config_word(hose,
+ current_bus,
+ pci_devfn,
+ PCI_PREF_MEMORY_LIMIT,
+ ((pciauto_upper_memspc - 1) & 0xfff00000) >> 16);
+
+ pciauto_upper_memspc -= 0x100000;
+
+ early_write_config_word(hose,
+ current_bus,
+ pci_devfn,
+ PCI_PREF_MEMORY_BASE,
+ pciauto_upper_memspc >> 16);
+
+ /* Round I/O allocator to 4KB boundary */
+ pciauto_upper_iospc &= ~(0x1000 - 1);
+ if (*iosave == pciauto_upper_iospc)
+ pciauto_upper_iospc -= 0x1000;
+
+ early_write_config_byte(hose,
+ current_bus,
+ pci_devfn,
+ PCI_IO_BASE,
+ (pciauto_upper_iospc & 0x0000f000) >> 8);
+ early_write_config_word(hose,
+ current_bus,
+ pci_devfn,
+ PCI_IO_BASE_UPPER16,
+ pciauto_upper_iospc >> 16);
+
+ /* Enable memory and I/O accesses, enable bus master */
+ early_read_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ PCI_COMMAND,
+ &cmdstat);
+ early_write_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ PCI_COMMAND,
+ cmdstat |
+ PCI_COMMAND_IO |
+ PCI_COMMAND_MEMORY |
+ PCI_COMMAND_MASTER);
+}
+
+void __init pciauto_prescan_setup_cardbus_bridge(struct pci_controller *hose,
+ int current_bus,
+ int pci_devfn,
+ int sub_bus,
+ int *iosave,
+ int *memsave)
+{
+ /* Configure bus number registers */
+ early_write_config_byte(hose,
+ current_bus,
+ pci_devfn,
+ PCI_PRIMARY_BUS,
+ current_bus);
+ early_write_config_byte(hose,
+ current_bus,
+ pci_devfn,
+ PCI_SECONDARY_BUS,
+ sub_bus + 1);
+ early_write_config_byte(hose,
+ current_bus,
+ pci_devfn,
+ PCI_SUBORDINATE_BUS,
+ 0xff);
+
+ /* Round memory allocator to 4KB boundary */
+ pciauto_upper_memspc &= ~(0x1000 - 1);
+ *memsave = pciauto_upper_memspc;
+
+ /* Round I/O allocator to 4 byte boundary */
+ pciauto_upper_iospc &= ~(0x4 - 1);
+ *iosave = pciauto_upper_iospc;
+
+ /* Set up memory and I/O filter limits, assume 32-bit I/O space */
+ early_write_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ 0x20,
+ pciauto_upper_memspc - 1);
+ early_write_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ 0x30,
+ pciauto_upper_iospc - 1);
+}
+
+void __init pciauto_postscan_setup_cardbus_bridge(struct pci_controller *hose,
+ int current_bus,
+ int pci_devfn,
+ int sub_bus,
+ int *iosave,
+ int *memsave)
+{
+ int cmdstat;
+
+ /*
+ * Configure subordinate bus number. The PCI subsystem
+ * bus scan will renumber buses (reserving three additional
+ * for this PCI<->CardBus bridge for the case where a CardBus
+ * adapter contains a P2P or CB2CB bridge.
+ */
+ early_write_config_byte(hose,
+ current_bus,
+ pci_devfn,
+ PCI_SUBORDINATE_BUS,
+ sub_bus);
+
+ /*
+ * Reserve an additional 4MB for mem space and 16KB for
+ * I/O space. This should cover any additional space
+ * requirement of unusual CardBus devices with
+ * additional bridges that can consume more address space.
+ *
+ * Although pcmcia-cs currently will reprogram bridge
+ * windows, the goal is to add an option to leave them
+ * alone and use the bridge window ranges as the regions
+ * that are searched for free resources upon hot-insertion
+ * of a device. This will allow a PCI<->CardBus bridge
+ * configured by this routine to happily live behind a
+ * P2P bridge in a system.
+ */
+ pciauto_upper_memspc -= 0x00400000;
+ pciauto_upper_iospc -= 0x00004000;
+
+ /* Round memory allocator to 4KB boundary */
+ pciauto_upper_memspc &= ~(0x1000 - 1);
+
+ early_write_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ 0x1c,
+ pciauto_upper_memspc);
+
+ /* Round I/O allocator to 4 byte boundary */
+ pciauto_upper_iospc &= ~(0x4 - 1);
+ early_write_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ 0x2c,
+ pciauto_upper_iospc);
+
+ /* Enable memory and I/O accesses, enable bus master */
+ early_read_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ PCI_COMMAND,
+ &cmdstat);
+ early_write_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ PCI_COMMAND,
+ cmdstat |
+ PCI_COMMAND_IO |
+ PCI_COMMAND_MEMORY |
+ PCI_COMMAND_MASTER);
+}
+
+int __init pciauto_bus_scan(struct pci_controller *hose, int current_bus)
+{
+ int sub_bus, pci_devfn, pci_class, cmdstat, found_multi = 0;
+ unsigned short vid;
+ unsigned char header_type;
+
+ /*
+ * Fetch our I/O and memory space upper boundaries used
+ * to allocated base addresses on this hose.
+ */
+ if (current_bus == hose->first_busno) {
+ pciauto_upper_iospc = hose->io_space.end + 1;
+ pciauto_upper_memspc = hose->mem_space.end + 1;
+ }
+
+ sub_bus = current_bus;
+
+ for (pci_devfn = 0; pci_devfn < 0xff; pci_devfn++) {
+ /* Skip our host bridge */
+ if ( (current_bus == hose->first_busno) && (pci_devfn == 0) )
+ continue;
+
+ if (PCI_FUNC(pci_devfn) && !found_multi)
+ continue;
+
+ /* If config space read fails from this device, move on */
+ if (early_read_config_byte(hose,
+ current_bus,
+ pci_devfn,
+ PCI_HEADER_TYPE,
+ &header_type))
+ continue;
+
+ if (!PCI_FUNC(pci_devfn))
+ found_multi = header_type & 0x80;
+
+ early_read_config_word(hose,
+ current_bus,
+ pci_devfn,
+ PCI_VENDOR_ID,
+ &vid);
+
+ if (vid != 0xffff) {
+ early_read_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ PCI_CLASS_REVISION, &pci_class);
+ if ( (pci_class >> 16) == PCI_CLASS_BRIDGE_PCI ) {
+ int iosave, memsave;
+
+ DBG("PCI Autoconfig: Found P2P bridge, device %d\n", PCI_SLOT(pci_devfn));
+ /* Allocate PCI I/O and/or memory space */
+ pciauto_setup_bars(hose,
+ current_bus,
+ pci_devfn,
+ PCI_BASE_ADDRESS_1);
+
+ pciauto_prescan_setup_bridge(hose,
+ current_bus,
+ pci_devfn,
+ sub_bus,
+ &iosave,
+ &memsave);
+ sub_bus = pciauto_bus_scan(hose, sub_bus+1);
+ pciauto_postscan_setup_bridge(hose,
+ current_bus,
+ pci_devfn,
+ sub_bus,
+ &iosave,
+ &memsave);
+ } else if ((pci_class >> 16) == PCI_CLASS_BRIDGE_CARDBUS) {
+ int iosave, memsave;
+
+ DBG("PCI Autoconfig: Found CardBus bridge, device %d function %d\n", PCI_SLOT(pci_devfn), PCI_FUNC(pci_devfn));
+ /* Place CardBus Socket/ExCA registers */
+ pciauto_setup_bars(hose,
+ current_bus,
+ pci_devfn,
+ PCI_BASE_ADDRESS_0);
+
+ pciauto_prescan_setup_cardbus_bridge(hose,
+ current_bus,
+ pci_devfn,
+ sub_bus,
+ &iosave,
+ &memsave);
+ sub_bus = pciauto_bus_scan(hose, sub_bus+1);
+ pciauto_postscan_setup_cardbus_bridge(hose,
+ current_bus,
+ pci_devfn,
+ sub_bus,
+ &iosave,
+ &memsave);
+ } else {
+ if ((pci_class >> 16) == PCI_CLASS_STORAGE_IDE) {
+ unsigned char prg_iface;
+
+ early_read_config_byte(hose,
+ current_bus,
+ pci_devfn,
+ PCI_CLASS_PROG,
+ &prg_iface);
+ if (!(prg_iface & PCIAUTO_IDE_MODE_MASK)) {
+ DBG("PCI Autoconfig: Skipping legacy mode IDE controller\n");
+ continue;
+ }
+ }
+ /* Allocate PCI I/O and/or memory space */
+ pciauto_setup_bars(hose,
+ current_bus,
+ pci_devfn,
+ PCI_BASE_ADDRESS_5);
+
+ /*
+ * Enable some standard settings
+ */
+ early_read_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ PCI_COMMAND,
+ &cmdstat);
+ early_write_config_dword(hose,
+ current_bus,
+ pci_devfn,
+ PCI_COMMAND,
+ cmdstat |
+ PCI_COMMAND_IO |
+ PCI_COMMAND_MEMORY |
+ PCI_COMMAND_MASTER);
+ early_write_config_byte(hose,
+ current_bus,
+ pci_devfn,
+ PCI_LATENCY_TIMER,
+ 0x80);
+ }
+ }
+ }
+ return sub_bus;
+}