]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/pci/pci.c
Merge branches 'pci/aspm', 'pci/dpc', 'pci/hotplug', 'pci/misc', 'pci/msi', 'pci...
[karo-tx-linux.git] / drivers / pci / pci.c
index c8b4dbdd1bddae95c214e92b52492b82364afcaf..14d24b4fc5087e4cffe6b5f5087f4a98aef5ee79 100644 (file)
@@ -7,8 +7,10 @@
  *     Copyright 1997 -- 2000 Martin Mares <mj@ucw.cz>
  */
 
+#include <linux/acpi.h>
 #include <linux/kernel.h>
 #include <linux/delay.h>
+#include <linux/dmi.h>
 #include <linux/init.h>
 #include <linux/of.h>
 #include <linux/of_pci.h>
@@ -25,7 +27,9 @@
 #include <linux/device.h>
 #include <linux/pm_runtime.h>
 #include <linux/pci_hotplug.h>
+#include <linux/vmalloc.h>
 #include <asm/setup.h>
+#include <asm/dma.h>
 #include <linux/aer.h>
 #include "pci.h"
 
@@ -81,6 +85,9 @@ unsigned long pci_cardbus_mem_size = DEFAULT_CARDBUS_MEM_SIZE;
 unsigned long pci_hotplug_io_size  = DEFAULT_HOTPLUG_IO_SIZE;
 unsigned long pci_hotplug_mem_size = DEFAULT_HOTPLUG_MEM_SIZE;
 
+#define DEFAULT_HOTPLUG_BUS_SIZE       1
+unsigned long pci_hotplug_bus_size = DEFAULT_HOTPLUG_BUS_SIZE;
+
 enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_DEFAULT;
 
 /*
@@ -101,6 +108,21 @@ unsigned int pcibios_max_latency = 255;
 /* If set, the PCIe ARI capability will not be used. */
 static bool pcie_ari_disabled;
 
