PCI: add latency tolerance reporting enable/disable support

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>
This commit is contained in:
Jesse Barnes
2011-01-14 08:53:04 -08:00
parent 48a92a8179
commit 51c2e0a7e5
3 changed files with 163 additions and 0 deletions

View 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;
/**