PCI: add latency tolerance reporting enable/disable support
Jesse Barnes [Fri, 14 Jan 2011 16:53:04 +0000 (08:53 -0800)]
Latency tolerance reporting allows devices to send messages to the root
complex indicating their latency tolerance for snooped & unsnooped
memory transactions.  Add support for enabling & disabling this
feature, along with a routine to set the max latencies a device should
send upstream.

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

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

index 01e4cab..53302cb 100644 (file)
@@ -1979,6 +1979,155 @@ void pci_disable_obff(struct pci_dev *dev)
 }
 EXPORT_SYMBOL(pci_disable_obff);
 
+/**
+ * pci_ltr_supported - check whether a device supports LTR
+ * @dev: PCI device
+ *
+ * RETURNS:
+ * True if @dev supports latency tolerance reporting, false otherwise.
+ */
+bool pci_ltr_supported(struct pci_dev *dev)
+{
+       int pos;
+       u32 cap;
+
+       if (!pci_is_pcie(dev))
+               return false;
+
+       pos = pci_pcie_cap(dev);
+       if (!pos)
+               return false;
+
+       pci_read_config_dword(dev, pos + PCI_EXP_DEVCAP2, &cap);
+
+       return cap & PCI_EXP_DEVCAP2_LTR;
+}
+EXPORT_SYMBOL(pci_ltr_supported);
+
+/**
+ * pci_enable_ltr - enable latency tolerance reporting
+ * @dev: PCI device
+ *
+ * Enable LTR on @dev if possible, which means enabling it first on
+ * upstream ports.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int pci_enable_ltr(struct pci_dev *dev)
+{
+       int pos;
+       u16 ctrl;
+       int ret;
+
+       if (!pci_ltr_supported(dev))
+               return -ENOTSUPP;
+
+       pos = pci_pcie_cap(dev);
+       if (!pos)
+               return -ENOTSUPP;
+
+       /* Only primary function can enable/disable LTR */
+       if (PCI_FUNC(dev->devfn) != 0)
+               return -EINVAL;
+
+       /* Enable upstream ports first */
+       if (dev->bus) {
+               ret = pci_enable_ltr(dev->bus->self);
+               if (ret)
+                       return ret;
+       }
+
+       pci_read_config_word(dev, pos + PCI_EXP_DEVCTL2, &ctrl);
+       ctrl |= PCI_EXP_LTR_EN;
+       pci_write_config_word(dev, pos + PCI_EXP_DEVCTL2, ctrl);
+
+       return 0;
+}
+EXPORT_SYMBOL(pci_enable_ltr);
+
+/**
+ * pci_disable_ltr - disable latency tolerance reporting
+ * @dev: PCI device
+ */
+void pci_disable_ltr(struct pci_dev *dev)
+{
+       int pos;
+       u16 ctrl;
+
+       if (!pci_ltr_supported(dev))
+               return;
+
+       pos = pci_pcie_cap(dev);
+       if (!pos)
+               return;
+
+       /* Only primary function can enable/disable LTR */
+       if (PCI_FUNC(dev->devfn) != 0)
+               return;
+
+       pci_read_config_word(dev, pos + PCI_EXP_DEVCTL2, &ctrl);
+       ctrl &= ~PCI_EXP_LTR_EN;
+       pci_write_config_word(dev, pos + PCI_EXP_DEVCTL2, ctrl);
+}
+EXPORT_SYMBOL(pci_disable_ltr);
+
+static int __pci_ltr_scale(int *val)
+{
+       int scale = 0;
+
+       while (*val > 1023) {
+               *val = (*val + 31) / 32;
+               scale++;
+       }
+       return scale;
+}
+
+/**
+ * pci_set_ltr - set LTR latency values
+ * @dev: PCI device
+ * @snoop_lat_ns: snoop latency in nanoseconds
+ * @nosnoop_lat_ns: nosnoop latency in nanoseconds
+ *
+ * Figure out the scale and set the LTR values accordingly.
+ */
+int pci_set_ltr(struct pci_dev *dev, int snoop_lat_ns, int nosnoop_lat_ns)
+{
+       int pos, ret, snoop_scale, nosnoop_scale;
+       u16 val;
+
+       if (!pci_ltr_supported(dev))
+               return -ENOTSUPP;
+
+       snoop_scale = __pci_ltr_scale(&snoop_lat_ns);
+       nosnoop_scale = __pci_ltr_scale(&nosnoop_lat_ns);
+
+       if (snoop_lat_ns > PCI_LTR_VALUE_MASK ||
+           nosnoop_lat_ns > PCI_LTR_VALUE_MASK)
+               return -EINVAL;
+
+       if ((snoop_scale > (PCI_LTR_SCALE_MASK >> PCI_LTR_SCALE_SHIFT)) ||
+           (nosnoop_scale > (PCI_LTR_SCALE_MASK >> PCI_LTR_SCALE_SHIFT)))
+               return -EINVAL;
+
+       pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_LTR);
+       if (!pos)
+               return -ENOTSUPP;
+
+       val = (snoop_scale << PCI_LTR_SCALE_SHIFT) | snoop_lat_ns;
+       ret = pci_write_config_word(dev, pos + PCI_LTR_MAX_SNOOP_LAT, val);
+       if (ret != 4)
+               return -EIO;
+
+       val = (nosnoop_scale << PCI_LTR_SCALE_SHIFT) | nosnoop_lat_ns;
+       ret = pci_write_config_word(dev, pos + PCI_LTR_MAX_NOSNOOP_LAT, val);
+       if (ret != 4)
+               return -EIO;
+
+       return 0;
+}
+EXPORT_SYMBOL(pci_set_ltr);
+
 static int pci_acs_enable;
 
 /**
index 45a035c..df4d69b 100644 (file)
@@ -840,6 +840,11 @@ enum pci_obff_signal_type {
 int pci_enable_obff(struct pci_dev *dev, enum pci_obff_signal_type);
 void pci_disable_obff(struct pci_dev *dev);
 
+bool pci_ltr_supported(struct pci_dev *dev);
+int pci_enable_ltr(struct pci_dev *dev);
+void pci_disable_ltr(struct pci_dev *dev);
+int pci_set_ltr(struct pci_dev *dev, int snoop_lat_ns, int nosnoop_lat_ns);
+
 /* 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);
index aa42026..e884096 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_DEVCAP2_LTR   0x800   /* Latency tolerance reporting */
 #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_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_LTR_EN                0x400   /* Latency tolerance reporting */
 #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_EXT_CAP_ID_ARI     14
 #define PCI_EXT_CAP_ID_ATS     15
 #define PCI_EXT_CAP_ID_SRIOV   16
+#define PCI_EXT_CAP_ID_LTR     24
 
 /* Advanced Error Reporting */
 #define PCI_ERR_UNCOR_STATUS   4       /* Uncorrectable Error Status */
 #define  PCI_SRIOV_VFM_MO      0x2     /* Active.MigrateOut */
 #define  PCI_SRIOV_VFM_AV      0x3     /* Active.Available */
 
+#define PCI_LTR_MAX_SNOOP_LAT  0x4
+#define PCI_LTR_MAX_NOSNOOP_LAT        0x6
+#define  PCI_LTR_VALUE_MASK    0x000003ff
+#define  PCI_LTR_SCALE_MASK    0x00001c00
+#define  PCI_LTR_SCALE_SHIFT   10
+
 /* Access Control Service */
 #define PCI_ACS_CAP            0x04    /* ACS Capability Register */
 #define  PCI_ACS_SV            0x01    /* Source Validation */