Merge branch 'fix/hda' into for-linus
[pandora-kernel.git] / drivers / ata / libata-sff.c
index 730ef3c..e3877b6 100644 (file)
@@ -33,6 +33,7 @@
  */
 
 #include <linux/kernel.h>
+#include <linux/gfp.h>
 #include <linux/pci.h>
 #include <linux/libata.h>
 #include <linux/highmem.h>
@@ -1667,6 +1668,7 @@ unsigned int ata_sff_host_intr(struct ata_port *ap,
 {
        struct ata_eh_info *ehi = &ap->link.eh_info;
        u8 status, host_stat = 0;
+       bool bmdma_stopped = false;
 
        VPRINTK("ata%u: protocol %d task_state %d\n",
                ap->print_id, qc->tf.protocol, ap->hsm_task_state);
@@ -1699,6 +1701,7 @@ unsigned int ata_sff_host_intr(struct ata_port *ap,
 
                        /* before we do anything else, clear DMA-Start bit */
                        ap->ops->bmdma_stop(qc);
+                       bmdma_stopped = true;
 
                        if (unlikely(host_stat & ATA_DMA_ERR)) {
                                /* error when transfering data to/from memory */
@@ -1716,8 +1719,14 @@ unsigned int ata_sff_host_intr(struct ata_port *ap,
 
        /* check main status, clearing INTRQ if needed */
        status = ata_sff_irq_status(ap);
-       if (status & ATA_BUSY)
-               goto idle_irq;
+       if (status & ATA_BUSY) {
+               if (bmdma_stopped) {
+                       /* BMDMA engine is already stopped, we're screwed */
+                       qc->err_mask |= AC_ERR_HSM;
+                       ap->hsm_task_state = HSM_ST_ERR;
+               } else
+                       goto idle_irq;
+       }
 
        /* ack bmdma irq events */
        ap->ops->sff_irq_clear(ap);
@@ -1762,25 +1771,68 @@ EXPORT_SYMBOL_GPL(ata_sff_host_intr);
 irqreturn_t ata_sff_interrupt(int irq, void *dev_instance)
 {
        struct ata_host *host = dev_instance;
+       bool retried = false;
        unsigned int i;
-       unsigned int handled = 0;
+       unsigned int handled, idle, polling;
        unsigned long flags;
 
        /* TODO: make _irqsave conditional on x86 PCI IDE legacy mode */
        spin_lock_irqsave(&host->lock, flags);
 
+retry:
+       handled = idle = polling = 0;
        for (i = 0; i < host->n_ports; i++) {
-               struct ata_port *ap;
+               struct ata_port *ap = host->ports[i];
+               struct ata_queued_cmd *qc;
 
-               ap = host->ports[i];
-               if (ap &&
-                   !(ap->flags & ATA_FLAG_DISABLED)) {
-                       struct ata_queued_cmd *qc;
+               if (unlikely(ap->flags & ATA_FLAG_DISABLED))
+                       continue;
 
-                       qc = ata_qc_from_tag(ap, ap->link.active_tag);
-                       if (qc && (!(qc->tf.flags & ATA_TFLAG_POLLING)) &&
-                           (qc->flags & ATA_QCFLAG_ACTIVE))
+               qc = ata_qc_from_tag(ap, ap->link.active_tag);
+               if (qc) {
+                       if (!(qc->tf.flags & ATA_TFLAG_POLLING))
                                handled |= ata_sff_host_intr(ap, qc);
+                       else
+                               polling |= 1 << i;
+               } else
+                       idle |= 1 << i;
+       }
+
+       /*
+        * If no port was expecting IRQ but the controller is actually
+        * asserting IRQ line, nobody cared will ensue.  Check IRQ
+        * pending status if available and clear spurious IRQ.
+        */
+       if (!handled && !retried) {
+               bool retry = false;
+
+               for (i = 0; i < host->n_ports; i++) {
+                       struct ata_port *ap = host->ports[i];
+
+                       if (polling & (1 << i))
+                               continue;
+
+                       if (!ap->ops->sff_irq_check ||
+                           !ap->ops->sff_irq_check(ap))
+                               continue;
+
+                       if (idle & (1 << i)) {
+                               ap->ops->sff_check_status(ap);
+                               ap->ops->sff_irq_clear(ap);
+                       } else {
+                               /* clear INTRQ and check if BUSY cleared */
+                               if (!(ap->ops->sff_check_status(ap) & ATA_BUSY))
+                                       retry |= true;
+                               /*
+                                * With command in flight, we can't do
+                                * sff_irq_clear() w/o racing with completion.
+                                */
+                       }
+               }
+
+               if (retry) {
+                       retried = true;
+                       goto retry;
                }
        }
 
@@ -2261,7 +2313,7 @@ EXPORT_SYMBOL_GPL(ata_sff_postreset);
  *     @qc: command
  *
  *     Drain the FIFO and device of any stuck data following a command
- *     failing to complete. In some cases this is neccessary before a
+ *     failing to complete. In some cases this is necessary before a
  *     reset will recover the device.
  *
  */
@@ -3011,6 +3063,7 @@ EXPORT_SYMBOL_GPL(ata_pci_sff_activate_host);
  *     @ppi: array of port_info, must be enough for two ports
  *     @sht: scsi_host_template to use when registering the host
  *     @host_priv: host private_data
+ *     @hflag: host flags
  *
  *     This is a helper function which can be called from a driver's
  *     xxx_init_one() probe function if the hardware uses traditional
@@ -3031,8 +3084,8 @@ EXPORT_SYMBOL_GPL(ata_pci_sff_activate_host);
  *     Zero on success, negative on errno-based value on error.
  */
 int ata_pci_sff_init_one(struct pci_dev *pdev,
-                        const struct ata_port_info * const *ppi,
-                        struct scsi_host_template *sht, void *host_priv)
+                const struct ata_port_info * const *ppi,
+                struct scsi_host_template *sht, void *host_priv, int hflag)
 {
        struct device *dev = &pdev->dev;
        const struct ata_port_info *pi = NULL;
@@ -3067,6 +3120,7 @@ int ata_pci_sff_init_one(struct pci_dev *pdev,
        if (rc)
                goto out;
        host->private_data = host_priv;
+       host->flags |= hflag;
 
        pci_set_master(pdev);
        rc = ata_pci_sff_activate_host(host, ata_sff_interrupt, sht);