libata: reimplement link power management
[linux-2.6.git] / drivers / ata / libata-eh.c
index 96aa75d..a645cd3 100644 (file)
@@ -1580,9 +1580,9 @@ static void ata_eh_analyze_serror(struct ata_link *link)
         * host links.  For disabled PMP links, only N bit is
         * considered as X bit is left at 1 for link plugging.
         */
-       hotplug_mask = 0;
-
-       if (!(link->flags & ATA_LFLAG_DISABLED) || ata_is_host_link(link))
+       if (link->lpm_policy != ATA_LPM_MAX_POWER)
+               hotplug_mask = 0;       /* hotplug doesn't work w/ LPM */
+       else if (!(link->flags & ATA_LFLAG_DISABLED) || ata_is_host_link(link))
                hotplug_mask = SERR_PHYRDY_CHG | SERR_DEV_XCHG;
        else
                hotplug_mask = SERR_PHYRDY_CHG;
@@ -2784,8 +2784,9 @@ int ata_eh_reset(struct ata_link *link, int classify,
        ata_eh_done(link, NULL, ATA_EH_RESET);
        if (slave)
                ata_eh_done(slave, NULL, ATA_EH_RESET);
-       ehc->last_reset = jiffies;      /* update to completion time */
+       ehc->last_reset = jiffies;              /* update to completion time */
        ehc->i.action |= ATA_EH_REVALIDATE;
+       link->lpm_policy = ATA_LPM_UNKNOWN;     /* reset LPM state */
 
        rc = 0;
  out:
@@ -3211,6 +3212,121 @@ static int ata_eh_maybe_retry_flush(struct ata_device *dev)
        return rc;
 }
 
+/**
+ *     ata_eh_set_lpm - configure SATA interface power management
+ *     @link: link to configure power management
+ *     @policy: the link power management policy
+ *     @r_failed_dev: out parameter for failed device
+ *
+ *     Enable SATA Interface power management.  This will enable
+ *     Device Interface Power Management (DIPM) for min_power
+ *     policy, and then call driver specific callbacks for
+ *     enabling Host Initiated Power management.
+ *
+ *     LOCKING:
+ *     EH context.
+ *
+ *     RETURNS:
+ *     0 on success, -errno on failure.
+ */
+static int ata_eh_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
+                         struct ata_device **r_failed_dev)
+{
+       struct ata_port *ap = link->ap;
+       struct ata_eh_context *ehc = &link->eh_context;
+       struct ata_device *dev, *link_dev = NULL, *lpm_dev = NULL;
+       unsigned int hints = ATA_LPM_EMPTY | ATA_LPM_HIPM;
+       unsigned int err_mask;
+       int rc;
+
+       /* if the link or host doesn't do LPM, noop */
+       if ((link->flags & ATA_LFLAG_NO_LPM) || (ap && !ap->ops->set_lpm))
+               return 0;
+
+       /*
+        * DIPM is enabled only for MIN_POWER as some devices
+        * misbehave when the host NACKs transition to SLUMBER.  Order
+        * device and link configurations such that the host always
+        * allows DIPM requests.
+        */
+       ata_for_each_dev(dev, link, ENABLED) {
+               bool hipm = ata_id_has_hipm(dev->id);
+               bool dipm = ata_id_has_dipm(dev->id);
+
+               /* find the first enabled and LPM enabled devices */
+               if (!link_dev)
+                       link_dev = dev;
+
+               if (!lpm_dev && (hipm || dipm))
+                       lpm_dev = dev;
+
+               hints &= ~ATA_LPM_EMPTY;
+               if (!hipm)
+                       hints &= ~ATA_LPM_HIPM;
+
+               /* disable DIPM before changing link config */
+               if (policy != ATA_LPM_MIN_POWER && dipm) {
+                       err_mask = ata_dev_set_feature(dev,
+                                       SETFEATURES_SATA_DISABLE, SATA_DIPM);
+                       if (err_mask && err_mask != AC_ERR_DEV) {
+                               ata_dev_printk(dev, KERN_WARNING,
+                                       "failed to disable DIPM, Emask 0x%x\n",
+                                       err_mask);
+                               rc = -EIO;
+                               goto fail;
+                       }
+               }
+       }
+
+       rc = ap->ops->set_lpm(link, policy, hints);
+       if (!rc && ap->slave_link)
+               rc = ap->ops->set_lpm(ap->slave_link, policy, hints);
+
+       /*
+        * Attribute link config failure to the first (LPM) enabled
+        * device on the link.
+        */
+       if (rc) {
+               if (rc == -EOPNOTSUPP) {
+                       link->flags |= ATA_LFLAG_NO_LPM;
+                       return 0;
+               }
+               dev = lpm_dev ? lpm_dev : link_dev;
+               goto fail;
+       }
+
+       /* host config updated, enable DIPM if transitioning to MIN_POWER */
+       ata_for_each_dev(dev, link, ENABLED) {
+               if (policy == ATA_LPM_MIN_POWER && ata_id_has_dipm(dev->id)) {
+                       err_mask = ata_dev_set_feature(dev,
+                                       SETFEATURES_SATA_ENABLE, SATA_DIPM);
+                       if (err_mask && err_mask != AC_ERR_DEV) {
+                               ata_dev_printk(dev, KERN_WARNING,
+                                       "failed to enable DIPM, Emask 0x%x\n",
+                                       err_mask);
+                               rc = -EIO;
+                               goto fail;
+                       }
+               }
+       }
+
+       link->lpm_policy = policy;
+       if (ap && ap->slave_link)
+               ap->slave_link->lpm_policy = policy;
+       return 0;
+
+fail:
+       /* if no device or only one more chance is left, disable LPM */
+       if (!dev || ehc->tries[dev->devno] <= 2) {
+               ata_link_printk(link, KERN_WARNING,
+                               "disabling LPM on the link\n");
+               link->flags |= ATA_LFLAG_NO_LPM;
+       }
+       if (r_failed_dev)
+               *r_failed_dev = dev;
+       return rc;
+}
+
 static int ata_link_nr_enabled(struct ata_link *link)
 {
        struct ata_device *dev;
@@ -3295,6 +3411,10 @@ static int ata_eh_schedule_probe(struct ata_device *dev)
        ehc->saved_xfer_mode[dev->devno] = 0;
        ehc->saved_ncq_enabled &= ~(1 << dev->devno);
 
+       /* the link maybe in a deep sleep, wake it up */
+       if (link->lpm_policy > ATA_LPM_MAX_POWER)
+               link->ap->ops->set_lpm(link, ATA_LPM_MAX_POWER, ATA_LPM_EMPTY);
+
        /* Record and count probe trials on the ering.  The specific
         * error mask used is irrelevant.  Because a successful device
         * detection clears the ering, this count accumulates only if
@@ -3396,8 +3516,7 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
 {
        struct ata_link *link;
        struct ata_device *dev;
-       int nr_failed_devs;
-       int rc;
+       int rc, nr_fails;
        unsigned long flags, deadline;
 
        DPRINTK("ENTER\n");
@@ -3438,7 +3557,6 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
 
  retry:
        rc = 0;
-       nr_failed_devs = 0;
 
        /* if UNLOADING, finish immediately */
        if (ap->pflags & ATA_PFLAG_UNLOADING)
@@ -3523,13 +3641,17 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
        }
 
        /* the rest */
