summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2010-09-01 17:50:07 +0200
committerJeff Garzik <jgarzik@redhat.com>2010-10-21 20:21:04 -0400
commit6c8ea89cecd780faa4f4c8ed8b3b6ab88f9fa841 (patch)
treea38ecca3c52cd4ca021137086b39d7dcd8b042cc
parent6b7ae9545ad9875a289f4191c0216b473e313cb9 (diff)
libata: implement LPM support for port multipliers
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>
-rw-r--r--drivers/ata/libata-eh.c21
-rw-r--r--drivers/ata/libata-pmp.c48
-rw-r--r--drivers/ata/libata.h8
3 files changed, 67 insertions, 10 deletions
diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c
index a645cd3ab16..06a4db1ec10 100644
--- a/drivers/ata/libata-eh.c
+++ b/drivers/ata/libata-eh.c
@@ -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
diff --git a/drivers/ata/libata-pmp.c b/drivers/ata/libata-pmp.c
index 505470237d7..3120596d4af 100644
--- a/drivers/ata/libata-pmp.c
+++ b/drivers/ata/libata-pmp.c
@@ -186,6 +186,27 @@ int sata_pmp_scr_write(struct ata_link *link, int reg, u32 val)
}
/**
+ * 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
* @gscr: buffer to read GSCR block into
@@ -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;
diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h
index 55a6f413a55..7c070a4b1c0 100644
--- a/drivers/ata/libata.h
+++ b/drivers/ata/libata.h
@@ -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;