Skip to content
Snippets Groups Projects
pci.c 80.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     *	PCI Bus Services, see include/linux/pci.h for further explanation.
     *
     *	Copyright 1993 -- 1997 Drew Eckhardt, Frederic Potter,
     *	David Mosberger-Tang
     *
     *	Copyright 1997 -- 2000 Martin Mares <mj@ucw.cz>
     */
    
    #include <linux/kernel.h>
    #include <linux/delay.h>
    #include <linux/init.h>
    #include <linux/pci.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/module.h>
    #include <linux/spinlock.h>
    
    #include <linux/string.h>
    
    #include <linux/log2.h>
    
    #include <linux/pci-aspm.h>
    
    #include <linux/pm_wakeup.h>
    
    #include <linux/interrupt.h>
    
    #include <linux/pm_runtime.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    const char *pci_power_names[] = {
    	"error", "D0", "D1", "D2", "D3hot", "D3cold", "unknown",
    };
    EXPORT_SYMBOL_GPL(pci_power_names);
    
    
    int isa_dma_bridge_buggy;
    EXPORT_SYMBOL(isa_dma_bridge_buggy);
    
    int pci_pci_problems;
    EXPORT_SYMBOL(pci_pci_problems);
    
    
    unsigned int pci_pm_d3_delay;
    
    
    static void pci_pme_list_scan(struct work_struct *work);
    
    static LIST_HEAD(pci_pme_list);
    static DEFINE_MUTEX(pci_pme_list_mutex);
    static DECLARE_DELAYED_WORK(pci_pme_work, pci_pme_list_scan);
    
    struct pci_pme_device {
    	struct list_head list;
    	struct pci_dev *dev;
    };
    
    #define PME_TIMEOUT 1000 /* How long between PME checks */
    
    
    static void pci_dev_d3_sleep(struct pci_dev *dev)
    {
    	unsigned int delay = dev->d3_delay;
    
    	if (delay < pci_pm_d3_delay)
    		delay = pci_pm_d3_delay;
    
    	msleep(delay);
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    #ifdef CONFIG_PCI_DOMAINS
    int pci_domains_supported = 1;
    #endif
    
    
    #define DEFAULT_CARDBUS_IO_SIZE		(256)
    #define DEFAULT_CARDBUS_MEM_SIZE	(64*1024*1024)
    /* pci=cbmemsize=nnM,cbiosize=nn can override this */
    unsigned long pci_cardbus_io_size = DEFAULT_CARDBUS_IO_SIZE;
    unsigned long pci_cardbus_mem_size = DEFAULT_CARDBUS_MEM_SIZE;
    
    
    #define DEFAULT_HOTPLUG_IO_SIZE		(256)
    #define DEFAULT_HOTPLUG_MEM_SIZE	(2*1024*1024)
    /* pci=hpmemsize=nnM,hpiosize=nn can override this */
    unsigned long pci_hotplug_io_size  = DEFAULT_HOTPLUG_IO_SIZE;
    unsigned long pci_hotplug_mem_size = DEFAULT_HOTPLUG_MEM_SIZE;
    
    
    /*
     * The default CLS is used if arch didn't set CLS explicitly and not
     * all pci devices agree on the same value.  Arch can override either
     * the dfl or actual value as it sees fit.  Don't forget this is
     * measured in 32-bit words, not bytes.
     */
    
    u8 pci_dfl_cache_line_size __devinitdata = L1_CACHE_BYTES >> 2;
    
    u8 pci_cache_line_size;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /**
     * pci_bus_max_busnr - returns maximum PCI bus number of given bus' children
     * @bus: pointer to PCI bus structure to search
     *
     * Given a PCI bus, returns the highest PCI bus number present in the set
     * including the given PCI bus and its list of child PCI buses.
     */
    
    unsigned char pci_bus_max_busnr(struct pci_bus* bus)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct list_head *tmp;
    	unsigned char max, n;
    
    
    	max = bus->subordinate;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	list_for_each(tmp, &bus->children) {
    		n = pci_bus_max_busnr(pci_bus_b(tmp));
    		if(n > max)
    			max = n;
    	}
    	return max;
    }
    
    EXPORT_SYMBOL_GPL(pci_bus_max_busnr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    #ifdef CONFIG_HAS_IOMEM
    void __iomem *pci_ioremap_bar(struct pci_dev *pdev, int bar)
    {
    	/*
    	 * Make sure the BAR is actually a memory resource, not an IO resource
    	 */
    	if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) {
    		WARN_ON(1);
    		return NULL;
    	}
    	return ioremap_nocache(pci_resource_start(pdev, bar),
    				     pci_resource_len(pdev, bar));
    }
    EXPORT_SYMBOL_GPL(pci_ioremap_bar);
    #endif
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /**
     * pci_max_busnr - returns maximum PCI bus number
     *
     * Returns the highest PCI bus number present in the system global list of
     * PCI buses.
     */
    unsigned char __devinit
    pci_max_busnr(void)
    {
    	struct pci_bus *bus = NULL;
    	unsigned char max, n;
    
    	max = 0;
    	while ((bus = pci_find_next_bus(bus)) != NULL) {
    		n = pci_bus_max_busnr(bus);
    		if(n > max)
    			max = n;
    	}
    	return max;
    }
    
    
    #define PCI_FIND_CAP_TTL	48
    
    static int __pci_find_next_cap_ttl(struct pci_bus *bus, unsigned int devfn,
    				   u8 pos, int cap, int *ttl)
    
    		pci_bus_read_config_byte(bus, devfn, pos, &pos);
    		if (pos < 0x40)
    			break;
    		pos &= ~3;
    		pci_bus_read_config_byte(bus, devfn, pos + PCI_CAP_LIST_ID,
    					 &id);
    		if (id == 0xff)
    			break;
    		if (id == cap)
    			return pos;
    		pos += PCI_CAP_LIST_NEXT;
    	}
    	return 0;
    }
    
    
    static int __pci_find_next_cap(struct pci_bus *bus, unsigned int devfn,
    			       u8 pos, int cap)
    {
    	int ttl = PCI_FIND_CAP_TTL;
    
    	return __pci_find_next_cap_ttl(bus, devfn, pos, cap, &ttl);
    }
    
    
    int pci_find_next_capability(struct pci_dev *dev, u8 pos, int cap)
    {
    	return __pci_find_next_cap(dev->bus, dev->devfn,
    				   pos + PCI_CAP_LIST_NEXT, cap);
    }
    EXPORT_SYMBOL_GPL(pci_find_next_capability);
    
    
    static int __pci_bus_find_cap_start(struct pci_bus *bus,
    				    unsigned int devfn, u8 hdr_type)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	u16 status;
    
    	pci_bus_read_config_word(bus, devfn, PCI_STATUS, &status);
    	if (!(status & PCI_STATUS_CAP_LIST))
    		return 0;
    
    	switch (hdr_type) {
    	case PCI_HEADER_TYPE_NORMAL:
    	case PCI_HEADER_TYPE_BRIDGE:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	case PCI_HEADER_TYPE_CARDBUS:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	default:
    		return 0;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    /**
     * pci_find_capability - query for devices' capabilities 
     * @dev: PCI device to query
     * @cap: capability code
     *
     * Tell if a device supports a given PCI capability.
     * Returns the address of the requested capability structure within the
     * device's PCI configuration space or 0 in case the device does not
     * support it.  Possible values for @cap:
     *
     *  %PCI_CAP_ID_PM           Power Management 
     *  %PCI_CAP_ID_AGP          Accelerated Graphics Port 
     *  %PCI_CAP_ID_VPD          Vital Product Data 
     *  %PCI_CAP_ID_SLOTID       Slot Identification 
     *  %PCI_CAP_ID_MSI          Message Signalled Interrupts
     *  %PCI_CAP_ID_CHSWP        CompactPCI HotSwap 
     *  %PCI_CAP_ID_PCIX         PCI-X
     *  %PCI_CAP_ID_EXP          PCI Express
     */
    int pci_find_capability(struct pci_dev *dev, int cap)
    {
    
    	int pos;
    
    	pos = __pci_bus_find_cap_start(dev->bus, dev->devfn, dev->hdr_type);
    	if (pos)
    		pos = __pci_find_next_cap(dev->bus, dev->devfn, pos, cap);
    
    	return pos;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    /**
     * pci_bus_find_capability - query for devices' capabilities 
     * @bus:   the PCI bus to query
     * @devfn: PCI device to query
     * @cap:   capability code
     *
     * Like pci_find_capability() but works for pci devices that do not have a
     * pci_dev structure set up yet. 
     *
     * Returns the address of the requested capability structure within the
     * device's PCI configuration space or 0 in case the device does not
     * support it.
     */
    int pci_bus_find_capability(struct pci_bus *bus, unsigned int devfn, int cap)
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	u8 hdr_type;
    
    	pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type);
    
    
    	pos = __pci_bus_find_cap_start(bus, devfn, hdr_type & 0x7f);
    	if (pos)
    		pos = __pci_find_next_cap(bus, devfn, pos, cap);
    
    	return pos;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    /**
     * pci_find_ext_capability - Find an extended capability
     * @dev: PCI device to query
     * @cap: capability code
     *
     * Returns the address of the requested extended capability structure
     * within the device's PCI configuration space or 0 if the device does
     * not support it.  Possible values for @cap:
     *
     *  %PCI_EXT_CAP_ID_ERR		Advanced Error Reporting
     *  %PCI_EXT_CAP_ID_VC		Virtual Channel
     *  %PCI_EXT_CAP_ID_DSN		Device Serial Number
     *  %PCI_EXT_CAP_ID_PWR		Power Budgeting
     */
    int pci_find_ext_capability(struct pci_dev *dev, int cap)
    {
    	u32 header;
    
    	int ttl;
    	int pos = PCI_CFG_SPACE_SIZE;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* minimum 8 bytes per capability */
    	ttl = (PCI_CFG_SPACE_EXP_SIZE - PCI_CFG_SPACE_SIZE) / 8;
    
    	if (dev->cfg_size <= PCI_CFG_SPACE_SIZE)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return 0;
    
    	if (pci_read_config_dword(dev, pos, &header) != PCIBIOS_SUCCESSFUL)
    		return 0;
    
    	/*
    	 * If we have no capabilities, this is indicated by cap ID,
    	 * cap version and next pointer all being 0.
    	 */
    	if (header == 0)
    		return 0;
    
    	while (ttl-- > 0) {
    		if (PCI_EXT_CAP_ID(header) == cap)
    			return pos;
    
    		pos = PCI_EXT_CAP_NEXT(header);
    
    		if (pos < PCI_CFG_SPACE_SIZE)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			break;
    
    		if (pci_read_config_dword(dev, pos, &header) != PCIBIOS_SUCCESSFUL)
    			break;
    	}
    
    	return 0;
    }
    
    EXPORT_SYMBOL_GPL(pci_find_ext_capability);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    /**
     * pci_bus_find_ext_capability - find an extended capability
     * @bus:   the PCI bus to query
     * @devfn: PCI device to query
     * @cap:   capability code
     *
     * Like pci_find_ext_capability() but works for pci devices that do not have a
     * pci_dev structure set up yet.
     *
     * Returns the address of the requested capability structure within the
     * device's PCI configuration space or 0 in case the device does not
     * support it.
     */
    int pci_bus_find_ext_capability(struct pci_bus *bus, unsigned int devfn,
    				int cap)
    {
    	u32 header;
    	int ttl;
    	int pos = PCI_CFG_SPACE_SIZE;
    
    	/* minimum 8 bytes per capability */
    	ttl = (PCI_CFG_SPACE_EXP_SIZE - PCI_CFG_SPACE_SIZE) / 8;
    
    	if (!pci_bus_read_config_dword(bus, devfn, pos, &header))
    		return 0;
    	if (header == 0xffffffff || header == 0)
    		return 0;
    
    	while (ttl-- > 0) {
    		if (PCI_EXT_CAP_ID(header) == cap)
    			return pos;
    
    		pos = PCI_EXT_CAP_NEXT(header);
    		if (pos < PCI_CFG_SPACE_SIZE)
    			break;
    
    		if (!pci_bus_read_config_dword(bus, devfn, pos, &header))
    			break;
    	}
    
    	return 0;
    }
    
    
    static int __pci_find_next_ht_cap(struct pci_dev *dev, int pos, int ht_cap)
    {
    	int rc, ttl = PCI_FIND_CAP_TTL;
    	u8 cap, mask;
    
    	if (ht_cap == HT_CAPTYPE_SLAVE || ht_cap == HT_CAPTYPE_HOST)
    		mask = HT_3BIT_CAP_MASK;
    	else
    		mask = HT_5BIT_CAP_MASK;
    
    	pos = __pci_find_next_cap_ttl(dev->bus, dev->devfn, pos,
    				      PCI_CAP_ID_HT, &ttl);
    	while (pos) {
    		rc = pci_read_config_byte(dev, pos + 3, &cap);
    		if (rc != PCIBIOS_SUCCESSFUL)
    			return 0;
    
    		if ((cap & mask) == ht_cap)
    			return pos;
    
    
    		pos = __pci_find_next_cap_ttl(dev->bus, dev->devfn,
    					      pos + PCI_CAP_LIST_NEXT,
    
    					      PCI_CAP_ID_HT, &ttl);
    	}
    
    	return 0;
    }
    /**
     * pci_find_next_ht_capability - query a device's Hypertransport capabilities
     * @dev: PCI device to query
     * @pos: Position from which to continue searching
     * @ht_cap: Hypertransport capability code
     *
     * To be used in conjunction with pci_find_ht_capability() to search for
     * all capabilities matching @ht_cap. @pos should always be a value returned
     * from pci_find_ht_capability().
     *
     * NB. To be 100% safe against broken PCI devices, the caller should take
     * steps to avoid an infinite loop.
     */
    int pci_find_next_ht_capability(struct pci_dev *dev, int pos, int ht_cap)
    {
    	return __pci_find_next_ht_cap(dev, pos + PCI_CAP_LIST_NEXT, ht_cap);
    }
    EXPORT_SYMBOL_GPL(pci_find_next_ht_capability);
    
    /**
     * pci_find_ht_capability - query a device's Hypertransport capabilities
     * @dev: PCI device to query
     * @ht_cap: Hypertransport capability code
     *
     * Tell if a device supports a given Hypertransport capability.
     * Returns an address within the device's PCI configuration space
     * or 0 in case the device does not support the request capability.
     * The address points to the PCI capability, of type PCI_CAP_ID_HT,
     * which has a Hypertransport capability matching @ht_cap.
     */
    int pci_find_ht_capability(struct pci_dev *dev, int ht_cap)
    {
    	int pos;
    
    	pos = __pci_bus_find_cap_start(dev->bus, dev->devfn, dev->hdr_type);
    	if (pos)
    		pos = __pci_find_next_ht_cap(dev, pos, ht_cap);
    
    	return pos;
    }
    EXPORT_SYMBOL_GPL(pci_find_ht_capability);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /**
     * pci_find_parent_resource - return resource region of parent bus of given region
     * @dev: PCI device structure contains resources to be searched
     * @res: child resource record for which parent is sought
     *
     *  For given resource region of given device, return the resource
     *  region of parent bus the given region is contained in or where
     *  it should be allocated from.
     */
    struct resource *
    pci_find_parent_resource(const struct pci_dev *dev, struct resource *res)
    {
    	const struct pci_bus *bus = dev->bus;
    	int i;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	pci_bus_for_each_resource(bus, r, i) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (!r)
    			continue;
    		if (res->start && !(res->start >= r->start && res->end <= r->end))
    			continue;	/* Not contained */
    		if ((res->flags ^ r->flags) & (IORESOURCE_IO | IORESOURCE_MEM))
    			continue;	/* Wrong type */
    		if (!((res->flags ^ r->flags) & IORESOURCE_PREFETCH))
    			return r;	/* Exact match */
    
    		/* We can't insert a non-prefetch resource inside a prefetchable parent .. */
    		if (r->flags & IORESOURCE_PREFETCH)
    			continue;
    		/* .. but we can put a prefetchable resource inside a non-prefetchable one */
    		if (!best)
    			best = r;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    	return best;
    }
    
    
    /**
     * pci_restore_bars - restore a devices BAR values (e.g. after wake-up)
     * @dev: PCI device to have its BARs restored
     *
     * Restore the BAR values for a given device, so as to make it
     * accessible by its driver.
     */
    
    static void
    
    	for (i = 0; i < PCI_BRIDGE_RESOURCES; i++)
    
    		pci_update_resource(dev, i);
    
    static struct pci_platform_pm_ops *pci_platform_pm;
    
    int pci_set_platform_pm(struct pci_platform_pm_ops *ops)
    {
    
    	if (!ops->is_manageable || !ops->set_state || !ops->choose_state
    	    || !ops->sleep_wake || !ops->can_wakeup)
    
    		return -EINVAL;
    	pci_platform_pm = ops;
    	return 0;
    }
    
    static inline bool platform_pci_power_manageable(struct pci_dev *dev)
    {
    	return pci_platform_pm ? pci_platform_pm->is_manageable(dev) : false;
    }
    
    static inline int platform_pci_set_power_state(struct pci_dev *dev,
                                                    pci_power_t t)
    {
    	return pci_platform_pm ? pci_platform_pm->set_state(dev, t) : -ENOSYS;
    }
    
    static inline pci_power_t platform_pci_choose_state(struct pci_dev *dev)
    {
    	return pci_platform_pm ?
    			pci_platform_pm->choose_state(dev) : PCI_POWER_ERROR;
    }
    
    static inline bool platform_pci_can_wakeup(struct pci_dev *dev)
    {
    	return pci_platform_pm ? pci_platform_pm->can_wakeup(dev) : false;
    }
    
    static inline int platform_pci_sleep_wake(struct pci_dev *dev, bool enable)
    {
    	return pci_platform_pm ?
    			pci_platform_pm->sleep_wake(dev, enable) : -ENODEV;
    }
    
    
    static inline int platform_pci_run_wake(struct pci_dev *dev, bool enable)
    {
    	return pci_platform_pm ?
    			pci_platform_pm->run_wake(dev, enable) : -ENODEV;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /**
    
     * pci_raw_set_power_state - Use PCI PM registers to set the power state of
     *                           given PCI device
     * @dev: PCI device to handle.
     * @state: PCI power state (D0, D1, D2, D3hot) to put the device into.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *
    
     * RETURN VALUE:
     * -EINVAL if the requested state is invalid.
     * -EIO if device does not support PCI PM or its PM capabilities register has a
     * wrong version, or device doesn't support the requested state.
     * 0 if device already is in the requested state.
     * 0 if device's power state has been successfully changed.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    
    static int pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Check if we're already there */
    	if (dev->current_state == state)
    		return 0;
    
    
    	if (!dev->pm_cap)
    
    	if (state < PCI_D0 || state > PCI_D3hot)
    		return -EINVAL;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* Validate current state:
    	 * Can enter D0 from any state, but if we can only go deeper 
    	 * to sleep if we're already in a low power state
    	 */
    
    	if (state != PCI_D0 && dev->current_state <= PCI_D3cold
    
    	    && dev->current_state > state) {
    
    		dev_err(&dev->dev, "invalid power transition "
    			"(from state %d to %d)\n", dev->current_state, state);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* check if this device supports the desired state */
    
    	if ((state == PCI_D1 && !dev->d1_support)
    	   || (state == PCI_D2 && !dev->d2_support))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);
    
    	/* If we're (effectively) in D3, force entire word to 0.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	 * This doesn't affect PME_Status, disables PME_En, and
    	 * sets PowerState to 0.
    	 */
    
    	switch (dev->current_state) {
    
    	case PCI_D0:
    	case PCI_D1:
    	case PCI_D2:
    		pmcsr &= ~PCI_PM_CTRL_STATE_MASK;
    		pmcsr |= state;
    		break;
    
    	case PCI_UNKNOWN: /* Boot-up */
    		if ((pmcsr & PCI_PM_CTRL_STATE_MASK) == PCI_D3hot
    
    		 && !(pmcsr & PCI_PM_CTRL_NO_SOFT_RESET))
    
    		/* Fall-through: force to D0 */
    	default:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	/* enter specified state */
    
    	pci_write_config_word(dev, dev->pm_cap + PCI_PM_CTRL, pmcsr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Mandatory power management transition delays */
    	/* see PCI PM 1.1 5.6.1 table 18 */
    	if (state == PCI_D3hot || dev->current_state == PCI_D3hot)
    
    		pci_dev_d3_sleep(dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	else if (state == PCI_D2 || dev->current_state == PCI_D2)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);
    	dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);
    	if (dev->current_state != state && printk_ratelimit())
    		dev_info(&dev->dev, "Refused to change power state, "
    			"currently in D%d\n", dev->current_state);
    
    
    	/* According to section 5.4.1 of the "PCI BUS POWER MANAGEMENT
    	 * INTERFACE SPECIFICATION, REV. 1.2", a device transitioning
    	 * from D3hot to D0 _may_ perform an internal reset, thereby
    	 * going to "D0 Uninitialized" rather than "D0 Initialized".
    	 * For example, at least some versions of the 3c905B and the
    	 * 3c556B exhibit this behaviour.
    	 *
    	 * At least some laptop BIOSen (e.g. the Thinkpad T21) leave
    	 * devices in a D3hot state at boot.  Consequently, we need to
    	 * restore at least the BARs so that the device will be
    	 * accessible to its driver.
    	 */
    	if (need_restore)
    		pci_restore_bars(dev);
    
    
    		pcie_aspm_pm_state_change(dev->bus->self);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    }
    
    
    /**
     * pci_update_current_state - Read PCI power state of given device from its
     *                            PCI PM registers and cache it
     * @dev: PCI device to handle.
    
     * @state: State to cache in case the device doesn't have the PM capability
    
    void pci_update_current_state(struct pci_dev *dev, pci_power_t state)
    
    	if (dev->pm_cap) {
    
    		pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);
    
    		dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);
    
    	} else {
    		dev->current_state = state;
    
    /**
     * pci_platform_power_transition - Use platform to change device power state
     * @dev: PCI device to handle.
     * @state: State to put the device into.
     */
    static int pci_platform_power_transition(struct pci_dev *dev, pci_power_t state)
    {
    	int error;
    
    	if (platform_pci_power_manageable(dev)) {
    		error = platform_pci_set_power_state(dev, state);
    		if (!error)
    			pci_update_current_state(dev, state);
    	} else {
    		error = -ENODEV;
    		/* Fall back to PCI_D0 if native PM is not supported */
    
    		if (!dev->pm_cap)
    			dev->current_state = PCI_D0;
    
    	}
    
    	return error;
    }
    
    /**
     * __pci_start_power_transition - Start power transition of a PCI device
     * @dev: PCI device to handle.
     * @state: State to put the device into.
     */
    static void __pci_start_power_transition(struct pci_dev *dev, pci_power_t state)
    {
    	if (state == PCI_D0)
    		pci_platform_power_transition(dev, PCI_D0);
    }
    
    /**
     * __pci_complete_power_transition - Complete power transition of a PCI device
     * @dev: PCI device to handle.
     * @state: State to put the device into.
     *
     * This function should not be called directly by device drivers.
     */
    int __pci_complete_power_transition(struct pci_dev *dev, pci_power_t state)
    {
    
    	return state >= PCI_D0 ?
    
    			pci_platform_power_transition(dev, state) : -EINVAL;
    }
    EXPORT_SYMBOL_GPL(__pci_complete_power_transition);
    
    
    /**
     * pci_set_power_state - Set the power state of a PCI device
     * @dev: PCI device to handle.
     * @state: PCI power state (D0, D1, D2, D3hot) to put the device into.
     *
    
     * Transition a device to a new power state, using the platform firmware and/or
    
     * the device's PCI PM registers.
     *
     * RETURN VALUE:
     * -EINVAL if the requested state is invalid.
     * -EIO if device does not support PCI PM or its PM capabilities register has a
     * wrong version, or device doesn't support the requested state.
     * 0 if device already is in the requested state.
     * 0 if device's power state has been successfully changed.
     */
    int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
    {
    
    
    	/* bound the state we're entering */
    	if (state > PCI_D3hot)
    		state = PCI_D3hot;
    	else if (state < PCI_D0)
    		state = PCI_D0;
    	else if ((state == PCI_D1 || state == PCI_D2) && pci_no_d1d2(dev))
    		/*
    		 * If the device or the parent bridge do not support PCI PM,
    		 * ignore the request if we're doing anything other than putting
    		 * it into D0 (which would only happen on boot).
    		 */
    		return 0;
    
    
    	__pci_start_power_transition(dev, state);
    
    
    	/* This device is quirked not to be put into D3, so
    	   don't put it in D3 */
    	if (state == PCI_D3hot && (dev->dev_flags & PCI_DEV_FLAGS_NO_D3))
    		return 0;
    
    	error = pci_raw_set_power_state(dev, state);
    
    	if (!__pci_complete_power_transition(dev, state))
    		error = 0;
    
    	/*
    	 * When aspm_policy is "powersave" this call ensures
    	 * that ASPM is configured.
    	 */
    	if (!error && dev->bus->self)
    		pcie_aspm_powersave_config_link(dev->bus->self);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /**
     * pci_choose_state - Choose the power state of a PCI device
     * @dev: PCI device to be suspended
     * @state: target sleep state for the whole system. This is the value
     *	that is passed to suspend() function.
     *
     * Returns PCI power state suitable for given device and given system
     * message.
     */
    
    pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state)
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!pci_find_capability(dev, PCI_CAP_ID_PM))
    		return PCI_D0;
    
    
    	ret = platform_pci_choose_state(dev);
    	if (ret != PCI_POWER_ERROR)
    		return ret;
    
    
    	switch (state.event) {
    	case PM_EVENT_ON:
    		return PCI_D0;
    	case PM_EVENT_FREEZE:
    
    	case PM_EVENT_PRETHAW:
    		/* REVISIT both freeze and pre-thaw "should" use D0 */
    
    	case PM_EVENT_SUSPEND:
    
    	case PM_EVENT_HIBERNATE:
    
    		return PCI_D3hot;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	default:
    
    		dev_info(&dev->dev, "unrecognized suspend event %d\n",
    			 state.event);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		BUG();
    	}
    	return PCI_D0;
    }
    
    EXPORT_SYMBOL(pci_choose_state);
    
    
    #define PCI_EXP_SAVE_REGS	7
    
    
    #define pcie_cap_has_devctl(type, flags)	1
    #define pcie_cap_has_lnkctl(type, flags)		\
    		((flags & PCI_EXP_FLAGS_VERS) > 1 ||	\
    		 (type == PCI_EXP_TYPE_ROOT_PORT ||	\
    		  type == PCI_EXP_TYPE_ENDPOINT ||	\
    		  type == PCI_EXP_TYPE_LEG_END))
    #define pcie_cap_has_sltctl(type, flags)		\
    		((flags & PCI_EXP_FLAGS_VERS) > 1 ||	\
    		 ((type == PCI_EXP_TYPE_ROOT_PORT) ||	\
    		  (type == PCI_EXP_TYPE_DOWNSTREAM &&	\
    		   (flags & PCI_EXP_FLAGS_SLOT))))
    #define pcie_cap_has_rtctl(type, flags)			\
    		((flags & PCI_EXP_FLAGS_VERS) > 1 ||	\
    		 (type == PCI_EXP_TYPE_ROOT_PORT ||	\
    		  type == PCI_EXP_TYPE_RC_EC))
    #define pcie_cap_has_devctl2(type, flags)		\
    		((flags & PCI_EXP_FLAGS_VERS) > 1)
    #define pcie_cap_has_lnkctl2(type, flags)		\
    		((flags & PCI_EXP_FLAGS_VERS) > 1)
    #define pcie_cap_has_sltctl2(type, flags)		\
    		((flags & PCI_EXP_FLAGS_VERS) > 1)
    
    
    static int pci_save_pcie_state(struct pci_dev *dev)
    {
    	int pos, i = 0;
    	struct pci_cap_saved_state *save_state;
    	u16 *cap;
    
    	pos = pci_pcie_cap(dev);
    	if (!pos)
    
    	save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP);
    
    		dev_err(&dev->dev, "buffer not found in %s\n", __func__);
    
    		return -ENOMEM;
    	}
    	cap = (u16 *)&save_state->data[0];
    
    
    	pci_read_config_word(dev, pos + PCI_EXP_FLAGS, &flags);
    
    	if (pcie_cap_has_devctl(dev->pcie_type, flags))
    		pci_read_config_word(dev, pos + PCI_EXP_DEVCTL, &cap[i++]);
    	if (pcie_cap_has_lnkctl(dev->pcie_type, flags))
    		pci_read_config_word(dev, pos + PCI_EXP_LNKCTL, &cap[i++]);
    	if (pcie_cap_has_sltctl(dev->pcie_type, flags))
    		pci_read_config_word(dev, pos + PCI_EXP_SLTCTL, &cap[i++]);
    	if (pcie_cap_has_rtctl(dev->pcie_type, flags))
    		pci_read_config_word(dev, pos + PCI_EXP_RTCTL, &cap[i++]);
    	if (pcie_cap_has_devctl2(dev->pcie_type, flags))
    		pci_read_config_word(dev, pos + PCI_EXP_DEVCTL2, &cap[i++]);
    	if (pcie_cap_has_lnkctl2(dev->pcie_type, flags))
    		pci_read_config_word(dev, pos + PCI_EXP_LNKCTL2, &cap[i++]);
    	if (pcie_cap_has_sltctl2(dev->pcie_type, flags))
    		pci_read_config_word(dev, pos + PCI_EXP_SLTCTL2, &cap[i++]);
    
    	return 0;
    }
    
    static void pci_restore_pcie_state(struct pci_dev *dev)
    {
    	int i = 0, pos;
    	struct pci_cap_saved_state *save_state;
    	u16 *cap;
    
    
    	save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP);
    	pos = pci_find_capability(dev, PCI_CAP_ID_EXP);
    	if (!save_state || pos <= 0)
    		return;
    	cap = (u16 *)&save_state->data[0];
    
    
    	pci_read_config_word(dev, pos + PCI_EXP_FLAGS, &flags);
    
    	if (pcie_cap_has_devctl(dev->pcie_type, flags))
    		pci_write_config_word(dev, pos + PCI_EXP_DEVCTL, cap[i++]);
    	if (pcie_cap_has_lnkctl(dev->pcie_type, flags))
    		pci_write_config_word(dev, pos + PCI_EXP_LNKCTL, cap[i++]);
    	if (pcie_cap_has_sltctl(dev->pcie_type, flags))
    		pci_write_config_word(dev, pos + PCI_EXP_SLTCTL, cap[i++]);
    	if (pcie_cap_has_rtctl(dev->pcie_type, flags))
    		pci_write_config_word(dev, pos + PCI_EXP_RTCTL, cap[i++]);
    	if (pcie_cap_has_devctl2(dev->pcie_type, flags))
    		pci_write_config_word(dev, pos + PCI_EXP_DEVCTL2, cap[i++]);
    	if (pcie_cap_has_lnkctl2(dev->pcie_type, flags))
    		pci_write_config_word(dev, pos + PCI_EXP_LNKCTL2, cap[i++]);
    	if (pcie_cap_has_sltctl2(dev->pcie_type, flags))
    		pci_write_config_word(dev, pos + PCI_EXP_SLTCTL2, cap[i++]);
    
    
    static int pci_save_pcix_state(struct pci_dev *dev)
    {
    
    	struct pci_cap_saved_state *save_state;
    
    	pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);
    	if (pos <= 0)
    		return 0;
    
    
    	save_state = pci_find_saved_cap(dev, PCI_CAP_ID_PCIX);
    
    	if (!save_state) {
    
    		dev_err(&dev->dev, "buffer not found in %s\n", __func__);
    
    		return -ENOMEM;
    	}
    
    
    	pci_read_config_word(dev, pos + PCI_X_CMD, (u16 *)save_state->data);
    
    
    	return 0;
    }
    
    static void pci_restore_pcix_state(struct pci_dev *dev)
    {
    	int i = 0, pos;
    	struct pci_cap_saved_state *save_state;
    	u16 *cap;
    
    	save_state = pci_find_saved_cap(dev, PCI_CAP_ID_PCIX);
    	pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);
    	if (!save_state || pos <= 0)
    		return;
    	cap = (u16 *)&save_state->data[0];
    
    	pci_write_config_word(dev, pos + PCI_X_CMD, cap[i++]);
    }
    
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /**
     * pci_save_state - save the PCI configuration space of a device before suspending
     * @dev: - PCI device that we're dealing with
     */
    int
    pci_save_state(struct pci_dev *dev)
    {
    	int i;
    	/* XXX: 100% dword access ok here? */
    	for (i = 0; i < 16; i++)
    
    		pci_read_config_dword(dev, i * 4, &dev->saved_config_space[i]);
    
    	if ((i = pci_save_pcie_state(dev)) != 0)
    		return i;
    
    	if ((i = pci_save_pcix_state(dev)) != 0)
    		return i;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    }
    
    /** 
     * pci_restore_state - Restore the saved state of a PCI device
     * @dev: - PCI device that we're dealing with
     */
    
    void pci_restore_state(struct pci_dev *dev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int i;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (!dev->state_saved)
    
    	/* PCI Express register must be restored first */
    	pci_restore_pcie_state(dev);
    
    
    	/*
    	 * The Base Address register should be programmed before the command
    	 * register(s)
    	 */
    	for (i = 15; i >= 0; i--) {
    
    		pci_read_config_dword(dev, i * 4, &val);
    		if (val != dev->saved_config_space[i]) {
    
    			dev_printk(KERN_DEBUG, &dev->dev, "restoring config "
    				"space at offset %#x (was %#x, writing %#x)\n",
    				i, val, (int)dev->saved_config_space[i]);
    
    			pci_write_config_dword(dev,i * 4,
    				dev->saved_config_space[i]);
    		}
    	}
    
    	pci_restore_pcix_state(dev);
    
    	pci_restore_msi_state(dev);
    
    Yu Zhao's avatar
    Yu Zhao committed
    	pci_restore_iov_state(dev);
    
    static int do_pci_enable_device(struct pci_dev *dev, int bars)
    {
    	int err;
    
    	err = pci_set_power_state(dev, PCI_D0);
    	if (err < 0 && err != -EIO)
    		return err;
    	err = pcibios_enable_device(dev, bars);
    	if (err < 0)
    		return err;
    	pci_fixup_device(pci_fixup_enable, dev);
    
    	return 0;
    }
    
    /**
    
     * pci_reenable_device - Resume abandoned device
    
     * @dev: PCI device to be resumed
     *
     *  Note this function is a backend of pci_default_resume and is not supposed
     *  to be called by normal code, write proper resume handler and use it instead.
     */
    
    int pci_reenable_device(struct pci_dev *dev)