diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/pci/pcie/portdrv_core.c |
Linux-2.6.12-rc2v2.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 'drivers/pci/pcie/portdrv_core.c')
-rw-r--r-- | drivers/pci/pcie/portdrv_core.c | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c new file mode 100644 index 000000000000..127f64f85dc5 --- /dev/null +++ b/drivers/pci/pcie/portdrv_core.c @@ -0,0 +1,434 @@ +/* + * File: portdrv_core.c + * Purpose: PCI Express Port Bus Driver's Core Functions + * + * Copyright (C) 2004 Intel + * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/pcieport_if.h> + +#include "portdrv.h" + +extern int pcie_mch_quirk; /* MSI-quirk Indicator */ + +static int pcie_port_probe_service(struct device *dev) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + int status = -ENODEV; + + if (!dev || !dev->driver) + return status; + + driver = to_service_driver(dev->driver); + if (!driver || !driver->probe) + return status; + + pciedev = to_pcie_device(dev); + status = driver->probe(pciedev, driver->id_table); + if (!status) { + printk(KERN_DEBUG "Load service driver %s on pcie device %s\n", + driver->name, dev->bus_id); + get_device(dev); + } + return status; +} + +static int pcie_port_remove_service(struct device *dev) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (!dev || !dev->driver) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(dev->driver); + if (driver && driver->remove) { + printk(KERN_DEBUG "Unload service driver %s on pcie device %s\n", + driver->name, dev->bus_id); + driver->remove(pciedev); + put_device(dev); + } + return 0; +} + +static void pcie_port_shutdown_service(struct device *dev) {} + +static int pcie_port_suspend_service(struct device *dev, u32 state, u32 level) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (!dev || !dev->driver) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(dev->driver); + if (driver && driver->suspend) + driver->suspend(pciedev, state); + return 0; +} + +static int pcie_port_resume_service(struct device *dev, u32 state) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (!dev || !dev->driver) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(dev->driver); + + if (driver && driver->resume) + driver->resume(pciedev); + return 0; +} + +/* + * release_pcie_device + * + * Being invoked automatically when device is being removed + * in response to device_unregister(dev) call. + * Release all resources being claimed. + */ +static void release_pcie_device(struct device *dev) +{ + printk(KERN_DEBUG "Free Port Service[%s]\n", dev->bus_id); + kfree(to_pcie_device(dev)); +} + +static int is_msi_quirked(struct pci_dev *dev) +{ + int port_type, quirk = 0; + u16 reg16; + + pci_read_config_word(dev, + pci_find_capability(dev, PCI_CAP_ID_EXP) + + PCIE_CAPABILITIES_REG, ®16); + port_type = (reg16 >> 4) & PORT_TYPE_MASK; + switch(port_type) { + case PCIE_RC_PORT: + if (pcie_mch_quirk == 1) + quirk = 1; + break; + case PCIE_SW_UPSTREAM_PORT: + case PCIE_SW_DOWNSTREAM_PORT: + default: + break; + } + return quirk; +} + +static int assign_interrupt_mode(struct pci_dev *dev, int *vectors, int mask) +{ + int i, pos, nvec, status = -EINVAL; + int interrupt_mode = PCIE_PORT_INTx_MODE; + + /* Set INTx as default */ + for (i = 0, nvec = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { + if (mask & (1 << i)) + nvec++; + vectors[i] = dev->irq; + } + + /* Check MSI quirk */ + if (is_msi_quirked(dev)) + return interrupt_mode; + + /* Select MSI-X over MSI if supported */ + pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); + if (pos) { + struct msix_entry msix_entries[PCIE_PORT_DEVICE_MAXSERVICES] = + {{0, 0}, {0, 1}, {0, 2}, {0, 3}}; + printk("%s Found MSIX capability\n", __FUNCTION__); + status = pci_enable_msix(dev, msix_entries, nvec); + if (!status) { + int j = 0; + + interrupt_mode = PCIE_PORT_MSIX_MODE; + for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { + if (mask & (1 << i)) + vectors[i] = msix_entries[j++].vector; + } + } + } + if (status) { + pos = pci_find_capability(dev, PCI_CAP_ID_MSI); + if (pos) { + printk("%s Found MSI capability\n", __FUNCTION__); + status = pci_enable_msi(dev); + if (!status) { + interrupt_mode = PCIE_PORT_MSI_MODE; + for (i = 0;i < PCIE_PORT_DEVICE_MAXSERVICES;i++) + vectors[i] = dev->irq; + } + } + } + return interrupt_mode; +} + +static int get_port_device_capability(struct pci_dev *dev) +{ + int services = 0, pos; + u16 reg16; + u32 reg32; + + pos = pci_find_capability(dev, PCI_CAP_ID_EXP); + pci_read_config_word(dev, pos + PCIE_CAPABILITIES_REG, ®16); + /* Hot-Plug Capable */ + if (reg16 & PORT_TO_SLOT_MASK) { + pci_read_config_dword(dev, + pos + PCIE_SLOT_CAPABILITIES_REG, ®32); + if (reg32 & SLOT_HP_CAPABLE_MASK) + services |= PCIE_PORT_SERVICE_HP; + } + /* PME Capable */ + pos = pci_find_capability(dev, PCI_CAP_ID_PME); + if (pos) + services |= PCIE_PORT_SERVICE_PME; + + pos = PCI_CFG_SPACE_SIZE; + while (pos) { + pci_read_config_dword(dev, pos, ®32); + switch (reg32 & 0xffff) { + case PCI_EXT_CAP_ID_ERR: + services |= PCIE_PORT_SERVICE_AER; + pos = reg32 >> 20; + break; + case PCI_EXT_CAP_ID_VC: + services |= PCIE_PORT_SERVICE_VC; + pos = reg32 >> 20; + break; + default: + pos = 0; + break; + } + } + + return services; +} + +static void pcie_device_init(struct pci_dev *parent, struct pcie_device *dev, + int port_type, int service_type, int irq, int irq_mode) +{ + struct device *device; + + dev->port = parent; + dev->interrupt_mode = irq_mode; + dev->irq = irq; + dev->id.vendor = parent->vendor; + dev->id.device = parent->device; + dev->id.port_type = port_type; + dev->id.service_type = (1 << service_type); + + /* Initialize generic device interface */ + device = &dev->device; + memset(device, 0, sizeof(struct device)); + INIT_LIST_HEAD(&device->node); + INIT_LIST_HEAD(&device->children); + INIT_LIST_HEAD(&device->bus_list); + device->bus = &pcie_port_bus_type; + device->driver = NULL; + device->driver_data = NULL; + device->release = release_pcie_device; /* callback to free pcie dev */ + sprintf(&device->bus_id[0], "pcie%02x", + get_descriptor_id(port_type, service_type)); + device->parent = &parent->dev; +} + +static struct pcie_device* alloc_pcie_device(struct pci_dev *parent, + int port_type, int service_type, int irq, int irq_mode) +{ + struct pcie_device *device; + + device = kmalloc(sizeof(struct pcie_device), GFP_KERNEL); + if (!device) + return NULL; + + memset(device, 0, sizeof(struct pcie_device)); + pcie_device_init(parent, device, port_type, service_type, irq,irq_mode); + printk(KERN_DEBUG "Allocate Port Service[%s]\n", device->device.bus_id); + return device; +} + +int pcie_port_device_probe(struct pci_dev *dev) +{ + int pos, type; + u16 reg; + + if (!(pos = pci_find_capability(dev, PCI_CAP_ID_EXP))) + return -ENODEV; + + pci_read_config_word(dev, pos + PCIE_CAPABILITIES_REG, ®); + type = (reg >> 4) & PORT_TYPE_MASK; + if ( type == PCIE_RC_PORT || type == PCIE_SW_UPSTREAM_PORT || + type == PCIE_SW_DOWNSTREAM_PORT ) + return 0; + + return -ENODEV; +} + +int pcie_port_device_register(struct pci_dev *dev) +{ + int status, type, capabilities, irq_mode, i; + int vectors[PCIE_PORT_DEVICE_MAXSERVICES]; + u16 reg16; + + /* Get port type */ + pci_read_config_word(dev, + pci_find_capability(dev, PCI_CAP_ID_EXP) + + PCIE_CAPABILITIES_REG, ®16); + type = (reg16 >> 4) & PORT_TYPE_MASK; + + /* Now get port services */ + capabilities = get_port_device_capability(dev); + irq_mode = assign_interrupt_mode(dev, vectors, capabilities); + + /* Allocate child services if any */ + for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { + struct pcie_device *child; + + if (capabilities & (1 << i)) { + child = alloc_pcie_device( + dev, /* parent */ + type, /* port type */ + i, /* service type */ + vectors[i], /* irq */ + irq_mode /* interrupt mode */); + if (child) { + status = device_register(&child->device); + if (status) { + kfree(child); + continue; + } + get_device(&child->device); + } + } + } + return 0; +} + +#ifdef CONFIG_PM +int pcie_port_device_suspend(struct pci_dev *dev, u32 state) +{ + struct list_head *head, *tmp; + struct device *parent, *child; + struct device_driver *driver; + struct pcie_port_service_driver *service_driver; + + parent = &dev->dev; + head = &parent->children; + tmp = head->next; + while (head != tmp) { + child = container_of(tmp, struct device, node); + tmp = tmp->next; + if (child->bus != &pcie_port_bus_type) + continue; + driver = child->driver; + if (!driver) + continue; + service_driver = to_service_driver(driver); + if (service_driver->suspend) + service_driver->suspend(to_pcie_device(child), state); + } + return 0; +} + +int pcie_port_device_resume(struct pci_dev *dev) +{ + struct list_head *head, *tmp; + struct device *parent, *child; + struct device_driver *driver; + struct pcie_port_service_driver *service_driver; + + parent = &dev->dev; + head = &parent->children; + tmp = head->next; + while (head != tmp) { + child = container_of(tmp, struct device, node); + tmp = tmp->next; + if (child->bus != &pcie_port_bus_type) + continue; + driver = child->driver; + if (!driver) + continue; + service_driver = to_service_driver(driver); + if (service_driver->resume) + service_driver->resume(to_pcie_device(child)); + } + return 0; + +} +#endif + +void pcie_port_device_remove(struct pci_dev *dev) +{ + struct list_head *head, *tmp; + struct device *parent, *child; + struct device_driver *driver; + struct pcie_port_service_driver *service_driver; + int interrupt_mode = PCIE_PORT_INTx_MODE; + + parent = &dev->dev; + head = &parent->children; + tmp = head->next; + while (head != tmp) { + child = container_of(tmp, struct device, node); + tmp = tmp->next; + if (child->bus != &pcie_port_bus_type) + continue; + driver = child->driver; + if (driver) { + service_driver = to_service_driver(driver); + if (service_driver->remove) + service_driver->remove(to_pcie_device(child)); + } + interrupt_mode = (to_pcie_device(child))->interrupt_mode; + put_device(child); + device_unregister(child); + } + /* Switch to INTx by default if MSI enabled */ + if (interrupt_mode == PCIE_PORT_MSIX_MODE) + pci_disable_msix(dev); + else if (interrupt_mode == PCIE_PORT_MSI_MODE) + pci_disable_msi(dev); +} + +void pcie_port_bus_register(void) +{ + bus_register(&pcie_port_bus_type); +} + +void pcie_port_bus_unregister(void) +{ + bus_unregister(&pcie_port_bus_type); +} + +int pcie_port_service_register(struct pcie_port_service_driver *new) +{ + new->driver.name = (char *)new->name; + new->driver.bus = &pcie_port_bus_type; + new->driver.probe = pcie_port_probe_service; + new->driver.remove = pcie_port_remove_service; + new->driver.shutdown = pcie_port_shutdown_service; + new->driver.suspend = pcie_port_suspend_service; + new->driver.resume = pcie_port_resume_service; + + return driver_register(&new->driver); +} + +void pcie_port_service_unregister(struct pcie_port_service_driver *new) +{ + driver_unregister(&new->driver); +} + +EXPORT_SYMBOL(pcie_port_service_register); +EXPORT_SYMBOL(pcie_port_service_unregister); |