libata: implement LPM support for port multipliers
authorTejun Heo <tj@kernel.org>
Wed, 1 Sep 2010 15:50:07 +0000 (17:50 +0200)
committerJeff Garzik <jgarzik@redhat.com>
Fri, 22 Oct 2010 00:21:04 +0000 (20:21 -0400)
Port multipliers can do DIPM on fan-out links fine.  Implement support
for it.  Tested w/ SIMG 57xx and marvell PMPs.  Both the host and
fan-out links enter power save modes nicely.

SIMG 37xx and 47xx report link offline on SStatus causing EH to detach
the devices.  Blacklisted.

Signed-off-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
drivers/ata/libata-eh.c
drivers/ata/libata-pmp.c
drivers/ata/libata.h

index a645cd3ab163a904f091cdfb55cf9bca7a49c746..06a4db1ec10ef376dac93d8e07f19cba5cf15251 100644 (file)
@@ -3232,7 +3232,7 @@ static int ata_eh_maybe_retry_flush(struct ata_device *dev)
 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_port *ap = ata_is_host_link(link) ? link->ap : NULL;
        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;
@@ -3278,9 +3278,12 @@ static int ata_eh_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
                }
        }
 
-       rc = ap->ops->set_lpm(link, policy, hints);
-       if (!rc && ap->slave_link)
-               rc = ap->ops->set_lpm(ap->slave_link, policy, hints);
+       if (ap) {
+               rc = ap->ops->set_lpm(link, policy, hints);
+               if (!rc && ap->slave_link)
+                       rc = ap->ops->set_lpm(ap->slave_link, policy, hints);
+       } else
+               rc = sata_pmp_set_lpm(link, policy, hints);
 
        /*
         * Attribute link config failure to the first (LPM) enabled
@@ -3412,8 +3415,14 @@ static int ata_eh_schedule_probe(struct ata_device *dev)
        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);
+       if (link->lpm_policy > ATA_LPM_MAX_POWER) {
+               if (ata_is_host_link(link))
+                       link->ap->ops->set_lpm(link, ATA_LPM_MAX_POWER,
+                                              ATA_LPM_EMPTY);
+               else
+                       sata_pmp_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
index 505470237d796e68e3eeaa3180e5f7e8ea8cf621..3120596d4afc9aaba293f100f7a075ace6438f9e 100644 (file)
@@ -185,6 +185,27 @@ int sata_pmp_scr_write(struct ata_link *link, int reg, u32 val)
        return 0;
 }
 
+/**
+ *     sata_pmp_set_lpm - configure LPM for a PMP link
+ *     @link: PMP link to configure LPM for
+ *     @policy: target LPM policy
+ *     @hints: LPM hints
+ *
+ *     Configure LPM for @link.  This function will contain any PMP
+ *     specific workarounds if necessary.
+ *
+ *     LOCKING:
+ *     EH context.
+ *
+ *     RETURNS:
+ *     0 on success, -errno on failure.
+ */
+int sata_pmp_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
+                    unsigned hints)
+{
+       return sata_link_scr_lpm(link, policy, true);
+}
+
 /**
  *     sata_pmp_read_gscr - read GSCR block of SATA PMP
  *     @dev: PMP device
@@ -365,6 +386,9 @@ static void sata_pmp_quirks(struct ata_port *ap)
        if (vendor == 0x1095 && devid == 0x3726) {
                /* sil3726 quirks */
                ata_for_each_link(link, ap, EDGE) {
+                       /* link reports offline after LPM */
+                       link->flags |= ATA_LFLAG_NO_LPM;
+
                        /* Class code report is unreliable and SRST
                         * times out under certain configurations.
                         */
@@ -380,6 +404,9 @@ static void sata_pmp_quirks(struct ata_port *ap)
        } else if (vendor == 0x1095 && devid == 0x4723) {
                /* sil4723 quirks */
                ata_for_each_link(link, ap, EDGE) {
+                       /* link reports offline after LPM */
+                       link->flags |= ATA_LFLAG_NO_LPM;
+
                        /* class code report is unreliable */
                        if (link->pmp < 2)
                                link->flags |= ATA_LFLAG_ASSUME_ATA;
@@ -392,6 +419,9 @@ static void sata_pmp_quirks(struct ata_port *ap)
        } else if (vendor == 0x1095 && devid == 0x4726) {
                /* sil4726 quirks */
                ata_for_each_link(link, ap, EDGE) {
+                       /* link reports offline after LPM */
+                       link->flags |= ATA_LFLAG_NO_LPM;
+
                        /* Class code report is unreliable and SRST
                         * times out under certain configurations.
                         * Config device can be at port 0 or 5 and
@@ -952,15 +982,25 @@ static int sata_pmp_eh_recover(struct ata_port *ap)
        if (rc)
                goto link_fail;
 
-       /* Connection status might have changed while resetting other
-        * links, check SATA_PMP_GSCR_ERROR before returning.
-        */
-
        /* clear SNotification */
        rc = sata_scr_read(&ap->link, SCR_NOTIFICATION, &sntf);
        if (rc == 0)
                sata_scr_write(&ap->link, SCR_NOTIFICATION, sntf);
 
+       /*
+        * If LPM is active on any fan-out port, hotplug wouldn't
+        * work.  Return w/ PHY event notification disabled.
+        */
+       ata_for_each_link(link, ap, EDGE)
+               if (link->lpm_policy > ATA_LPM_MAX_POWER)
+                       return 0;
+
+       /*
+        * Connection status might have changed while resetting other
+        * links, enable notification and check SATA_PMP_GSCR_ERROR
+        * before returning.
+        */
+
        /* enable notification */
        if (pmp_dev->flags & ATA_DFLAG_AN) {
                gscr[SATA_PMP_GSCR_FEAT_EN] |= SATA_PMP_FEAT_NOTIFY;
index 55a6f413a5507abd0a0eb680edbf3dab248ca76d..7c070a4b1c084cbce7298656f40f4dd53b9a5826 100644 (file)
@@ -176,6 +176,8 @@ extern int ata_ering_map(struct ata_ering *ering,
 #ifdef CONFIG_SATA_PMP
 extern int sata_pmp_scr_read(struct ata_link *link, int reg, u32 *val);
 extern int sata_pmp_scr_write(struct ata_link *link, int reg, u32 val);
+extern int sata_pmp_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
+                           unsigned hints);
 extern int sata_pmp_attach(struct ata_device *dev);
 #else /* CONFIG_SATA_PMP */
 static inline int sata_pmp_scr_read(struct ata_link *link, int reg, u32 *val)
@@ -188,6 +190,12 @@ static inline int sata_pmp_scr_write(struct ata_link *link, int reg, u32 val)
        return -EINVAL;
 }
 
+static inline int sata_pmp_set_lpm(struct ata_link *link,
+                                  enum ata_lpm_policy policy, unsigned hints)
+{
+       return -EINVAL;
+}
+
 static inline int sata_pmp_attach(struct ata_device *dev)
 {
        return -EINVAL;