aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/scsi/libata-core.c107
-rw-r--r--include/linux/libata.h6
2 files changed, 99 insertions, 14 deletions
diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c
index a9f79b47b4c..4823ecefb8a 100644
--- a/drivers/scsi/libata-core.c
+++ b/drivers/scsi/libata-core.c
@@ -61,6 +61,11 @@
#include "libata.h"
+/* debounce timing parameters in msecs { interval, duration, timeout } */
+const unsigned long sata_deb_timing_boot[] = { 5, 100, 2000 };
+const unsigned long sata_deb_timing_eh[] = { 25, 500, 2000 };
+const unsigned long sata_deb_timing_before_fsrst[] = { 100, 2000, 5000 };
+
static unsigned int ata_dev_init_params(struct ata_device *dev,
u16 heads, u16 sectors);
static unsigned int ata_dev_set_xfermode(struct ata_device *dev);
@@ -2427,10 +2432,81 @@ err_out:
DPRINTK("EXIT\n");
}
-static int sata_phy_resume(struct ata_port *ap)
+/**
+ * sata_phy_debounce - debounce SATA phy status
+ * @ap: ATA port to debounce SATA phy status for
+ * @params: timing parameters { interval, duratinon, timeout } in msec
+ *
+ * Make sure SStatus of @ap reaches stable state, determined by
+ * holding the same value where DET is not 1 for @duration polled
+ * every @interval, before @timeout. Timeout constraints the
+ * beginning of the stable state. Because, after hot unplugging,
+ * DET gets stuck at 1 on some controllers, this functions waits
+ * until timeout then returns 0 if DET is stable at 1.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep)
+ *
+ * RETURNS:
+ * 0 on success, -errno on failure.
+ */
+int sata_phy_debounce(struct ata_port *ap, const unsigned long *params)
{
- unsigned long timeout = jiffies + (HZ * 5);
- u32 scontrol, sstatus;
+ unsigned long interval_msec = params[0];
+ unsigned long duration = params[1] * HZ / 1000;
+ unsigned long timeout = jiffies + params[2] * HZ / 1000;
+ unsigned long last_jiffies;
+ u32 last, cur;
+ int rc;
+
+ if ((rc = sata_scr_read(ap, SCR_STATUS, &cur)))
+ return rc;
+ cur &= 0xf;
+
+ last = cur;
+ last_jiffies = jiffies;
+
+ while (1) {
+ msleep(interval_msec);
+ if ((rc = sata_scr_read(ap, SCR_STATUS, &cur)))
+ return rc;
+ cur &= 0xf;
+
+ /* DET stable? */
+ if (cur == last) {
+ if (cur == 1 && time_before(jiffies, timeout))
+ continue;
+ if (time_after(jiffies, last_jiffies + duration))
+ return 0;
+ continue;
+ }
+
+ /* unstable, start over */
+ last = cur;
+ last_jiffies = jiffies;
+
+ /* check timeout */
+ if (time_after(jiffies, timeout))
+ return -EBUSY;
+ }
+}
+
+/**
+ * sata_phy_resume - resume SATA phy
+ * @ap: ATA port to resume SATA phy for
+ * @params: timing parameters { interval, duratinon, timeout } in msec
+ *
+ * Resume SATA phy of @ap and debounce it.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep)
+ *
+ * RETURNS:
+ * 0 on success, -errno on failure.
+ */
+int sata_phy_resume(struct ata_port *ap, const unsigned long *params)
+{
+ u32 scontrol;
int rc;
if ((rc = sata_scr_read(ap, SCR_CONTROL, &scontrol)))
@@ -2441,16 +2517,12 @@ static int sata_phy_resume(struct ata_port *ap)
if ((rc = sata_scr_write(ap, SCR_CONTROL, scontrol)))
return rc;
- /* Wait for phy to become ready, if necessary. */
- do {
- msleep(200);
- if ((rc = sata_scr_read(ap, SCR_STATUS, &sstatus)))
- return rc;
- if ((sstatus & 0xf) != 1)
- return 0;
- } while (time_before(jiffies, timeout));
+ /* Some PHYs react badly if SStatus is pounded immediately
+ * after resuming. Delay 200ms before debouncing.
+ */
+ msleep(200);
- return -EBUSY;
+ return sata_phy_debounce(ap, params);
}
/**
@@ -2468,8 +2540,10 @@ static int sata_phy_resume(struct ata_port *ap)
*/
void ata_std_probeinit(struct ata_port *ap)
{
+ static const unsigned long deb_timing[] = { 5, 100, 5000 };
+
/* resume link */
- sata_phy_resume(ap);
+ sata_phy_resume(ap, deb_timing);
/* wait for device */
if (ata_port_online(ap))
@@ -2585,7 +2659,7 @@ int sata_std_hardreset(struct ata_port *ap, unsigned int *class)
msleep(1);
/* bring phy back */
- sata_phy_resume(ap);
+ sata_phy_resume(ap, sata_deb_timing_eh);
/* TODO: phy layer with polling, timeouts, etc. */
if (ata_port_offline(ap)) {
@@ -5718,6 +5792,9 @@ u32 ata_wait_register(void __iomem *reg, u32 mask, u32 val,
* Do not depend on ABI/API stability.
*/
+EXPORT_SYMBOL_GPL(sata_deb_timing_boot);
+EXPORT_SYMBOL_GPL(sata_deb_timing_eh);
+EXPORT_SYMBOL_GPL(sata_deb_timing_before_fsrst);
EXPORT_SYMBOL_GPL(ata_std_bios_param);
EXPORT_SYMBOL_GPL(ata_std_ports);
EXPORT_SYMBOL_GPL(ata_device_add);
@@ -5757,6 +5834,8 @@ EXPORT_SYMBOL_GPL(ata_bmdma_error_handler);
EXPORT_SYMBOL_GPL(ata_bmdma_post_internal_cmd);
EXPORT_SYMBOL_GPL(ata_port_probe);
EXPORT_SYMBOL_GPL(sata_set_spd);
+EXPORT_SYMBOL_GPL(sata_phy_debounce);
+EXPORT_SYMBOL_GPL(sata_phy_resume);
EXPORT_SYMBOL_GPL(sata_phy_reset);
EXPORT_SYMBOL_GPL(__sata_phy_reset);
EXPORT_SYMBOL_GPL(ata_bus_reset);
diff --git a/include/linux/libata.h b/include/linux/libata.h
index c0513c75275..1c167f728fb 100644
--- a/include/linux/libata.h
+++ b/include/linux/libata.h
@@ -607,11 +607,17 @@ struct ata_timing {
#define FIT(v,vmin,vmax) max_t(short,min_t(short,v,vmax),vmin)
+extern const unsigned long sata_deb_timing_boot[];
+extern const unsigned long sata_deb_timing_eh[];
+extern const unsigned long sata_deb_timing_before_fsrst[];
+
extern void ata_port_probe(struct ata_port *);
extern void __sata_phy_reset(struct ata_port *ap);
extern void sata_phy_reset(struct ata_port *ap);
extern void ata_bus_reset(struct ata_port *ap);
extern int sata_set_spd(struct ata_port *ap);
+extern int sata_phy_debounce(struct ata_port *ap, const unsigned long *param);
+extern int sata_phy_resume(struct ata_port *ap, const unsigned long *param);
extern int ata_drive_probe_reset(struct ata_port *ap,
ata_probeinit_fn_t probeinit,
ata_reset_fn_t softreset, ata_reset_fn_t hardreset,