-       ata_for_each_link(link, ap, EDGE) {
+       nr_fails = 0;
+       ata_for_each_link(link, ap, PMP_FIRST) {
                struct ata_eh_context *ehc = &link->eh_context;
 
+               if (sata_pmp_attached(ap) && ata_is_host_link(link))
+                       goto config_lpm;
+
                /* revalidate existing devices and attach new ones */
                rc = ata_eh_revalidate_and_attach(link, &dev);
                if (rc)
-                       goto dev_fail;
+                       goto rest_fail;
 
                /* if PMP got attached, return, pmp EH will take care of it */
                if (link->device->class == ATA_DEV_PMP) {
@@ -3541,7 +3663,7 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
                if (ehc->i.flags & ATA_EHI_SETMODE) {
                        rc = ata_set_mode(link, &dev);
                        if (rc)
-                               goto dev_fail;
+                               goto rest_fail;
                        ehc->i.flags &= ~ATA_EHI_SETMODE;
                }
 
@@ -3554,7 +3676,7 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
                                        continue;
                                rc = atapi_eh_clear_ua(dev);
                                if (rc)
-                                       goto dev_fail;
+                                       goto rest_fail;
                        }
                }
 
@@ -3564,21 +3686,25 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
                                continue;
                        rc = ata_eh_maybe_retry_flush(dev);
                        if (rc)
-                               goto dev_fail;
+                               goto rest_fail;
                }
 
+       config_lpm:
                /* configure link power saving */
-               if (ehc->i.action & ATA_EH_LPM)
-                       ata_for_each_dev(dev, link, ALL)
-                               ata_dev_enable_pm(dev, ap->lpm_policy);
+               if (link->lpm_policy != ap->target_lpm_policy) {
+                       rc = ata_eh_set_lpm(link, ap->target_lpm_policy, &dev);
+                       if (rc)
+                               goto rest_fail;
+               }
 
                /* this link is okay now */
                ehc->i.flags = 0;
                continue;
 
-dev_fail:
-               nr_failed_devs++;
-               ata_eh_handle_dev_fail(dev, rc);
+       rest_fail:
+               nr_fails++;
+               if (dev)
+                       ata_eh_handle_dev_fail(dev, rc);
 
                if (ap->pflags & ATA_PFLAG_FROZEN) {
                        /* PMP reset requires working host port.
@@ -3590,7 +3716,7 @@ dev_fail:
                }
        }
 
-       if (nr_failed_devs)
+       if (nr_fails)
                goto retry;
 
  out: