diff options
Diffstat (limited to 'drivers/ata/ahci.c')
-rw-r--r-- | drivers/ata/ahci.c | 207 |
1 files changed, 184 insertions, 23 deletions
diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c index c7a92a743ed0..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, }; @@ -66,6 +68,7 @@ enum board_ids { board_ahci_yes_fbs, /* board IDs for specific chipsets in alphabetical order */ + board_ahci_avn, board_ahci_mcp65, board_ahci_mcp77, board_ahci_mcp89, @@ -84,6 +87,8 @@ enum board_ids { static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent); static int ahci_vt8251_hardreset(struct ata_link *link, unsigned int *class, unsigned long deadline); +static int ahci_avn_hardreset(struct ata_link *link, unsigned int *class, + unsigned long deadline); static void ahci_mcp89_apple_enable(struct pci_dev *pdev); static bool is_mcp89_apple(struct pci_dev *pdev); static int ahci_p5wdh_hardreset(struct ata_link *link, unsigned int *class, @@ -107,6 +112,11 @@ static struct ata_port_operations ahci_p5wdh_ops = { .hardreset = ahci_p5wdh_hardreset, }; +static struct ata_port_operations ahci_avn_ops = { + .inherits = &ahci_ops, + .hardreset = ahci_avn_hardreset, +}; + static const struct ata_port_info ahci_port_info[] = { /* by features */ [board_ahci] = { @@ -151,6 +161,12 @@ static const struct ata_port_info ahci_port_info[] = { .port_ops = &ahci_ops, }, /* by chipsets */ + [board_ahci_avn] = { + .flags = AHCI_FLAG_COMMON, + .pio_mask = ATA_PIO4, + .udma_mask = ATA_UDMA6, + .port_ops = &ahci_avn_ops, + }, [board_ahci_mcp65] = { AHCI_HFLAGS (AHCI_HFLAG_NO_FPDMA_AA | AHCI_HFLAG_NO_PMP | AHCI_HFLAG_YES_NCQ), @@ -290,14 +306,14 @@ static const struct pci_device_id ahci_pci_tbl[] = { { PCI_VDEVICE(INTEL, 0x1f27), board_ahci }, /* Avoton RAID */ { PCI_VDEVICE(INTEL, 0x1f2e), board_ahci }, /* Avoton RAID */ { PCI_VDEVICE(INTEL, 0x1f2f), board_ahci }, /* Avoton RAID */ - { PCI_VDEVICE(INTEL, 0x1f32), board_ahci }, /* Avoton AHCI */ - { PCI_VDEVICE(INTEL, 0x1f33), board_ahci }, /* Avoton AHCI */ - { PCI_VDEVICE(INTEL, 0x1f34), board_ahci }, /* Avoton RAID */ - { PCI_VDEVICE(INTEL, 0x1f35), board_ahci }, /* Avoton RAID */ - { PCI_VDEVICE(INTEL, 0x1f36), board_ahci }, /* Avoton RAID */ - { PCI_VDEVICE(INTEL, 0x1f37), board_ahci }, /* Avoton RAID */ - { PCI_VDEVICE(INTEL, 0x1f3e), board_ahci }, /* Avoton RAID */ - { PCI_VDEVICE(INTEL, 0x1f3f), board_ahci }, /* Avoton RAID */ + { PCI_VDEVICE(INTEL, 0x1f32), board_ahci_avn }, /* Avoton AHCI */ + { PCI_VDEVICE(INTEL, 0x1f33), board_ahci_avn }, /* Avoton AHCI */ + { PCI_VDEVICE(INTEL, 0x1f34), board_ahci_avn }, /* Avoton RAID */ + { PCI_VDEVICE(INTEL, 0x1f35), board_ahci_avn }, /* Avoton RAID */ + { PCI_VDEVICE(INTEL, 0x1f36), board_ahci_avn }, /* Avoton RAID */ + { PCI_VDEVICE(INTEL, 0x1f37), board_ahci_avn }, /* Avoton RAID */ + { PCI_VDEVICE(INTEL, 0x1f3e), board_ahci_avn }, /* Avoton RAID */ + { PCI_VDEVICE(INTEL, 0x1f3f), board_ahci_avn }, /* Avoton RAID */ { PCI_VDEVICE(INTEL, 0x2823), board_ahci }, /* Wellsburg RAID */ { PCI_VDEVICE(INTEL, 0x2827), board_ahci }, /* Wellsburg RAID */ { PCI_VDEVICE(INTEL, 0x8d02), board_ahci }, /* Wellsburg AHCI */ @@ -670,6 +686,79 @@ static int ahci_p5wdh_hardreset(struct ata_link *link, unsigned int *class, return rc; } +/* + * ahci_avn_hardreset - attempt more aggressive recovery of Avoton ports. + * + * It has been observed with some SSDs that the timing of events in the + * link synchronization phase can leave the port in a state that can not + * be recovered by a SATA-hard-reset alone. The failing signature is + * SStatus.DET stuck at 1 ("Device presence detected but Phy + * communication not established"). It was found that unloading and + * reloading the driver when this problem occurs allows the drive + * connection to be recovered (DET advanced to 0x3). The critical + * component of reloading the driver is that the port state machines are + * reset by bouncing "port enable" in the AHCI PCS configuration + * register. So, reproduce that effect by bouncing a port whenever we + * see DET==1 after a reset. + */ +static int ahci_avn_hardreset(struct ata_link *link, unsigned int *class, + unsigned long deadline) +{ + const unsigned long *timing = sata_ehc_deb_timing(&link->eh_context); + struct ata_port *ap = link->ap; + struct ahci_port_priv *pp = ap->private_data; + struct ahci_host_priv *hpriv = ap->host->private_data; + u8 *d2h_fis = pp->rx_fis + RX_FIS_D2H_REG; + unsigned long tmo = deadline - jiffies; + struct ata_taskfile tf; + bool online; + int rc, i; + + DPRINTK("ENTER\n"); + + ahci_stop_engine(ap); + + for (i = 0; i < 2; i++) { + u16 val; + u32 sstatus; + int port = ap->port_no; + struct ata_host *host = ap->host; + struct pci_dev *pdev = to_pci_dev(host->dev); + + /* clear D2H reception area to properly wait for D2H FIS */ + ata_tf_init(link->device, &tf); + tf.command = ATA_BUSY; + ata_tf_to_fis(&tf, 0, 0, d2h_fis); + + rc = sata_link_hardreset(link, timing, deadline, &online, + ahci_check_ready); + + if (sata_scr_read(link, SCR_STATUS, &sstatus) != 0 || + (sstatus & 0xf) != 1) + break; + + ata_link_printk(link, KERN_INFO, "avn bounce port%d\n", + port); + + pci_read_config_word(pdev, 0x92, &val); + val &= ~(1 << port); + pci_write_config_word(pdev, 0x92, val); + ata_msleep(ap, 1000); + val |= 1 << port; + pci_write_config_word(pdev, 0x92, val); + deadline += tmo; + } + + hpriv->start_engine(ap); + + if (online) + *class = ahci_dev_classify(ap); + + DPRINTK("EXIT, rc=%d, class=%u\n", rc, *class); + return rc; +} + + #ifdef CONFIG_PM static int ahci_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg) { @@ -1201,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 @@ -1224,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) { @@ -1237,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; -intx: + 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; + + /* lagacy intx interrupts */ pci_intx(pdev, 1); + hpriv->irq = pdev->irq; + return 0; } @@ -1284,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 @@ -1410,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 @@ -1462,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); |