aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/ata/ahci.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/ata/ahci.c')
-rw-r--r--drivers/ata/ahci.c104
1 files changed, 89 insertions, 15 deletions
diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
index 65ee94454bbd..7e62751abfac 100644
--- a/drivers/ata/ahci.c
+++ b/drivers/ata/ahci.c
@@ -42,6 +42,7 @@
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/gfp.h>
+#include <linux/msi.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_cmnd.h>
#include <linux/libata.h>
@@ -52,6 +53,7 @@
enum {
AHCI_PCI_BAR_STA2X11 = 0,
+ AHCI_PCI_BAR_CAVIUM = 0,
AHCI_PCI_BAR_ENMOTUS = 2,
AHCI_PCI_BAR_STANDARD = 5,
};
@@ -1288,17 +1290,60 @@ static inline void ahci_gtf_filter_workaround(struct ata_host *host)
{}
#endif
-static int ahci_init_interrupts(struct pci_dev *pdev, unsigned int n_ports,
- struct ahci_host_priv *hpriv)
+/*
+ * ahci_init_msix() only implements single MSI-X support, not multiple
+ * MSI-X per-port interrupts. This is needed for host controllers that only
+ * have MSI-X support implemented, but no MSI or intx.
+ */
+static int ahci_init_msix(struct pci_dev *pdev, unsigned int n_ports,
+ struct ahci_host_priv *hpriv)
{
int rc, nvec;
+ struct msix_entry entry = {};
+ /* Do not init MSI-X if MSI is disabled for the device */
if (hpriv->flags & AHCI_HFLAG_NO_MSI)
- goto intx;
+ return -ENODEV;
+
+ nvec = pci_msix_vec_count(pdev);
+ if (nvec < 0)
+ return nvec;
+
+ if (!nvec) {
+ rc = -ENODEV;
+ goto fail;
+ }
+
+ /*
+ * There can be more than one vector (e.g. for error detection or
+ * hdd hotplug). Only the first vector (entry.entry = 0) is used.
+ */
+ rc = pci_enable_msix_exact(pdev, &entry, 1);
+ if (rc < 0)
+ goto fail;
+
+ hpriv->irq = entry.vector;
+
+ return 1;
+fail:
+ dev_err(&pdev->dev,
+ "failed to enable MSI-X with error %d, # of vectors: %d\n",
+ rc, nvec);
+
+ return rc;
+}
+
+static int ahci_init_msi(struct pci_dev *pdev, unsigned int n_ports,
+ struct ahci_host_priv *hpriv)
+{
+ int rc, nvec;
+
+ if (hpriv->flags & AHCI_HFLAG_NO_MSI)
+ return -ENODEV;
nvec = pci_msi_vec_count(pdev);
if (nvec < 0)
- goto intx;
+ return nvec;
/*
* If number of MSIs is less than number of ports then Sharing Last
@@ -1311,8 +1356,8 @@ static int ahci_init_interrupts(struct pci_dev *pdev, unsigned int n_ports,
rc = pci_enable_msi_exact(pdev, nvec);
if (rc == -ENOSPC)
goto single_msi;
- else if (rc < 0)
- goto intx;
+ if (rc < 0)
+ return rc;
/* fallback to single MSI mode if the controller enforced MRSM mode */
if (readl(hpriv->mmio + HOST_CTL) & HOST_MRSM) {
@@ -1324,15 +1369,42 @@ static int ahci_init_interrupts(struct pci_dev *pdev, unsigned int n_ports,
if (nvec > 1)
hpriv->flags |= AHCI_HFLAG_MULTI_MSI;
- return nvec;
+ goto out;
single_msi:
- if (pci_enable_msi(pdev))
- goto intx;
- return 1;
+ nvec = 1;
+
+ rc = pci_enable_msi(pdev);
+ if (rc < 0)
+ return rc;
+out:
+ hpriv->irq = pdev->irq;
+
+ return nvec;
+}
+
+static int ahci_init_interrupts(struct pci_dev *pdev, unsigned int n_ports,
+ struct ahci_host_priv *hpriv)
+{
+ int nvec;
+
+ nvec = ahci_init_msi(pdev, n_ports, hpriv);
+ if (nvec >= 0)
+ return nvec;
+
+ /*
+ * Currently, MSI-X support only implements single IRQ mode and
+ * exists for controllers which can't do other types of IRQ. Only
+ * set it up if MSI fails.
+ */
+ nvec = ahci_init_msix(pdev, n_ports, hpriv);
+ if (nvec >= 0)
+ return nvec;
-intx:
+ /* lagacy intx interrupts */
pci_intx(pdev, 1);
+ hpriv->irq = pdev->irq;
+
return 0;
}
@@ -1371,11 +1443,13 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
dev_info(&pdev->dev,
"PDC42819 can only drive SATA devices with this driver\n");
- /* Both Connext and Enmotus devices use non-standard BARs */
+ /* Some devices use non-standard BARs */
if (pdev->vendor == PCI_VENDOR_ID_STMICRO && pdev->device == 0xCC06)
ahci_pci_bar = AHCI_PCI_BAR_STA2X11;
else if (pdev->vendor == 0x1c44 && pdev->device == 0x8000)
ahci_pci_bar = AHCI_PCI_BAR_ENMOTUS;
+ else if (pdev->vendor == 0x177d && pdev->device == 0xa01c)
+ ahci_pci_bar = AHCI_PCI_BAR_CAVIUM;
/*
* The JMicron chip 361/363 contains one SATA controller and one
@@ -1497,13 +1571,13 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
*/
n_ports = max(ahci_nr_ports(hpriv->cap), fls(hpriv->port_map));
- ahci_init_interrupts(pdev, n_ports, hpriv);
-
host = ata_host_alloc_pinfo(&pdev->dev, ppi, n_ports);
if (!host)
return -ENOMEM;
host->private_data = hpriv;
+ ahci_init_interrupts(pdev, n_ports, hpriv);
+
if (!(hpriv->cap & HOST_CAP_SSS) || ahci_ignore_sss)
host->flags |= ATA_HOST_PARALLEL_SCAN;
else
@@ -1549,7 +1623,7 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
pci_set_master(pdev);
- return ahci_host_activate(host, pdev->irq, &ahci_sht);
+ return ahci_host_activate(host, &ahci_sht);
}
module_pci_driver(ahci_pci_driver);