diff options
Diffstat (limited to 'drivers/ata/libahci.c')
-rw-r--r-- | drivers/ata/libahci.c | 231 |
1 files changed, 78 insertions, 153 deletions
diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c index 1fd935ff5..85ea5142a 100644 --- a/drivers/ata/libahci.c +++ b/drivers/ata/libahci.c @@ -43,6 +43,7 @@ #include <scsi/scsi_host.h> #include <scsi/scsi_cmnd.h> #include <linux/libata.h> +#include <linux/pci.h> #include "ahci.h" #include "libata.h" @@ -112,6 +113,7 @@ static ssize_t ahci_store_em_buffer(struct device *dev, const char *buf, size_t size); static ssize_t ahci_show_em_supported(struct device *dev, struct device_attribute *attr, char *buf); +static irqreturn_t ahci_single_level_irq_intr(int irq, void *dev_instance); static DEVICE_ATTR(ahci_host_caps, S_IRUGO, ahci_show_host_caps, NULL); static DEVICE_ATTR(ahci_host_cap2, S_IRUGO, ahci_show_host_cap2, NULL); @@ -511,6 +513,9 @@ void ahci_save_initial_config(struct device *dev, struct ahci_host_priv *hpriv) if (!hpriv->start_engine) hpriv->start_engine = ahci_start_engine; + + if (!hpriv->irq_handler) + hpriv->irq_handler = ahci_single_level_irq_intr; } EXPORT_SYMBOL_GPL(ahci_save_initial_config); @@ -592,8 +597,22 @@ EXPORT_SYMBOL_GPL(ahci_start_engine); int ahci_stop_engine(struct ata_port *ap) { void __iomem *port_mmio = ahci_port_base(ap); + struct ahci_host_priv *hpriv = ap->host->private_data; u32 tmp; + /* + * On some controllers, stopping a port's DMA engine while the port + * is in ALPM state (partial or slumber) results in failures on + * subsequent DMA engine starts. For those controllers, put the + * port back in active state before stopping its DMA engine. + */ + if ((hpriv->flags & AHCI_HFLAG_WAKE_BEFORE_STOP) && + (ap->link.lpm_policy > ATA_LPM_MAX_POWER) && + ahci_set_lpm(&ap->link, ATA_LPM_MAX_POWER, ATA_LPM_WAKE_ONLY)) { + dev_err(ap->host->dev, "Failed to wake up port before engine stop\n"); + return -EIO; + } + tmp = readl(port_mmio + PORT_CMD); /* check if the HBA is idle */ @@ -684,11 +703,13 @@ static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy, { struct ata_port *ap = link->ap; struct ahci_host_priv *hpriv = ap->host->private_data; - struct ahci_port_priv *ppriv = ap->private_data; struct ahci_port_priv *pp = ap->private_data; void __iomem *port_mmio = ahci_port_base(ap); if (policy != ATA_LPM_MAX_POWER) { + /* wakeup flag only applies to the max power policy */ + hints &= ~ATA_LPM_WAKE_ONLY; + /* * Disable interrupts on Phy Ready. This keeps us from * getting woken up due to spurious phy ready @@ -702,9 +723,10 @@ static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy, if (hpriv->cap & HOST_CAP_ALPM) { u32 cmd = readl(port_mmio + PORT_CMD); - cmd &= ~(PORT_CMD_ASP | PORT_CMD_ALPE); if (policy == ATA_LPM_MAX_POWER || !(hints & ATA_LPM_HIPM)) { + if (!(hints & ATA_LPM_WAKE_ONLY)) + cmd &= ~(PORT_CMD_ASP | PORT_CMD_ALPE); cmd |= PORT_CMD_ICC_ACTIVE; writel(cmd, port_mmio + PORT_CMD); @@ -712,13 +734,9 @@ static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy, /* wait 10ms to be sure we've come out of LPM state */ ata_msleep(ap, 10); - } else if (policy == ATA_LPM_FIRMWARE_DEFAULTS) { - if (ppriv->init_alpe) - cmd |= PORT_CMD_ALPE; - if (ppriv->init_asp) - cmd |= PORT_CMD_ASP; - writel(cmd, port_mmio + PORT_CMD); + if (hints & ATA_LPM_WAKE_ONLY) + return 0; } else { cmd |= PORT_CMD_ALPE; if (policy == ATA_LPM_MIN_POWER) @@ -733,17 +751,10 @@ static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy, if ((hpriv->cap2 & HOST_CAP2_SDS) && (hpriv->cap2 & HOST_CAP2_SADM) && (link->device->flags & ATA_DFLAG_DEVSLP)) { - switch (policy) { - case ATA_LPM_MIN_POWER: + if (policy == ATA_LPM_MIN_POWER) ahci_set_aggressive_devslp(ap, true); - break; - case ATA_LPM_FIRMWARE_DEFAULTS: - ahci_set_aggressive_devslp(ap, ppriv->init_devslp); - break; - default: + else ahci_set_aggressive_devslp(ap, false); - break; - } } if (policy == ATA_LPM_MAX_POWER) { @@ -1818,44 +1829,27 @@ static void ahci_port_intr(struct ata_port *ap) ahci_handle_port_interrupt(ap, port_mmio, status); } -static irqreturn_t ahci_port_thread_fn(int irq, void *dev_instance) +static irqreturn_t ahci_multi_irqs_intr_hard(int irq, void *dev_instance) { struct ata_port *ap = dev_instance; - struct ahci_port_priv *pp = ap->private_data; void __iomem *port_mmio = ahci_port_base(ap); u32 status; - status = atomic_xchg(&pp->intr_status, 0); - if (!status) - return IRQ_NONE; - - spin_lock_bh(ap->lock); - ahci_handle_port_interrupt(ap, port_mmio, status); - spin_unlock_bh(ap->lock); - - return IRQ_HANDLED; -} - -static irqreturn_t ahci_multi_irqs_intr(int irq, void *dev_instance) -{ - struct ata_port *ap = dev_instance; - void __iomem *port_mmio = ahci_port_base(ap); - struct ahci_port_priv *pp = ap->private_data; - u32 status; - VPRINTK("ENTER\n"); status = readl(port_mmio + PORT_IRQ_STAT); writel(status, port_mmio + PORT_IRQ_STAT); - atomic_or(status, &pp->intr_status); + spin_lock(ap->lock); + ahci_handle_port_interrupt(ap, port_mmio, status); + spin_unlock(ap->lock); VPRINTK("EXIT\n"); - return IRQ_WAKE_THREAD; + return IRQ_HANDLED; } -static u32 ahci_handle_port_intr(struct ata_host *host, u32 irq_masked) +u32 ahci_handle_port_intr(struct ata_host *host, u32 irq_masked) { unsigned int i, handled = 0; @@ -1881,43 +1875,7 @@ static u32 ahci_handle_port_intr(struct ata_host *host, u32 irq_masked) return handled; } - -static irqreturn_t ahci_single_edge_irq_intr(int irq, void *dev_instance) -{ - struct ata_host *host = dev_instance; - struct ahci_host_priv *hpriv; - unsigned int rc = 0; - void __iomem *mmio; - u32 irq_stat, irq_masked; - - VPRINTK("ENTER\n"); - - hpriv = host->private_data; - mmio = hpriv->mmio; - - /* sigh. 0xffffffff is a valid return from h/w */ - irq_stat = readl(mmio + HOST_IRQ_STAT); - if (!irq_stat) - return IRQ_NONE; - - irq_masked = irq_stat & hpriv->port_map; - - spin_lock(&host->lock); - - /* - * HOST_IRQ_STAT behaves as edge triggered latch meaning that - * it should be cleared before all the port events are cleared. - */ - writel(irq_stat, mmio + HOST_IRQ_STAT); - - rc = ahci_handle_port_intr(host, irq_masked); - - spin_unlock(&host->lock); - - VPRINTK("EXIT\n"); - - return IRQ_RETVAL(rc); -} +EXPORT_SYMBOL_GPL(ahci_handle_port_intr); static irqreturn_t ahci_single_level_irq_intr(int irq, void *dev_instance) { @@ -2070,7 +2028,6 @@ static void ahci_post_internal_cmd(struct ata_queued_cmd *qc) static void ahci_set_aggressive_devslp(struct ata_port *ap, bool sleep) { struct ahci_host_priv *hpriv = ap->host->private_data; - struct ahci_port_priv *ppriv = ap->private_data; void __iomem *port_mmio = ahci_port_base(ap); struct ata_device *dev = ap->link.device; u32 devslp, dm, dito, mdat, deto; @@ -2106,32 +2063,26 @@ static void ahci_set_aggressive_devslp(struct ata_port *ap, bool sleep) if (rc) return; - if (ppriv->init_devslp) { - dito = ppriv->init_dito; - deto = ppriv->init_deto; - mdat = ppriv->init_mdat; - } else { - dm = (devslp & PORT_DEVSLP_DM_MASK) >> PORT_DEVSLP_DM_OFFSET; - dito = devslp_idle_timeout / (dm + 1); - if (dito > 0x3ff) - dito = 0x3ff; + dm = (devslp & PORT_DEVSLP_DM_MASK) >> PORT_DEVSLP_DM_OFFSET; + dito = devslp_idle_timeout / (dm + 1); + if (dito > 0x3ff) + dito = 0x3ff; - /* Use the nominal value 10 ms if the read MDAT is zero, - * the nominal value of DETO is 20 ms. - */ - if (dev->devslp_timing[ATA_LOG_DEVSLP_VALID] & - ATA_LOG_DEVSLP_VALID_MASK) { - mdat = dev->devslp_timing[ATA_LOG_DEVSLP_MDAT] & - ATA_LOG_DEVSLP_MDAT_MASK; - if (!mdat) - mdat = 10; - deto = dev->devslp_timing[ATA_LOG_DEVSLP_DETO]; - if (!deto) - deto = 20; - } else { + /* Use the nominal value 10 ms if the read MDAT is zero, + * the nominal value of DETO is 20 ms. + */ + if (dev->devslp_timing[ATA_LOG_DEVSLP_VALID] & + ATA_LOG_DEVSLP_VALID_MASK) { + mdat = dev->devslp_timing[ATA_LOG_DEVSLP_MDAT] & + ATA_LOG_DEVSLP_MDAT_MASK; + if (!mdat) mdat = 10; + deto = dev->devslp_timing[ATA_LOG_DEVSLP_DETO]; + if (!deto) deto = 20; - } + } else { + mdat = 10; + deto = 20; } devslp |= ((dito << PORT_DEVSLP_DITO_OFFSET) | @@ -2294,53 +2245,19 @@ static int ahci_port_suspend(struct ata_port *ap, pm_message_t mesg) } #endif -/* - * Allocate port privdata and read back initial power management configuration - */ -int ahci_setup_port_privdata(struct ata_port *ap) -{ - struct ahci_port_priv *pp; - u32 cmd, devslp; - void __iomem *port_mmio = ahci_port_base(ap); - - pp = kzalloc(sizeof(*pp), GFP_KERNEL); - if (!pp) - return -ENOMEM; - - ap->private_data = pp; - - cmd = readl(port_mmio + PORT_CMD); - - if (cmd & PORT_CMD_ALPE) - pp->init_alpe = true; - - if (cmd & PORT_CMD_ASP) - pp->init_asp = true; - - devslp = readl(port_mmio + PORT_DEVSLP); - - /* devslp unsupported or disabled */ - if (!(devslp & PORT_DEVSLP_DSP) || !(devslp & PORT_DEVSLP_ADSE)) - return 0; - - pp->init_devslp = true; - pp->init_dito = (devslp >> PORT_DEVSLP_DITO_OFFSET) & 0x3ff; - pp->init_deto = (devslp >> PORT_DEVSLP_DETO_OFFSET) & 0xff; - pp->init_mdat = (devslp >> PORT_DEVSLP_MDAT_OFFSET) & 0x1f; - - return 0; -} -EXPORT_SYMBOL_GPL(ahci_setup_port_privdata); - static int ahci_port_start(struct ata_port *ap) { struct ahci_host_priv *hpriv = ap->host->private_data; - struct ahci_port_priv *pp = ap->private_data; struct device *dev = ap->host->dev; + struct ahci_port_priv *pp; void *mem; dma_addr_t mem_dma; size_t dma_sz, rx_fis_sz; + pp = devm_kzalloc(dev, sizeof(*pp), GFP_KERNEL); + if (!pp) + return -ENOMEM; + if (ap->host->n_ports > 1) { pp->irq_desc = devm_kzalloc(dev, 8, GFP_KERNEL); if (!pp->irq_desc) { @@ -2419,6 +2336,8 @@ static int ahci_port_start(struct ata_port *ap) ap->lock = &pp->lock; } + ap->private_data = pp; + /* engage engines, captain */ return ahci_port_resume(ap); } @@ -2532,9 +2451,10 @@ void ahci_set_em_messages(struct ahci_host_priv *hpriv, } EXPORT_SYMBOL_GPL(ahci_set_em_messages); -static int ahci_host_activate_multi_irqs(struct ata_host *host, int irq, +static int ahci_host_activate_multi_irqs(struct ata_host *host, struct scsi_host_template *sht) { + struct ahci_host_priv *hpriv = host->private_data; int i, rc; rc = ata_host_start(host); @@ -2546,6 +2466,7 @@ static int ahci_host_activate_multi_irqs(struct ata_host *host, int irq, */ for (i = 0; i < host->n_ports; i++) { struct ahci_port_priv *pp = host->ports[i]->private_data; + int irq = ahci_irq_vector(hpriv, i); /* Do not receive interrupts sent by dummy ports */ if (!pp) { @@ -2553,14 +2474,14 @@ static int ahci_host_activate_multi_irqs(struct ata_host *host, int irq, continue; } - rc = devm_request_threaded_irq(host->dev, irq + i, - ahci_multi_irqs_intr, - ahci_port_thread_fn, 0, - pp->irq_desc, host->ports[i]); + rc = devm_request_irq(host->dev, irq, ahci_multi_irqs_intr_hard, + 0, pp->irq_desc, host->ports[i]); + if (rc) return rc; - ata_port_desc(host->ports[i], "irq %d", irq + i); + ata_port_desc(host->ports[i], "irq %d", irq); } + return ata_host_register(host, sht); } @@ -2581,14 +2502,18 @@ int ahci_host_activate(struct ata_host *host, struct scsi_host_template *sht) int irq = hpriv->irq; int rc; - if (hpriv->flags & AHCI_HFLAG_MULTI_MSI) - rc = ahci_host_activate_multi_irqs(host, irq, sht); - else if (hpriv->flags & AHCI_HFLAG_EDGE_IRQ) - rc = ata_host_activate(host, irq, ahci_single_edge_irq_intr, - IRQF_SHARED, sht); - else - rc = ata_host_activate(host, irq, ahci_single_level_irq_intr, + if (hpriv->flags & (AHCI_HFLAG_MULTI_MSI | AHCI_HFLAG_MULTI_MSIX)) { + if (hpriv->irq_handler) + dev_warn(host->dev, "both AHCI_HFLAG_MULTI_MSI flag set \ + and custom irq handler implemented\n"); + + rc = ahci_host_activate_multi_irqs(host, sht); + } else { + rc = ata_host_activate(host, irq, hpriv->irq_handler, IRQF_SHARED, sht); + } + + return rc; } EXPORT_SYMBOL_GPL(ahci_host_activate); |