+/* Disable bridge_d3 for all PCIe ports */
+static bool pci_bridge_d3_disable;
+/* Force bridge_d3 for all PCIe ports */
+static bool pci_bridge_d3_force;
+
+static int __init pcie_port_pm_setup(char *str)
+{
+       if (!strcmp(str, "off"))
+               pci_bridge_d3_disable = true;
+       else if (!strcmp(str, "force"))
+               pci_bridge_d3_force = true;
+       return 1;
+}
+__setup("pcie_port_pm=", pcie_port_pm_setup);
+
 /**
  * pci_bus_max_busnr - returns maximum PCI bus number of given bus' children
  * @bus: pointer to PCI bus structure to search
@@ -2155,6 +2177,164 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev)
                pm_runtime_put_sync(parent);
 }
 
+/**
+ * pci_bridge_d3_possible - Is it possible to put the bridge into D3
+ * @bridge: Bridge to check
+ *
+ * This function checks if it is possible to move the bridge to D3.
+ * Currently we only allow D3 for recent enough PCIe ports.
+ */
+static bool pci_bridge_d3_possible(struct pci_dev *bridge)
+{
+       unsigned int year;
+
+       if (!pci_is_pcie(bridge))
+               return false;
+
+       switch (pci_pcie_type(bridge)) {
+       case PCI_EXP_TYPE_ROOT_PORT:
+       case PCI_EXP_TYPE_UPSTREAM:
+       case PCI_EXP_TYPE_DOWNSTREAM:
+               if (pci_bridge_d3_disable)
+                       return false;
+               if (pci_bridge_d3_force)
+                       return true;
+
+               /*
+                * It should be safe to put PCIe ports from 2015 or newer
+                * to D3.
+                */
+               if (dmi_get_date(DMI_BIOS_DATE, &year, NULL, NULL) &&
+                   year >= 2015) {
+                       return true;
+               }
+               break;
+       }
+
+       return false;
+}
+
+static int pci_dev_check_d3cold(struct pci_dev *dev, void *data)
+{
+       bool *d3cold_ok = data;
+       bool no_d3cold;
+
+       /*
+        * The device needs to be allowed to go D3cold and if it is wake
+        * capable to do so from D3cold.
+        */
+       no_d3cold = dev->no_d3cold || !dev->d3cold_allowed ||
+               (device_may_wakeup(&dev->dev) && !pci_pme_capable(dev, PCI_D3cold)) ||
+               !pci_power_manageable(dev);
+
+       *d3cold_ok = !no_d3cold;
+
+       return no_d3cold;
+}
+
+/*
+ * pci_bridge_d3_update - Update bridge D3 capabilities
+ * @dev: PCI device which is changed
+ * @remove: Is the device being removed
+ *
+ * Update upstream bridge PM capabilities accordingly depending on if the
+ * device PM configuration was changed or the device is being removed.  The
+ * change is also propagated upstream.
+ */
+static void pci_bridge_d3_update(struct pci_dev *dev, bool remove)
+{
+       struct pci_dev *bridge;
+       bool d3cold_ok = true;
+
+       bridge = pci_upstream_bridge(dev);
+       if (!bridge || !pci_bridge_d3_possible(bridge))
+               return;
+
+       pci_dev_get(bridge);
+       /*
+        * If the device is removed we do not care about its D3cold
+        * capabilities.
+        */
+       if (!remove)
+               pci_dev_check_d3cold(dev, &d3cold_ok);
+
+       if (d3cold_ok) {
+               /*
+                * We need to go through all children to find out if all of
+                * them can still go to D3cold.
+                */
+               pci_walk_bus(bridge->subordinate, pci_dev_check_d3cold,
+                            &d3cold_ok);
+       }
+
+       if (bridge->bridge_d3 != d3cold_ok) {
+               bridge->bridge_d3 = d3cold_ok;
+               /* Propagate change to upstream bridges */
+               pci_bridge_d3_update(bridge, false);
+       }
+
+       pci_dev_put(bridge);
+}
+
+/**
+ * pci_bridge_d3_device_changed - Update bridge D3 capabilities on change
+ * @dev: PCI device that was changed
+ *
+ * If a device is added or its PM configuration, such as is it allowed to
+ * enter D3cold, is changed this function updates upstream bridge PM
+ * capabilities accordingly.
+ */
+void pci_bridge_d3_device_changed(struct pci_dev *dev)
+{
+       pci_bridge_d3_update(dev, false);
+}
+
+/**
+ * pci_bridge_d3_device_removed - Update bridge D3 capabilities on remove
+ * @dev: PCI device being removed
+ *
+ * Function updates upstream bridge PM capabilities based on other devices
+ * still left on the bus.
+ */
+void pci_bridge_d3_device_removed(struct pci_dev *dev)
+{
+       pci_bridge_d3_update(dev, true);
+}
+
+/**
+ * pci_d3cold_enable - Enable D3cold for device
+ * @dev: PCI device to handle
+ *
+ * This function can be used in drivers to enable D3cold from the device
+ * they handle.  It also updates upstream PCI bridge PM capabilities
+ * accordingly.
+ */
+void pci_d3cold_enable(struct pci_dev *dev)
+{
+       if (dev->no_d3cold) {
+               dev->no_d3cold = false;
+               pci_bridge_d3_device_changed(dev);
+       }
+}
+EXPORT_SYMBOL_GPL(pci_d3cold_enable);
+
+/**
+ * pci_d3cold_disable - Disable D3cold for device
+ * @dev: PCI device to handle
+ *
+ * This function can be used in drivers to disable D3cold from the device
+ * they handle.  It also updates upstream PCI bridge PM capabilities
+ * accordingly.
+ */
+void pci_d3cold_disable(struct pci_dev *dev)
+{
+       if (!dev->no_d3cold) {
+               dev->no_d3cold = true;
+               pci_bridge_d3_device_changed(dev);
+       }
+}
+EXPORT_SYMBOL_GPL(pci_d3cold_disable);
+
 /**
  * pci_pm_init - Initialize PM functions of given PCI device
  * @dev: PCI device to handle.
@@ -2189,6 +2369,7 @@ void pci_pm_init(struct pci_dev *dev)
        dev->pm_cap = pm;
        dev->d3_delay = PCI_PM_D3_WAIT;
        dev->d3cold_delay = PCI_PM_D3COLD_WAIT;
+       dev->bridge_d3 = pci_bridge_d3_possible(dev);
        dev->d3cold_allowed = true;
 
        dev->d1_support = false;
@@ -3165,6 +3346,23 @@ int __weak pci_remap_iospace(const struct resource *res, phys_addr_t phys_addr)
 #endif
 }
 
+/**
+ *     pci_unmap_iospace - Unmap the memory mapped I/O space
+ *     @res: resource to be unmapped
+ *
+ *     Unmap the CPU virtual address @res from virtual address space.
+ *     Only architectures that have memory mapped IO functions defined
+ *     (and the PCI_IOBASE value defined) should call this function.
+ */
+void pci_unmap_iospace(struct resource *res)
+{
+#if defined(PCI_IOBASE) && defined(CONFIG_MMU)
+       unsigned long vaddr = (unsigned long)PCI_IOBASE + res->start;
+
+       unmap_kernel_range(vaddr, resource_size(res));
+#endif
+}
+
 static void __pci_set_master(struct pci_dev *dev, bool enable)
 {
        u16 old_cmd, cmd;
@@ -4897,7 +5095,7 @@ static ssize_t pci_resource_alignment_store(struct bus_type *bus,
        return pci_set_resource_alignment_param(buf, count);
 }
 
-BUS_ATTR(resource_alignment, 0644, pci_resource_alignment_show,
+static BUS_ATTR(resource_alignment, 0644, pci_resource_alignment_show,
                                        pci_resource_alignment_store);
 
 static int __init pci_resource_alignment_sysfs_init(void)
@@ -4923,7 +5121,7 @@ int pci_get_new_domain_nr(void)
 }
 
 #ifdef CONFIG_PCI_DOMAINS_GENERIC
-void pci_bus_assign_domain_nr(struct pci_bus *bus, struct device *parent)
+static int of_pci_bus_find_domain_nr(struct device *parent)
 {
        static int use_dt_domains = -1;
        int domain = -1;
@@ -4967,7 +5165,13 @@ void pci_bus_assign_domain_nr(struct pci_bus *bus, struct device *parent)
                domain = -1;
        }
 
-       bus->domain_nr = domain;
+       return domain;
+}
+
+int pci_bus_find_domain_nr(struct pci_bus *bus, struct device *parent)
+{
+       return acpi_disabled ? of_pci_bus_find_domain_nr(parent) :
+                              acpi_pci_bus_find_domain_nr(bus);
 }
 #endif
 #endif
@@ -5021,6 +5225,11 @@ static int __init pci_setup(char *str)
                                pci_hotplug_io_size = memparse(str + 9, &str);
                        } else if (!strncmp(str, "hpmemsize=", 10)) {
                                pci_hotplug_mem_size = memparse(str + 10, &str);
+                       } else if (!strncmp(str, "hpbussize=", 10)) {
+                               pci_hotplug_bus_size =
+                                       simple_strtoul(str + 10, &str, 0);
+                               if (pci_hotplug_bus_size > 0xff)
+                                       pci_hotplug_bus_size = DEFAULT_HOTPLUG_BUS_SIZE;
                        } else if (!strncmp(str, "pcie_bus_tune_off", 17)) {
                                pcie_bus_config = PCIE_BUS_TUNE_OFF;
                        } else if (!strncmp(str, "pcie_bus_safe", 13)) {