PCI: add OBFF enable/disable support
Jesse Barnes [Mon, 10 Jan 2011 20:46:36 +0000 (12:46 -0800)]
OBFF (optimized buffer flush/fill), where supported, can help improve
energy efficiency by giving devices information about when interrupts
and other activity will have a reduced power impact.  It requires
support from both the device and system (i.e. not only does the device
need to respond to OBFF messages, but the platform must be capable of
generating and routing them to the end point).

Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>

drivers/pci/pci.c
include/linux/pci.h
include/linux/pci_regs.h

index d0182be..01e4cab 100644 (file)
@@ -1887,6 +1887,98 @@ void pci_disable_ido(struct pci_dev *dev, unsigned long type)
 }
 EXPORT_SYMBOL(pci_disable_ido);
 
+/**
+ * pci_enable_obff - enable optimized buffer flush/fill
+ * @dev: PCI device
+ * @type: type of signaling to use
+ *
+ * Try to enable @type OBFF signaling on @dev.  It will try using WAKE#
+ * signaling if possible, falling back to message signaling only if
+ * WAKE# isn't supported.  @type should indicate whether the PCIe link
+ * be brought out of L0s or L1 to send the message.  It should be either
+ * %PCI_EXP_OBFF_SIGNAL_ALWAYS or %PCI_OBFF_SIGNAL_L0.
+ *
+ * If your device can benefit from receiving all messages, even at the
+ * power cost of bringing the link back up from a low power state, use
+ * %PCI_EXP_OBFF_SIGNAL_ALWAYS.  Otherwise, use %PCI_OBFF_SIGNAL_L0 (the
+ * preferred type).
+ *
+ * RETURNS:
+ * Zero on success, appropriate error number on failure.
+ */
+int pci_enable_obff(struct pci_dev *dev, enum pci_obff_signal_type type)
+{
+       int pos;
+       u32 cap;
+       u16 ctrl;
+       int ret;
+
+       if (!pci_is_pcie(dev))
+               return -ENOTSUPP;
+
+       pos = pci_pcie_cap(dev);
+       if (!pos)
+               return -ENOTSUPP;
+
+       pci_read_config_dword(dev, pos + PCI_EXP_DEVCAP2, &cap);
+       if (!(cap & PCI_EXP_OBFF_MASK))
+               return -ENOTSUPP; /* no OBFF support at all */
+
+       /* Make sure the topology supports OBFF as well */
+       if (dev->bus) {
+               ret = pci_enable_obff(dev->bus->self, type);
+               if (ret)
+                       return ret;
+       }
+
+       pci_read_config_word(dev, pos + PCI_EXP_DEVCTL2, &ctrl);
+       if (cap & PCI_EXP_OBFF_WAKE)
+               ctrl |= PCI_EXP_OBFF_WAKE_EN;
+       else {
+               switch (type) {
+               case PCI_EXP_OBFF_SIGNAL_L0:
+                       if (!(ctrl & PCI_EXP_OBFF_WAKE_EN))
+                               ctrl |= PCI_EXP_OBFF_MSGA_EN;
+                       break;
+               case PCI_EXP_OBFF_SIGNAL_ALWAYS:
+                       ctrl &= ~PCI_EXP_OBFF_WAKE_EN;
+                       ctrl |= PCI_EXP_OBFF_MSGB_EN;
+                       break;
+               default:
+                       WARN(1, "bad OBFF signal type\n");
+                       return -ENOTSUPP;
+               }
+       }
+       pci_write_config_word(dev, pos + PCI_EXP_DEVCTL2, ctrl);
+
+       return 0;
+}
+EXPORT_SYMBOL(pci_enable_obff);
+
+/**
+ * pci_disable_obff - disable optimized buffer flush/fill
+ * @dev: PCI device
+ *
+ * Disable OBFF on @dev.
+ */
+void pci_disable_obff(struct pci_dev *dev)
+{
+       int pos;
+       u16 ctrl;
+
+       if (!pci_is_pcie(dev))
+               return;
+
+       pos = pci_pcie_cap(dev);
+       if (!pos)
+               return;
+
+       pci_read_config_word(dev, pos + PCI_EXP_DEVCTL2, &ctrl);
+       ctrl &= ~PCI_EXP_OBFF_WAKE_EN;
+       pci_write_config_word(dev, pos + PCI_EXP_DEVCTL2, ctrl);
+}
+EXPORT_SYMBOL(pci_disable_obff);
+
 static int pci_acs_enable;
 
 /**
index 551ddcb..45a035c 100644 (file)
@@ -833,6 +833,13 @@ static inline int pci_enable_wake(struct pci_dev *dev, pci_power_t state,
 void pci_enable_ido(struct pci_dev *dev, unsigned long type);
 void pci_disable_ido(struct pci_dev *dev, unsigned long type);
 
+enum pci_obff_signal_type {
+       PCI_EXP_OBFF_SIGNAL_L0,
+       PCI_EXP_OBFF_SIGNAL_ALWAYS,
+};
+int pci_enable_obff(struct pci_dev *dev, enum pci_obff_signal_type);
+void pci_disable_obff(struct pci_dev *dev);
+
 /* For use by arch with custom probe code */
 void set_pcie_port_type(struct pci_dev *pdev);
 void set_pcie_hotplug_bridge(struct pci_dev *pdev);
@@ -1220,6 +1227,15 @@ static inline void pci_disable_ido(struct pci_dev *dev, unsigned long type)
 {
 }
 
+static inline int pci_enable_obff(struct pci_dev *dev, unsigned long type)
+{
+       return 0;
+}
+
+static inline void pci_disable_obff(struct pci_dev *dev)
+{
+}
+
 static inline int pci_request_regions(struct pci_dev *dev, const char *res_name)
 {
        return -EIO;
index d9acf9b..aa42026 100644 (file)
 #define PCI_EXP_RTSTA_PENDING  0x20000 /* PME pending */
 #define PCI_EXP_DEVCAP2                36      /* Device Capabilities 2 */
 #define  PCI_EXP_DEVCAP2_ARI   0x20    /* Alternative Routing-ID */
+#define  PCI_EXP_OBFF_MASK     0xc0000 /* OBFF support mechanism */
+#define  PCI_EXP_OBFF_MSG      0x40000 /* New message signaling */
+#define  PCI_EXP_OBFF_WAKE     0x80000 /* Re-use WAKE# for OBFF */
 #define PCI_EXP_DEVCTL2                40      /* Device Control 2 */
 #define  PCI_EXP_DEVCTL2_ARI   0x20    /* Alternative Routing-ID */
 #define  PCI_EXP_IDO_REQ_EN    0x100   /* ID-based ordering request enable */
 #define  PCI_EXP_IDO_CMP_EN    0x200   /* ID-based ordering completion enable */
+#define  PCI_EXP_OBFF_MSGA_EN  0x2000  /* OBFF enable with Message type A */
+#define  PCI_EXP_OBFF_MSGB_EN  0x4000  /* OBFF enable with Message type B */
+#define  PCI_EXP_OBFF_WAKE_EN  0x6000  /* OBFF using WAKE# signaling */
 #define PCI_EXP_LNKCTL2                48      /* Link Control 2 */
 #define PCI_EXP_SLTCTL2                56      /* Slot Control 2 */