summaryrefslogtreecommitdiffstats
path: root/drivers/pci/hotplug/acpiphp_pci.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/hotplug/acpiphp_pci.c')
-rw-r--r--drivers/pci/hotplug/acpiphp_pci.c449
1 files changed, 449 insertions, 0 deletions
diff --git a/drivers/pci/hotplug/acpiphp_pci.c b/drivers/pci/hotplug/acpiphp_pci.c
new file mode 100644
index 00000000000..54d97c9d1df
--- /dev/null
+++ b/drivers/pci/hotplug/acpiphp_pci.c
@@ -0,0 +1,449 @@
+/*
+ * ACPI PCI HotPlug PCI configuration space management
+ *
+ * Copyright (C) 1995,2001 Compaq Computer Corporation
+ * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2001,2002 IBM Corp.
+ * Copyright (C) 2002 Takayoshi Kochi (t-kochi@bq.jp.nec.com)
+ * Copyright (C) 2002 Hiroshi Aono (h-aono@ap.jp.nec.com)
+ * Copyright (C) 2002 NEC Corporation
+ *
+ * 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 published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * 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, GOOD TITLE or
+ * NON INFRINGEMENT. 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Send feedback to <t-kochi@bq.jp.nec.com>
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/acpi.h>
+#include "../pci.h"
+#include "pci_hotplug.h"
+#include "acpiphp.h"
+
+#define MY_NAME "acpiphp_pci"
+
+
+/* allocate mem/pmem/io resource to a new function */
+static int init_config_space (struct acpiphp_func *func)
+{
+ u32 bar, len;
+ u32 address[] = {
+ PCI_BASE_ADDRESS_0,
+ PCI_BASE_ADDRESS_1,
+ PCI_BASE_ADDRESS_2,
+ PCI_BASE_ADDRESS_3,
+ PCI_BASE_ADDRESS_4,
+ PCI_BASE_ADDRESS_5,
+ 0
+ };
+ int count;
+ struct acpiphp_bridge *bridge;
+ struct pci_resource *res;
+ struct pci_bus *pbus;
+ int bus, device, function;
+ unsigned int devfn;
+ u16 tmp;
+
+ bridge = func->slot->bridge;
+ pbus = bridge->pci_bus;
+ bus = bridge->bus;
+ device = func->slot->device;
+ function = func->function;
+ devfn = PCI_DEVFN(device, function);
+
+ for (count = 0; address[count]; count++) { /* for 6 BARs */
+ pci_bus_write_config_dword(pbus, devfn,
+ address[count], 0xFFFFFFFF);
+ pci_bus_read_config_dword(pbus, devfn, address[count], &bar);
+
+ if (!bar) /* This BAR is not implemented */
+ continue;
+
+ dbg("Device %02x.%02x BAR %d wants %x\n", device, function, count, bar);
+
+ if (bar & PCI_BASE_ADDRESS_SPACE_IO) {
+ /* This is IO */
+
+ len = bar & (PCI_BASE_ADDRESS_IO_MASK & 0xFFFF);
+ len = len & ~(len - 1);
+
+ dbg("len in IO %x, BAR %d\n", len, count);
+
+ spin_lock(&bridge->res_lock);
+ res = acpiphp_get_io_resource(&bridge->io_head, len);
+ spin_unlock(&bridge->res_lock);
+
+ if (!res) {
+ err("cannot allocate requested io for %02x:%02x.%d len %x\n",
+ bus, device, function, len);
+ return -1;
+ }
+ pci_bus_write_config_dword(pbus, devfn,
+ address[count],
+ (u32)res->base);
+ res->next = func->io_head;
+ func->io_head = res;
+
+ } else {
+ /* This is Memory */
+ if (bar & PCI_BASE_ADDRESS_MEM_PREFETCH) {
+ /* pfmem */
+
+ len = bar & 0xFFFFFFF0;
+ len = ~len + 1;
+
+ dbg("len in PFMEM %x, BAR %d\n", len, count);
+
+ spin_lock(&bridge->res_lock);
+ res = acpiphp_get_resource(&bridge->p_mem_head, len);
+ spin_unlock(&bridge->res_lock);
+
+ if (!res) {
+ err("cannot allocate requested pfmem for %02x:%02x.%d len %x\n",
+ bus, device, function, len);
+ return -1;
+ }
+
+ pci_bus_write_config_dword(pbus, devfn,
+ address[count],
+ (u32)res->base);
+
+ if (bar & PCI_BASE_ADDRESS_MEM_TYPE_64) { /* takes up another dword */
+ dbg("inside the pfmem 64 case, count %d\n", count);
+ count += 1;
+ pci_bus_write_config_dword(pbus, devfn,
+ address[count],
+ (u32)(res->base >> 32));
+ }
+
+ res->next = func->p_mem_head;
+ func->p_mem_head = res;
+
+ } else {
+ /* regular memory */
+
+ len = bar & 0xFFFFFFF0;
+ len = ~len + 1;
+
+ dbg("len in MEM %x, BAR %d\n", len, count);
+
+ spin_lock(&bridge->res_lock);
+ res = acpiphp_get_resource(&bridge->mem_head, len);
+ spin_unlock(&bridge->res_lock);
+
+ if (!res) {
+ err("cannot allocate requested pfmem for %02x:%02x.%d len %x\n",
+ bus, device, function, len);
+ return -1;
+ }
+
+ pci_bus_write_config_dword(pbus, devfn,
+ address[count],
+ (u32)res->base);
+
+ if (bar & PCI_BASE_ADDRESS_MEM_TYPE_64) {
+ /* takes up another dword */
+ dbg("inside mem 64 case, reg. mem, count %d\n", count);
+ count += 1;
+ pci_bus_write_config_dword(pbus, devfn,
+ address[count],
+ (u32)(res->base >> 32));
+ }
+
+ res->next = func->mem_head;
+ func->mem_head = res;
+
+ }
+ }
+ }
+
+ /* disable expansion rom */
+ pci_bus_write_config_dword(pbus, devfn, PCI_ROM_ADDRESS, 0x00000000);
+
+ /* set PCI parameters from _HPP */
+ pci_bus_write_config_byte(pbus, devfn, PCI_CACHE_LINE_SIZE,
+ bridge->hpp.cache_line_size);
+ pci_bus_write_config_byte(pbus, devfn, PCI_LATENCY_TIMER,
+ bridge->hpp.latency_timer);
+
+ pci_bus_read_config_word(pbus, devfn, PCI_COMMAND, &tmp);
+ if (bridge->hpp.enable_SERR)
+ tmp |= PCI_COMMAND_SERR;
+ if (bridge->hpp.enable_PERR)
+ tmp |= PCI_COMMAND_PARITY;
+ pci_bus_write_config_word(pbus, devfn, PCI_COMMAND, tmp);
+
+ return 0;
+}
+
+/* detect_used_resource - subtract resource under dev from bridge */
+static int detect_used_resource (struct acpiphp_bridge *bridge, struct pci_dev *dev)
+{
+ int count;
+
+ dbg("Device %s\n", pci_name(dev));
+
+ for (count = 0; count < DEVICE_COUNT_RESOURCE; count++) {
+ struct pci_resource *res;
+ struct pci_resource **head;
+ unsigned long base = dev->resource[count].start;
+ unsigned long len = dev->resource[count].end - base + 1;
+ unsigned long flags = dev->resource[count].flags;
+
+ if (!flags)
+ continue;
+
+ dbg("BAR[%d] 0x%lx - 0x%lx (0x%lx)\n", count, base,
+ base + len - 1, flags);
+
+ if (flags & IORESOURCE_IO) {
+ head = &bridge->io_head;
+ } else if (flags & IORESOURCE_PREFETCH) {
+ head = &bridge->p_mem_head;
+ } else {
+ head = &bridge->mem_head;
+ }
+
+ spin_lock(&bridge->res_lock);
+ res = acpiphp_get_resource_with_base(head, base, len);
+ spin_unlock(&bridge->res_lock);
+ if (res)
+ kfree(res);
+ }
+
+ return 0;
+}
+
+
+/**
+ * acpiphp_detect_pci_resource - detect resources under bridge
+ * @bridge: detect all resources already used under this bridge
+ *
+ * collect all resources already allocated for all devices under a bridge.
+ */
+int acpiphp_detect_pci_resource (struct acpiphp_bridge *bridge)
+{
+ struct list_head *l;
+ struct pci_dev *dev;
+
+ list_for_each (l, &bridge->pci_bus->devices) {
+ dev = pci_dev_b(l);
+ detect_used_resource(bridge, dev);
+ }
+
+ return 0;
+}
+
+
+/**
+ * acpiphp_init_slot_resource - gather resource usage information of a slot
+ * @slot: ACPI slot object to be checked, should have valid pci_dev member
+ *
+ * TBD: PCI-to-PCI bridge case
+ * use pci_dev->resource[]
+ */
+int acpiphp_init_func_resource (struct acpiphp_func *func)
+{
+ u64 base;
+ u32 bar, len;
+ u32 address[] = {
+ PCI_BASE_ADDRESS_0,
+ PCI_BASE_ADDRESS_1,
+ PCI_BASE_ADDRESS_2,
+ PCI_BASE_ADDRESS_3,
+ PCI_BASE_ADDRESS_4,
+ PCI_BASE_ADDRESS_5,
+ 0
+ };
+ int count;
+ struct pci_resource *res;
+ struct pci_dev *dev;
+
+ dev = func->pci_dev;
+ dbg("Hot-pluggable device %s\n", pci_name(dev));
+
+ for (count = 0; address[count]; count++) { /* for 6 BARs */
+ pci_read_config_dword(dev, address[count], &bar);
+
+ if (!bar) /* This BAR is not implemented */
+ continue;
+
+ pci_write_config_dword(dev, address[count], 0xFFFFFFFF);
+ pci_read_config_dword(dev, address[count], &len);
+
+ if (len & PCI_BASE_ADDRESS_SPACE_IO) {
+ /* This is IO */
+ base = bar & 0xFFFFFFFC;
+ len = len & (PCI_BASE_ADDRESS_IO_MASK & 0xFFFF);
+ len = len & ~(len - 1);
+
+ dbg("BAR[%d] %08x - %08x (IO)\n", count, (u32)base, (u32)base + len - 1);
+
+ res = acpiphp_make_resource(base, len);
+ if (!res)
+ goto no_memory;
+
+ res->next = func->io_head;
+ func->io_head = res;
+
+ } else {
+ /* This is Memory */
+ base = bar & 0xFFFFFFF0;
+ if (len & PCI_BASE_ADDRESS_MEM_PREFETCH) {
+ /* pfmem */
+
+ len &= 0xFFFFFFF0;
+ len = ~len + 1;
+
+ if (len & PCI_BASE_ADDRESS_MEM_TYPE_64) { /* takes up another dword */
+ dbg("prefetch mem 64\n");
+ count += 1;
+ }
+ dbg("BAR[%d] %08x - %08x (PMEM)\n", count, (u32)base, (u32)base + len - 1);
+ res = acpiphp_make_resource(base, len);
+ if (!res)
+ goto no_memory;
+
+ res->next = func->p_mem_head;
+ func->p_mem_head = res;
+
+ } else {
+ /* regular memory */
+
+ len &= 0xFFFFFFF0;
+ len = ~len + 1;
+
+ if (len & PCI_BASE_ADDRESS_MEM_TYPE_64) {
+ /* takes up another dword */
+ dbg("mem 64\n");
+ count += 1;
+ }
+ dbg("BAR[%d] %08x - %08x (MEM)\n", count, (u32)base, (u32)base + len - 1);
+ res = acpiphp_make_resource(base, len);
+ if (!res)
+ goto no_memory;
+
+ res->next = func->mem_head;
+ func->mem_head = res;
+
+ }
+ }
+
+ pci_write_config_dword(dev, address[count], bar);
+ }
+#if 1
+ acpiphp_dump_func_resource(func);
+#endif
+
+ return 0;
+
+ no_memory:
+ err("out of memory\n");
+ acpiphp_free_resource(&func->io_head);
+ acpiphp_free_resource(&func->mem_head);
+ acpiphp_free_resource(&func->p_mem_head);
+
+ return -1;
+}
+
+
+/**
+ * acpiphp_configure_slot - allocate PCI resources
+ * @slot: slot to be configured
+ *
+ * initializes a PCI functions on a device inserted
+ * into the slot
+ *
+ */
+int acpiphp_configure_slot (struct acpiphp_slot *slot)
+{
+ struct acpiphp_func *func;
+ struct list_head *l;
+ u8 hdr;
+ u32 dvid;
+ int retval = 0;
+ int is_multi = 0;
+
+ pci_bus_read_config_byte(slot->bridge->pci_bus,
+ PCI_DEVFN(slot->device, 0),
+ PCI_HEADER_TYPE, &hdr);
+
+ if (hdr & 0x80)
+ is_multi = 1;
+
+ list_for_each (l, &slot->funcs) {
+ func = list_entry(l, struct acpiphp_func, sibling);
+ if (is_multi || func->function == 0) {
+ pci_bus_read_config_dword(slot->bridge->pci_bus,
+ PCI_DEVFN(slot->device,
+ func->function),
+ PCI_VENDOR_ID, &dvid);
+ if (dvid != 0xffffffff) {
+ retval = init_config_space(func);
+ if (retval)
+ break;
+ }
+ }
+ }
+
+ return retval;
+}
+
+/**
+ * acpiphp_configure_function - configure PCI function
+ * @func: function to be configured
+ *
+ * initializes a PCI functions on a device inserted
+ * into the slot
+ *
+ */
+int acpiphp_configure_function (struct acpiphp_func *func)
+{
+ /* all handled by the pci core now */
+ return 0;
+}
+
+/**
+ * acpiphp_unconfigure_function - unconfigure PCI function
+ * @func: function to be unconfigured
+ *
+ */
+void acpiphp_unconfigure_function (struct acpiphp_func *func)
+{
+ struct acpiphp_bridge *bridge;
+
+ /* if pci_dev is NULL, ignore it */
+ if (!func->pci_dev)
+ return;
+
+ pci_remove_bus_device(func->pci_dev);
+
+ /* free all resources */
+ bridge = func->slot->bridge;
+
+ spin_lock(&bridge->res_lock);
+ acpiphp_move_resource(&func->io_head, &bridge->io_head);
+ acpiphp_move_resource(&func->mem_head, &bridge->mem_head);
+ acpiphp_move_resource(&func->p_mem_head, &bridge->p_mem_head);
+ acpiphp_move_resource(&func->bus_head, &bridge->bus_head);
+ spin_unlock(&bridge->res_lock);
+}