[PATCH] AHCI powersaving and port-stopping (2.6.22-rc4)

rami jiossy sramij at yahoo.com
Thu Jun 14 06:01:15 PDT 2007


Skipped content of type multipart/alternative-------------- next part --------------
diff -u -p --recursive linux-2.6.22-rc4/drivers/ata/ahci.c linux-2.6.22-rc4-antu/drivers/ata/ahci.c
--- linux-2.6.22-rc4/drivers/ata/ahci.c	2007-06-10 06:03:38.000000000 +0000
+++ linux-2.6.22-rc4-antu/drivers/ata/ahci.c	2007-06-11 03:12:17.000000000 +0000
@@ -95,7 +95,8 @@ enum {
 	HOST_AHCI_EN		= (1 << 31), /* AHCI enabled */
 
 	/* HOST_CAP bits */
-	HOST_CAP_SSC		= (1 << 14), /* Slumber capable */
+	HOST_CAP_PARTIAL	= (1 << 13), /* Partial state support */
+	HOST_CAP_SLUMBER	= (1 << 14), /* Slumber state support */
 	HOST_CAP_CLO		= (1 << 24), /* Command List Override support */
 	HOST_CAP_SSS		= (1 << 27), /* Staggered Spin-up */
 	HOST_CAP_NCQ		= (1 << 30), /* Native Command Queueing */
@@ -211,6 +212,9 @@ struct ahci_port_priv {
 	unsigned int		ncq_saw_d2h:1;
 	unsigned int		ncq_saw_dmas:1;
 	unsigned int		ncq_saw_sdb:1;
+
+	/* For port-stopping */
+ 	unsigned int		ps_stopped:1;	
 };
 
 static u32 ahci_scr_read (struct ata_port *ap, unsigned int sc_reg);
@@ -228,6 +232,7 @@ static void ahci_thaw(struct ata_port *a
 static void ahci_error_handler(struct ata_port *ap);
 static void ahci_vt8251_error_handler(struct ata_port *ap);
 static void ahci_post_internal_cmd(struct ata_queued_cmd *qc);
+static void ahci_set_powersave(struct ata_port *ap, int ps_state);
 #ifdef CONFIG_PM
 static int ahci_port_suspend(struct ata_port *ap, pm_message_t mesg);
 static int ahci_port_resume(struct ata_port *ap);
@@ -235,6 +240,10 @@ static int ahci_pci_device_suspend(struc
 static int ahci_pci_device_resume(struct pci_dev *pdev);
 #endif
 
+static int stop_port_on_slumber = 1;
+module_param(stop_port_on_slumber, int, 0644);
+MODULE_PARM_DESC(stop_port_on_slumber, "Stop port when entering slumber mode");
+
 static struct scsi_host_template ahci_sht = {
 	.module			= THIS_MODULE,
 	.name			= DRV_NAME,
@@ -284,6 +293,8 @@ static const struct ata_port_operations 
 	.port_resume		= ahci_port_resume,
 #endif
 
+	.set_powersave		= ahci_set_powersave,
+
 	.port_start		= ahci_port_start,
 	.port_stop		= ahci_port_stop,
 };
@@ -715,6 +726,74 @@ static void ahci_power_down(struct ata_p
 	writel(cmd, port_mmio + PORT_CMD);
 }
 #endif
+static void ahci_update_sctl_spm(struct ata_port *ap, u8 sctl_spm,
+				 int may_push_sctl)
+{
+	static const u32 icc_map[] = {
+		[0x1]	= PORT_CMD_ICC_PARTIAL,
+		[0x2]	= PORT_CMD_ICC_SLUMBER,
+		[0x4]	= PORT_CMD_ICC_ACTIVE,
+	};
+	void __iomem *port_mmio = ahci_port_base(ap);
+ 	struct ahci_host_priv *hpriv = ap->host->private_data;
+ 	struct ahci_port_priv *pp = ap->private_data;
+	u32 tmp;
+
+	tmp = readl(port_mmio + PORT_CMD);
+
+ 	/* update iff the requested mode is supported && ICC status is idle */
+ 	if ((sctl_spm != 0x2 || (hpriv->cap & HOST_CAP_SLUMBER)) &&
+ 	    !(tmp & PORT_CMD_ICC_MASK))
+		writel(tmp | icc_map[sctl_spm], port_mmio + PORT_CMD);
+
+	if (sctl_spm != 0x2 || !stop_port_on_slumber)
+		return;
+
+	/* Just entered slumber, stop the port.  We don't care if it
+	 * succeeds or not.
+	 */
+	if(0)ata_port_printk(ap, KERN_INFO,
+			"stopping port to save power\n");
+
+	tmp &= ~PORT_CMD_START;
+	writel(tmp, port_mmio + PORT_CMD);
+	pp->ps_stopped = 1;
+}
+
+static unsigned long ahci_hips_timer_fn(struct ata_port *ap, int seq)
+{
+	u8 sctl_ipm = ata_scontrol_field(ap->scontrol, ATA_SCTL_IPM);
+
+	return sata_do_hips_timer_fn(ap, seq, sctl_ipm, ahci_update_sctl_spm);
+}
+
+static void ahci_set_powersave(struct ata_port *ap, int ps_state)
+{
+	void __iomem *port_mmio = ahci_port_base(ap);
+	struct ahci_host_priv *hpriv = ap->host->private_data;
+	u8 sctl_ipm;
+
+	/* determine IPM */
+	sctl_ipm = 0x3;
+	if (hpriv->cap & HOST_CAP_PARTIAL)
+		sctl_ipm &= ~0x1;
+ 	if ((hpriv->cap & HOST_CAP_SLUMBER) ||
+ 	    (ata_ps_dynamic(ps_state) == ATA_PS_HIPS && stop_port_on_slumber))
+		sctl_ipm &= ~0x2;
+
+	/* turn off SError.N IRQ if entering dynamic powersave mode */
+	if (ata_ps_dynamic(ps_state))
+		writel(DEF_PORT_IRQ & ~PORT_IRQ_PHYRDY,
+		       port_mmio + PORT_IRQ_MASK);
+
+	/* do standard SATA set_powersave */
+	if (ata_ps_dynamic(ps_state) == ATA_PS_HIPS) {
+		ap->ps_timer_fn = ahci_hips_timer_fn;
+		sata_determine_hips_params(ap, &sctl_ipm);
+	}
+
+	sata_do_set_powersave(ap, ps_state, sctl_ipm, ahci_update_sctl_spm);
+}
 
 static void ahci_init_port(struct ata_port *ap)
 {
@@ -1111,6 +1190,11 @@ static void ahci_qc_prep(struct ata_queu
 	const u32 cmd_fis_len = 5; /* five dwords */
 	unsigned int n_elem;
 
+	if (pp->ps_stopped) {
+		ahci_start_engine(ap);
+		pp->ps_stopped = 0;
+	}
+
 	/*
 	 * Fill in command table information.  First, the header,
 	 * a SATA Register - Host to Device command FIS.
@@ -1219,6 +1303,9 @@ static void ahci_host_intr(struct ata_po
 	status = readl(port_mmio + PORT_IRQ_STAT);
 	writel(status, port_mmio + PORT_IRQ_STAT);
 
+	if (ata_ps_dynamic(ap->ps_state))
+		status &= ~PORT_IRQ_PHYRDY;
+
 	if (unlikely(status & PORT_IRQ_ERROR)) {
 		ahci_error_intr(ap, status);
 		return;
@@ -1713,6 +1800,9 @@ static int ahci_init_one(struct pci_dev 
 	/* prepare host */
 	if (!(pi.flags & AHCI_FLAG_NO_NCQ) && (hpriv->cap & HOST_CAP_NCQ))
 		pi.flags |= ATA_FLAG_NCQ;
+ 	if ((hpriv->cap & (HOST_CAP_PARTIAL | HOST_CAP_SLUMBER)) ||
+ 	    stop_port_on_slumber)
+ 		pi.flags |= ATA_FLAG_HIPS | ATA_FLAG_DIPS;
 
 	host = ata_host_alloc_pinfo(&pdev->dev, ppi, fls(hpriv->port_map));
 	if (!host)
diff -u -p --recursive linux-2.6.22-rc4/drivers/ata/libata-core.c linux-2.6.22-rc4-antu/drivers/ata/libata-core.c
--- linux-2.6.22-rc4/drivers/ata/libata-core.c	2007-06-10 06:03:38.000000000 +0000
+++ linux-2.6.22-rc4-antu/drivers/ata/libata-core.c	2007-06-10 07:57:45.000000000 +0000
@@ -72,6 +72,12 @@ static unsigned int ata_dev_init_params(
 static unsigned int ata_dev_set_xfermode(struct ata_device *dev);
 static void ata_dev_xfermask(struct ata_device *dev);
 
+static int ata_param_set_powersave(const char *val, struct kernel_param *kp);
+static int ata_param_set_hips_timeout(const char *val, struct kernel_param *kp);
+
+static DEFINE_MUTEX(ata_all_ports_mutex);
+static LIST_HEAD(ata_all_ports);
+
 unsigned int ata_print_id = 1;
 static struct workqueue_struct *ata_wq;
 
@@ -101,12 +107,60 @@ int libata_noacpi = 1;
 module_param_named(noacpi, libata_noacpi, int, 0444);
 MODULE_PARM_DESC(noacpi, "Disables the use of ACPI in suspend/resume when set");
 
+static int libata_powersave = 0;	/* protected by all_ports_mutex */
+module_param_call(powersave, ata_param_set_powersave, param_get_int,
+		  &libata_powersave, 0644);
+MODULE_PARM_DESC(powersave, "Powersave mode (0=none, 1=HIPS, 2=DIPS, "
+		 "3=static, 4=HIPS/static, 5=DIPS/static)");
+
+static unsigned long libata_partial_timeout = 100;
+module_param_call(partial_timeout, ata_param_set_hips_timeout, param_get_ulong,
+		  &libata_partial_timeout, 0644);
+MODULE_PARM_DESC(partial_timeout, "Host-initiated partial powersave timeout "
+		 "(milliseconds, default 100, 0 to disable)");
+
+static unsigned long libata_slumber_timeout = 3000;
+module_param_call(slumber_timeout, ata_param_set_hips_timeout, param_get_ulong,
+		  &libata_slumber_timeout, 0644);
+MODULE_PARM_DESC(slumber_timeout, "Host-initiated slumber powersave timeout "
+		 "(milliseconds, default 3000, 0 to disable)");
+
 MODULE_AUTHOR("Jeff Garzik");
 MODULE_DESCRIPTION("Library module for ATA devices");
 MODULE_LICENSE("GPL");
 MODULE_VERSION(DRV_VERSION);
 
 
+int ata_port_nr_vacant(struct ata_port *ap)
+{
+	int i, cnt = 0;
+
+	for (i = 0; i < ATA_MAX_DEVICES; i++)
+		if (ap->device[i].class == ATA_DEV_UNKNOWN)
+			cnt++;
+	return cnt;
+}
+
+int ata_port_nr_enabled(struct ata_port *ap)
+{
+	int i, cnt = 0;
+
+	for (i = 0; i < ATA_MAX_DEVICES; i++)
+		if (ata_dev_enabled(&ap->device[i]))
+			cnt++;
+	return cnt;
+}
+
+int ata_port_nr_ready(struct ata_port *ap)
+{
+	int i, cnt = 0;
+
+	for (i = 0; i < ATA_MAX_DEVICES; i++)
+		if (ata_dev_ready(&ap->device[i]))
+			cnt++;
+	return cnt;
+}
+
 /**
  *	ata_tf_to_fis - Convert ATA taskfile to SATA FIS structure
  *	@tf: Taskfile to convert
@@ -2445,26 +2499,19 @@ int sata_down_spd_limit(struct ata_port 
 	return 0;
 }
 
-static int __sata_set_spd_needed(struct ata_port *ap, u32 *scontrol)
+static inline int sata_spd_val(struct ata_port *ap)
 {
-	u32 spd, limit;
-
 	if (ap->sata_spd_limit == UINT_MAX)
-		limit = 0;
+		return 0;
 	else
-		limit = fls(ap->sata_spd_limit);
-
-	spd = (*scontrol >> 4) & 0xf;
-	*scontrol = (*scontrol & ~0xf0) | ((limit & 0xf) << 4);
-
-	return spd != limit;
+		return fls(ap->sata_spd_limit);
 }
 
 /**
  *	sata_set_spd_needed - is SATA spd configuration needed
  *	@ap: Port in question
  *
- *	Test whether the spd limit in SControl matches
+ *	Test whether the spd limit in ap->scontrol matches
  *	@ap->sata_spd_limit.  This function is used to determine
  *	whether hardreset is necessary to apply SATA spd
  *	configuration.
@@ -2477,12 +2524,9 @@ static int __sata_set_spd_needed(struct 
  */
 int sata_set_spd_needed(struct ata_port *ap)
 {
-	u32 scontrol;
-
-	if (sata_scr_read(ap, SCR_CONTROL, &scontrol))
-		return 0;
+	u32 spd = (ap->scontrol >> 4) & 0xf;
 
-	return __sata_set_spd_needed(ap, &scontrol);
+	return spd != sata_spd_val(ap);
 }
 
 /**
@@ -2500,16 +2544,12 @@ int sata_set_spd_needed(struct ata_port 
  */
 int sata_set_spd(struct ata_port *ap)
 {
-	u32 scontrol;
 	int rc;
 
-	if ((rc = sata_scr_read(ap, SCR_CONTROL, &scontrol)))
-		return rc;
-
-	if (!__sata_set_spd_needed(ap, &scontrol))
+	if (!sata_set_spd_needed(ap))
 		return 0;
 
-	if ((rc = sata_scr_write(ap, SCR_CONTROL, scontrol)))
+	if ((rc = sata_update_scontrol(ap, ATA_SCTL_SPD, sata_spd_val(ap))))
 		return rc;
 
 	return 1;
@@ -3303,15 +3343,9 @@ int sata_phy_debounce(struct ata_port *a
 int sata_phy_resume(struct ata_port *ap, const unsigned long *params,
 		    unsigned long deadline)
 {
-	u32 scontrol;
 	int rc;
 
-	if ((rc = sata_scr_read(ap, SCR_CONTROL, &scontrol)))
-		return rc;
-
-	scontrol = (scontrol & 0x0f0) | 0x300;
-
-	if ((rc = sata_scr_write(ap, SCR_CONTROL, scontrol)))
+	if ((rc = sata_update_scontrol(ap, ATA_SCTL_DET, 0x0)))
 		return rc;
 
 	/* Some PHYs react badly if SStatus is pounded immediately
@@ -3452,7 +3486,6 @@ int ata_std_softreset(struct ata_port *a
 int sata_port_hardreset(struct ata_port *ap, const unsigned long *timing,
 			unsigned long deadline)
 {
-	u32 scontrol;
 	int rc;
 
 	DPRINTK("ENTER\n");
@@ -3463,24 +3496,14 @@ int sata_port_hardreset(struct ata_port 
 		 * reconfiguration.  This works for at least ICH7 AHCI
 		 * and Sil3124.
 		 */
-		if ((rc = sata_scr_read(ap, SCR_CONTROL, &scontrol)))
-			goto out;
-
-		scontrol = (scontrol & 0x0f0) | 0x304;
-
-		if ((rc = sata_scr_write(ap, SCR_CONTROL, scontrol)))
+		if ((rc = sata_update_scontrol(ap, ATA_SCTL_DET, 0x4)))
 			goto out;
 
 		sata_set_spd(ap);
 	}
 
 	/* issue phy wake/reset */
-	if ((rc = sata_scr_read(ap, SCR_CONTROL, &scontrol)))
-		goto out;
-
-	scontrol = (scontrol & 0x0f0) | 0x301;
-
-	if ((rc = sata_scr_write_flush(ap, SCR_CONTROL, scontrol)))
+	if ((rc = sata_update_scontrol(ap, ATA_SCTL_DET, 0x1)))
 		goto out;
 
 	/* Couldn't find anything in SATA I/II specs, but AHCI-1.1
@@ -3600,6 +3623,217 @@ void ata_std_postreset(struct ata_port *
 	DPRINTK("EXIT\n");
 }
 
+static void sata_std_update_sctl_spm(struct ata_port *ap, u8 sctl_spm,
+				     int may_push_sctl)
+{
+	if (may_push_sctl)
+		sata_update_scontrol_push(ap, ATA_SCTL_SPM, sctl_spm);
+	else
+		sata_update_scontrol(ap, ATA_SCTL_SPM, sctl_spm);
+}
+
+/**
+ *	sata_do_hips_timer_fn - helper to build SATA HIPS timer callback
+ *	@ap: target ATA port
+ *	@seq: current PS sequence
+ *	@sctl_ipm: current SControl IPM
+ *	@update_sctl_spm: update SControl SPM method
+ *
+ *	Implements standard SATA OS-driven host-initiated link
+ *	powersave.  @sctl_ipm is used to determine which powersave
+ *	modes are allowed and @update_sctl_spm is used to actually
+ *	transit powersave mode.
+ *
+ *	LOCKING:
+ *	spin_lock_irqsave(ap->lock).
+ *
+ *	RETURNS:
+ *	Timeout in jiffies if next PS sequence is needed, 0 otherwise.
+ */
+unsigned long sata_do_hips_timer_fn(struct ata_port *ap, int seq, u8 sctl_ipm,
+				    ata_update_sctl_spm_fn_t update_sctl_spm)
+{
+	unsigned long next_timeout = 0;
+
+	switch (seq) {
+	case 0:
+		if (!(sctl_ipm & 0x1)) {
+			update_sctl_spm(ap, 0x1, 0);
+
+			if (!(sctl_ipm & 0x2))
+				next_timeout = ap->ps_2nd_timeout;
+		} else if (!(sctl_ipm & 0x2))
+			update_sctl_spm(ap, 0x2, 0);
+		break;
+
+	case 1:
+		/* Slumber timeout expired.  Cannot directly transit
+		 * to slumber from partial.  Transit to active first.
+		 */
+		update_sctl_spm(ap, 0x4, 0);
+
+		/* spec says 1ms max, be generous and give it 5 */
+		next_timeout = msecs_to_jiffies(5);
+		break;
+
+	case 2:
+		update_sctl_spm(ap, 0x2, 0);
+		break;
+	}
+
+	return next_timeout;
+}
+
+/**
+ *	sata_std_hips_timer_fn - SATA standard powersave HIPS timer callback
+ *	@ap: target ATA port
+ *	@seq: current PS sequence
+ *
+ *	SATA standard powersave HIPS timer callback.
+ *
+ *	LOCKING:
+ *	spin_lock_irqsave(ap->lock).
+ *
+ *	RETURNS:
+ *	Timeout in jiffies if next PS sequence is needed, 0 otherwise.
+ */
+unsigned long sata_std_hips_timer_fn(struct ata_port *ap, int seq)
+{
+	u8 sctl_ipm = ata_scontrol_field(ap->scontrol, ATA_SCTL_IPM);
+
+	return sata_do_hips_timer_fn(ap, seq, sctl_ipm,
+				     sata_std_update_sctl_spm);
+}
+
+/**
+ *	sata_do_set_powersave - helper to build ->set_powersave method
+ *	@ap: target ATA port
+ *	@ps_state: target powersave state
+ *	@sctl_ipm: SControl IPM value for HIPS/DIPS
+ *	@update_sctl_spm: update SControl SPM method (can be NULL)
+ *
+ *	This function helps building ->set_powersave method for
+ *	controllers with standard SCR registers.
+ *
+ *	LOCKING:
+ *	None (must be called from EH context).
+ */
+void sata_do_set_powersave(struct ata_port *ap, int ps_state, u8 sctl_ipm,
+			   ata_update_sctl_spm_fn_t update_sctl_spm)
+{
+	struct ata_eh_context *ehc = &ap->eh_context;
+	unsigned long flags;
+
+	if (ps_state == ATA_PS_NONE) {
+		/* powersave off */
+		if (ata_ps_dynamic(ap->ps_state)) {
+			/* Make link active.  If host cannot issue PS
+			 * state change, the link can be in PS when
+			 * recovery kicks in, which results in SRST
+			 * failure as link looks offline.  Force
+			 * hardreset in such cases.
+			 */
+			if (update_sctl_spm)
+				update_sctl_spm(ap, 0x4, 1);
+			else if (ehc->i.action & ATA_EH_RESET_MASK)
+				ehc->i.action |= ATA_EH_HARDRESET;
+		}
+		sata_update_scontrol_push(ap, ATA_SCTL_IPM, 0x3);
+		sata_update_scontrol(ap, ATA_SCTL_DET, 0x0);
+		return;
+	}
+
+	/* entering powersave mode */
+	if (ata_ps_static(ps_state) && !ata_port_nr_ready(ap)) {
+		/* no ready device, power down PHY */
+		sata_update_scontrol(ap, ATA_SCTL_DET, 0x4);
+		return;
+	}
+
+	switch (ata_ps_dynamic(ps_state)) {
+	case ATA_PS_NONE:
+		break;
+
+	case ATA_PS_HIPS:
+		sata_update_scontrol(ap, ATA_SCTL_IPM, sctl_ipm);
+
+		if (ap->ps_timer_fn && ap->ps_timeout) {
+			spin_lock_irqsave(ap->lock, flags);
+			ap->pflags |= ATA_PFLAG_PS_TIMER;
+			spin_unlock_irqrestore(ap->lock, flags);
+		}
+		break;
+
+	case ATA_PS_DIPS:
+		sata_update_scontrol(ap, ATA_SCTL_IPM, sctl_ipm);
+		break;
+	}
+}
+
+/**
+ *	sata_determine_hips_params - determine standard HIPS params
+ *	@ap: target ATA port
+ *	@sctl_ipm: I/O argument to constraint and return resulting SControl IPM
+ *
+ *	Determine standard HIPS parameters from two module parameters
+ *	- libata.partial_timeout and libata.slumber_timeout.
+ *	Determined timeout values are stored in ap->ps_[2nd_]timeout
+ *	and SControl IPM value in *@sctl_ipm.
+ *
+ *	LOCKING:
+ *	None.
+ */
+void sata_determine_hips_params(struct ata_port *ap, u8 *sctl_ipm)
+{
+	unsigned long partial_tout = msecs_to_jiffies(libata_partial_timeout);
+	unsigned long slumber_tout = msecs_to_jiffies(libata_slumber_timeout);
+
+	if (!(*sctl_ipm & 0x1) && partial_tout &&
+	    (!slumber_tout || partial_tout < slumber_tout)) {
+		ap->ps_timeout = partial_tout;
+
+		if (slumber_tout)
+			ap->ps_2nd_timeout = slumber_tout - partial_tout;
+		else
+			*sctl_ipm |= 0x2;
+	} else if (!(*sctl_ipm & 0x2) && slumber_tout) {
+		*sctl_ipm |= 0x1;
+		ap->ps_timeout = slumber_tout;
+	} else {
+		*sctl_ipm |= 0x3;
+		ap->ps_timeout = 0;
+
+		ata_port_printk(ap, KERN_WARNING, "failed to initialize "
+				"HIPS timer, illegal parameters\n");
+	}
+}
+
+/**
+ *	sata_std_set_powersave - standard SATA set_powersave method
+ *	@ap: target ATA port
+ *	@ps_state: target powersave state
+ *
+ *	Standard SATA set_powersave method.
+ *
+ *	LOCKING:
+ *	None (must be called from EH context).
+ */
+void sata_std_set_powersave(struct ata_port *ap, int ps_state)
+{
+	ata_update_sctl_spm_fn_t update_sctl_spm = NULL;
+	u8 sctl_ipm = 0;
+
+	if (ap->flags & ATA_FLAG_HIPS)
+		update_sctl_spm = sata_std_update_sctl_spm;
+
+	if (ata_ps_dynamic(ps_state) == ATA_PS_HIPS) {
+		ap->ps_timer_fn = sata_std_hips_timer_fn;
+		sata_determine_hips_params(ap, &sctl_ipm);
+	}
+
+	sata_do_set_powersave(ap, ps_state, sctl_ipm, update_sctl_spm);
+}
+
 /**
  *	ata_dev_same_device - Determine whether new ID matches configured device
  *	@dev: device to compare against
@@ -4445,6 +4679,66 @@ void ata_data_xfer_noirq(struct ata_devi
 	local_irq_restore(flags);
 }
 
+/**
+ *	ata_param_set_powersave - param_set method for libata.powersave
+ *	@val: input value from user
+ *	@kp: kernel_param pointing to libata.powersave
+ *
+ *	This function is invoked when user writes to module parameter
+ *	node /sys/module/libata/parameters/powersave and responsible
+ *	for changing powersave configuration accordingly.
+ *
+ *	LOCKING:
+ *	Kernel thread context (may sleep).
+ *
+ *	RETURNS:
+ *	0 on success, -errno otherwise.
+ */
+static int ata_param_set_powersave(const char *val, struct kernel_param *kp)
+{
+	struct kernel_param tkp;
+	struct ata_port *ap;
+	int new_val, rc;
+
+	tkp = *kp;
+	tkp.arg = &new_val;
+
+	rc = param_set_int(val, &tkp);
+	if (rc)
+		return rc;
+	if (new_val == libata_powersave)
+		return 0;
+	if (new_val < 0 || new_val >= ATA_PS_NR_STATES)
+		return -EINVAL;
+
+	/* powersave state change requested */
+	mutex_lock(&ata_all_ports_mutex);
+
+	list_for_each_entry(ap, &ata_all_ports, all_ports_entry) {
+		unsigned long flags;
+
+		/* set target ps_state and schedule EH */
+		spin_lock_irqsave(ap->lock, flags);
+
+		ap->target_ps_state = new_val;
+
+		ap->eh_info.action |= ATA_EH_SOFTRESET;
+		ap->eh_info.flags |= ATA_EHI_QUIET;
+		ata_port_schedule_eh(ap);
+
+		spin_unlock_irqrestore(ap->lock, flags);
+	}
+
+	/* wait for EH */
+	list_for_each_entry(ap, &ata_all_ports, all_ports_entry)
+		ata_port_wait_eh(ap);
+
+	libata_powersave = new_val;
+
+	mutex_unlock(&ata_all_ports_mutex);
+
+	return 0;
+}
 
 /**
  *	ata_pio_sector - Transfer a sector of data.
@@ -5211,13 +5505,15 @@ void ata_qc_complete(struct ata_queued_c
 	 * taken care of.
 	 */
 	if (ap->ops->error_handler) {
+		int internal = ata_tag_internal(qc->tag);
+
 		WARN_ON(ap->pflags & ATA_PFLAG_FROZEN);
 
 		if (unlikely(qc->err_mask))
 			qc->flags |= ATA_QCFLAG_FAILED;
 
 		if (unlikely(qc->flags & ATA_QCFLAG_FAILED)) {
-			if (!ata_tag_internal(qc->tag)) {
+			if (!internal) {
 				/* always fill result TF for failed qc */
 				fill_result_tf(qc);
 				ata_qc_schedule_eh(qc);
@@ -5230,6 +5526,13 @@ void ata_qc_complete(struct ata_queued_c
 			fill_result_tf(qc);
 
 		__ata_qc_complete(qc);
+
+		/* update PS timer */
+		if ((ap->pflags & ATA_PFLAG_PS_TIMER) &&
+		    !ap->qc_active && !internal) {
+			ap->ps_seq = 0;
+			mod_timer(&ap->ps_timer, jiffies + ap->ps_timeout);
+		}
 	} else {
 		if (qc->flags & ATA_QCFLAG_EH_SCHEDULED)
 			return;
@@ -5337,6 +5640,9 @@ void ata_qc_issue(struct ata_queued_cmd 
 	 */
 	WARN_ON(ap->ops->error_handler && ata_tag_valid(ap->active_tag));
 
+	if (ap->pflags & ATA_PFLAG_PS_TIMER)
+		del_timer(&ap->ps_timer);
+
 	if (qc->tf.protocol == ATA_PROT_NCQ) {
 		WARN_ON(ap->sactive & (1 << qc->tag));
 		ap->sactive |= 1 << qc->tag;
@@ -5745,6 +6051,77 @@ int sata_scr_write_flush(struct ata_port
 }
 
 /**
+ *	sata_update_scontrol_push - push SControl register update
+ *	@ap: ATA port to update SControl for
+ *	@sel: ATA_SCTL_* subfield selector
+ *	@val: value to be written
+ *
+ *	SControl hosts several control subfields which need to be
+ *	treated separately.  SControl value is cached on
+ *	initialization and this function updates only the requested
+ *	field of the cache.  This function only accumulates SControl
+ *	updates in the cache and does NOT write the updated value to
+ *	SControl.
+ *
+ *	SControl cache reduces SControl access and helps preserving
+ *	SControl when hardware forgets the configured value (e.g. over
+ *	suspend/resume).
+ *
+ *	LOCKING:
+ *	ap->scontrol is protected by ap->lock while EH is inactive and
+ *	owned by EH while it's active.  So, spin_lock_irqsave(host_set
+ *	lock) out of EH, and none during EH.
+ *
+ *	RETURNS:
+ *	0 on success, negative errno on failure.
+ */
+void sata_update_scontrol_push(struct ata_port *ap, int sel, u8 val)
+{
+	u32 scontrol = ap->scontrol;
+	int shift = sel * 4;
+
+	WARN_ON(val > 0xf);
+	val &= 0xf;
+
+	scontrol = (scontrol & ~(0xf << shift)) | (val << shift);
+
+	ap->scontrol = scontrol;
+}
+
+/**
+ *	sata_update_scontrol - update SControl register
+ *	@ap: ATA port to update SControl for
+ *	@sel: ATA_SCTL_* subfield selector
+ *	@val: value to be written
+ *
+ *	Update SControl cache using sata_update_scontrol_push() and
+ *	write the cached value to the SControl register.
+ *
+ *	LOCKING:
+ *	ap->scontrol is protected by ap->lock while EH is inactive and
+ *	owned by EH while it's active.  So, spin_lock_irqsave(host_set
+ *	lock) out of EH, and none during EH.
+ *
+ *	RETURNS:
+ *	0 on success, negative errno on failure.
+ */
+int sata_update_scontrol(struct ata_port *ap, int sel, u8 val)
+{
+	int rc;
+
+	/* push requested change */
+	sata_update_scontrol_push(ap, sel, val);
+
+	/* always use the flushing version */
+	rc = sata_scr_write_flush(ap, SCR_CONTROL, ap->scontrol);
+
+	/* SPM is oneshot field, don't cache it over write */
+	ap->scontrol &= ~(0xf << (ATA_SCTL_SPM * 4));
+
+	return 0;
+}
+
+/**
  *	ata_port_online - test whether the given port is online
  *	@ap: ATA port to test
  *
@@ -5905,6 +6282,77 @@ void ata_host_resume(struct ata_host *ho
 #endif
 
 /**
+ *	ata_kill_ps_timer - kill powersave timer
+ *	@ap: ATA port of interest
+ *
+ *	Kill powersave timer.  On return from this function, powersave
+ *	timer is guaranteed to be not pending && not running.
+ *
+ *	LOCKING:
+ *	None (must be called from EH context).
+ */
+void ata_kill_ps_timer(struct ata_port *ap)
+{
+	unsigned long flags;
+	int rc;
+
+	if (ap->pflags & ATA_PFLAG_PS_TIMER) {
+		do {
+			spin_lock_irqsave(ap->lock, flags);
+			rc = try_to_del_timer_sync(&ap->ps_timer);
+			ap->pflags &= ~ATA_PFLAG_PS_TIMER;
+			spin_unlock_irqrestore(ap->lock, flags);
+			cpu_relax();
+		} while (rc < 0);
+
+		ap->ps_timer_fn = NULL;
+		ap->ps_timeout = 0;
+	} else
+		BUG_ON(timer_pending(&ap->ps_timer));
+}
+
+/**
+ *	ata_ps_timer_worker - powersave timer worker
+ *	@arg: ATA port of interest
+ *
+ *	This function is called when ap->ps_timer expires.  If the
+ *	condition is right, it calls ap->ps_timer_fn() with the
+ *	current sequence number.
+ *
+ *	libata automatically arms and cancels PS timer on port idle
+ *	and command issue respectively.  libata also initializes
+ *	sequence number to zero when it arms PS timer because of port
+ *	idle.
+ *
+ *	ap->ps_timer_fn() can request requeue of PS timer by returning
+ *	non-zero value which will be used as timeout.  Each requeue
+ *	increments PS sequence number by one.
+ *
+ *	LOCKING:
+ *	None.
+ */
+static void ata_ps_timer_worker(unsigned long arg)
+{
+	struct ata_port *ap = (void *)arg;
+	unsigned long flags;
+
+	spin_lock_irqsave(ap->lock, flags);
+
+	if (!ap->qc_active && !timer_pending(&ap->ps_timer)) {
+		unsigned long next_timeout;
+
+		next_timeout = ap->ps_timer_fn(ap, ap->ps_seq);
+		if (next_timeout) {
+			ap->ps_seq++;
+			ap->ps_timer.expires = jiffies + next_timeout;
+			add_timer(&ap->ps_timer);
+		}
+	}
+
+	spin_unlock_irqrestore(ap->lock, flags);
+}
+
+/**
  *	ata_port_start - Set port up for dma.
  *	@ap: Port to initialize
  *
@@ -6016,6 +6464,7 @@ struct ata_port *ata_port_alloc(struct a
 	INIT_WORK(&ap->scsi_rescan_task, ata_scsi_dev_rescan);
 	INIT_LIST_HEAD(&ap->eh_done_q);
 	init_waitqueue_head(&ap->eh_wait_q);
+	setup_timer(&ap->ps_timer, ata_ps_timer_worker, (unsigned long)ap);
 
 	ap->cbl = ATA_CBL_NONE;
 
@@ -6303,16 +6752,30 @@ int ata_host_register(struct ata_host *h
 	for (i = 0; i < host->n_ports; i++) {
 		struct ata_port *ap = host->ports[i];
 		int irq_line;
-		u32 scontrol;
 		unsigned long xfer_mask;
 
 		/* set SATA cable type if still unset */
 		if (ap->cbl == ATA_CBL_NONE && (ap->flags & ATA_FLAG_SATA))
 			ap->cbl = ATA_CBL_SATA;
 
-		/* init sata_spd_limit to the current value */
-		if (sata_scr_read(ap, SCR_CONTROL, &scontrol) == 0) {
-			int spd = (scontrol >> 4) & 0xf;
+		/* This port is active now, add it to all_ports.
+		 * Initial powersave setting is also configured here
+		 * as it's protected by all_ports_mutex.
+		 */
+		mutex_lock(&ata_all_ports_mutex);
+		list_add_tail(&ap->all_ports_entry, &ata_all_ports);
+		ap->target_ps_state = libata_powersave;
+		mutex_unlock(&ata_all_ports_mutex);
+
+		/* init scontrol and sata_spd_limit to the current value */
+		ap->scontrol = 0x300; /* default value */
+		if (sata_scr_read(ap, SCR_CONTROL, &ap->scontrol) == 0) {
+			int spd;
+
+			/* zero PMP/SPM and no powersaving */
+			ap->scontrol = (ap->scontrol & 0xfff000ff) | 0x300;
+
+			spd = ata_scontrol_field(ap->scontrol, ATA_SCTL_SPD);
 			ap->hw_sata_spd_limit &= (1 << spd) - 1;
 		}
 		ap->sata_spd_limit = ap->hw_sata_spd_limit;
@@ -6489,6 +6952,17 @@ void ata_port_detach(struct ata_port *ap
 	cancel_delayed_work(&ap->hotplug_task);
 	cancel_work_sync(&ap->hotplug_task.work);
 
+	/* kill PS timer and power off */
+	ata_kill_ps_timer(ap);
+
+	if (ap->ops->set_powersave)
+		ap->ops->set_powersave(ap, ATA_PS_STATIC);
+
+	/* this port is dead now, remove from all_ports */
+	mutex_lock(&ata_all_ports_mutex);
+	list_del_init(&ap->all_ports_entry);
+	mutex_unlock(&ata_all_ports_mutex);
+
  skip_eh:
 	/* remove the associated SCSI host */
 	scsi_remove_host(ap->scsi_host);
@@ -6537,6 +7011,57 @@ void ata_std_ports(struct ata_ioports *i
 	ioaddr->command_addr = ioaddr->cmd_addr + ATA_REG_CMD;
 }
 
+/**
+ *	ata_param_set_hips_timeout - param_set method for HIPS timeouts
+ *	@val: input value from user
+ *	@kp: kernel_param pointing to libata.(partial|slumber)_timeout
+ *
+ *	This function is invoked when user writes to module parameter
+ *	node /sys/module/libata/parameters/(partial|slumber)_timeout
+ *	and responsible for changing powersave configuration
+ *	accordingly.
+ *
+ *	LOCKING:
+ *	Kernel thread context (may sleep).
+ *
+ *	RETURNS:
+ *	0 on success, -errno otherwise.
+ */
+static int ata_param_set_hips_timeout(const char *val, struct kernel_param *kp)
+{
+	unsigned long *timeout = kp->arg;
+	struct kernel_param tkp;
+	struct ata_port *ap;
+	unsigned long new_val;
+	int rc;
+
+	tkp = *kp;
+	tkp.arg = &new_val;
+
+	rc = param_set_ulong(val, &tkp);
+	if (rc)
+		return rc;
+	if (new_val == *timeout)
+		return 0;
+
+	/* timeout updated */
+	*timeout = new_val;
+
+	/* tell EH to update PS configuration */
+	mutex_lock(&ata_all_ports_mutex);
+	list_for_each_entry(ap, &ata_all_ports, all_ports_entry) {
+		unsigned long flags;
+
+		spin_lock_irqsave(ap->lock, flags);
+		ap->eh_info.flags |= ATA_EHI_QUIET;
+		ata_port_schedule_eh(ap);
+		spin_unlock_irqrestore(ap->lock, flags);
+	}
+	mutex_unlock(&ata_all_ports_mutex);
+
+	return 0;
+}
+
 
 #ifdef CONFIG_PCI
 
@@ -6593,6 +7118,8 @@ int pci_test_config_bits(struct pci_dev 
 	return (tmp == bits->val) ? 1 : 0;
 }
 
+
+
 #ifdef CONFIG_PM
 void ata_pci_device_do_suspend(struct pci_dev *pdev, pm_message_t mesg)
 {
@@ -6847,6 +7374,11 @@ EXPORT_SYMBOL_GPL(ata_std_softreset);
 EXPORT_SYMBOL_GPL(sata_port_hardreset);
 EXPORT_SYMBOL_GPL(sata_std_hardreset);
 EXPORT_SYMBOL_GPL(ata_std_postreset);
+EXPORT_SYMBOL_GPL(sata_do_hips_timer_fn);
+EXPORT_SYMBOL_GPL(sata_std_hips_timer_fn);
+EXPORT_SYMBOL_GPL(sata_do_set_powersave);
+EXPORT_SYMBOL_GPL(sata_determine_hips_params);
+EXPORT_SYMBOL_GPL(sata_std_set_powersave);
 EXPORT_SYMBOL_GPL(ata_dev_classify);
 EXPORT_SYMBOL_GPL(ata_dev_pair);
 EXPORT_SYMBOL_GPL(ata_port_disable);
@@ -6865,6 +7397,8 @@ EXPORT_SYMBOL_GPL(sata_scr_valid);
 EXPORT_SYMBOL_GPL(sata_scr_read);
 EXPORT_SYMBOL_GPL(sata_scr_write);
 EXPORT_SYMBOL_GPL(sata_scr_write_flush);
+EXPORT_SYMBOL_GPL(sata_update_scontrol_push);
+EXPORT_SYMBOL_GPL(sata_update_scontrol);
 EXPORT_SYMBOL_GPL(ata_port_online);
 EXPORT_SYMBOL_GPL(ata_port_offline);
 #ifdef CONFIG_PM
diff -u -p --recursive linux-2.6.22-rc4/drivers/ata/libata-eh.c linux-2.6.22-rc4-antu/drivers/ata/libata-eh.c
--- linux-2.6.22-rc4/drivers/ata/libata-eh.c	2007-06-10 06:03:38.000000000 +0000
+++ linux-2.6.22-rc4-antu/drivers/ata/libata-eh.c	2007-06-10 06:49:19.000000000 +0000
@@ -293,6 +293,9 @@ void ata_scsi_error(struct Scsi_Host *ho
 	} else
 		spin_unlock_wait(ap->lock);
 
+	/* kill powersave timer before beginning EH */
+	ata_kill_ps_timer(ap);
+
  repeat:
 	/* invoke error handler */
 	if (ap->ops->error_handler) {
@@ -372,6 +375,14 @@ void ata_scsi_error(struct Scsi_Host *ho
 
 	ap->pflags &= ~(ATA_PFLAG_SCSI_HOTPLUG | ATA_PFLAG_RECOVERED);
 
+	/* start PS timer if requested */
+	if (ap->pflags & ATA_PFLAG_PS_TIMER) {
+		BUG_ON(!ap->ps_timer_fn || !ap->ps_timeout);
+		ap->ps_seq = 0;
+		ap->ps_timer.expires = jiffies + ap->ps_timeout;
+		add_timer(&ap->ps_timer);
+	}
+
 	/* tell wait_eh that we're done */
 	ap->pflags &= ~ATA_PFLAG_EH_IN_PROGRESS;
 	wake_up_all(&ap->eh_wait_q);
@@ -1066,7 +1077,8 @@ static void ata_eh_analyze_serror(struct
 		err_mask |= AC_ERR_SYSTEM;
 		action |= ATA_EH_HARDRESET;
 	}
-	if (serror & (SERR_PHYRDY_CHG | SERR_DEV_XCHG))
+	if ((serror & SERR_DEV_XCHG) ||
+	    (!ata_ps_dynamic(ap->ps_state) && (serror & SERR_PHYRDY_CHG)))
 		ata_ehi_hotplugged(&ehc->i);
 
 	ehc->i.err_mask |= err_mask;
@@ -1858,24 +1870,120 @@ static int ata_eh_revalidate_and_attach(
 	return rc;
 }
 
-static int ata_port_nr_enabled(struct ata_port *ap)
+static unsigned int ata_dev_set_dips(struct ata_device *dev, int on)
 {
-	int i, cnt = 0;
+	struct ata_taskfile tf;
+	unsigned int err_mask;
 
-	for (i = 0; i < ATA_MAX_DEVICES; i++)
-		if (ata_dev_enabled(&ap->device[i]))
-			cnt++;
-	return cnt;
+	/* set up set-features taskfile */
+	DPRINTK("set features - SATA DIPS\n");
+
+	ata_tf_init(dev, &tf);
+	tf.command = ATA_CMD_SET_FEATURES;
+	if (on)
+		tf.feature = SETFEATURES_SATA_ON;
+	else
+		tf.feature = SETFEATURES_SATA_OFF;
+	tf.flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE;
+	tf.protocol = ATA_PROT_NODATA;
+	tf.nsect = SETFEATURES_SATA_DIPS;
+
+	err_mask = ata_exec_internal(dev, &tf, NULL, DMA_NONE, NULL, 0);
+
+	DPRINTK("EXIT, err_mask=%x\n", err_mask);
+	return err_mask;
 }
 
-static int ata_port_nr_vacant(struct ata_port *ap)
+static int ata_eh_set_powersave(struct ata_port *ap,
+				struct ata_device **r_failed_dev)
 {
-	int i, cnt = 0;
+	static const char * const ps_strs[] = {
+		[ATA_PS_NONE]		= "none",
+		[ATA_PS_HIPS]		= "HIPS",
+		[ATA_PS_DIPS]		= "DIPS",
+		[ATA_PS_STATIC]		= "static",
+		[ATA_PS_HIPS_STATIC]	= "HIPS/static",
+		[ATA_PS_DIPS_STATIC]	= "DIPS/static",
+	};
+	void (*set_ps)(struct ata_port *, int) = ap->ops->set_powersave;
+	int ps_state, ps_static, ps_dynamic;
+	int dev_dips, dev_hips, i;
+	unsigned long flags;
 
-	for (i = 0; i < ATA_MAX_DEVICES; i++)
-		if (ap->device[i].class == ATA_DEV_UNKNOWN)
-			cnt++;
-	return cnt;
+	/* power up for recovery */
+	if (r_failed_dev == NULL) {
+		if (ap->ps_state && set_ps)
+			set_ps(ap, ATA_PS_NONE);
+		WARN_ON(ap->flags & ATA_PFLAG_PS_TIMER);
+		return 0;
+	}
+
+	/* determine possible ps_state */
+	ps_state = ap->target_ps_state;
+	ps_static = ata_ps_static(ps_state);
+	ps_dynamic = ata_ps_dynamic(ps_state);
+
+	dev_dips = 1;
+	dev_hips = 1;
+	for (i = 0; i < ATA_MAX_DEVICES; i++) {
+		struct ata_device *dev = &ap->device[i];
+
+		if (ata_dev_ready(dev) && !ata_id_has_dips(dev->id))
+			dev_dips = 0;
+		if (ata_dev_ready(dev) && !ata_id_has_hips(dev->id))
+			dev_hips = 0;
+	}
+
+	if (ps_dynamic == ATA_PS_DIPS && (!(ap->flags & ATA_FLAG_DIPS) ||
+					  !dev_dips))
+		ps_dynamic--;
+
+	if (ps_dynamic == ATA_PS_HIPS && (!(ap->flags & ATA_FLAG_HIPS) ||
+					  !dev_hips))
+		ps_dynamic--;
+
+	ps_state = ps_static + ps_dynamic;
+
+	/* At this point, we're in ATA_PS_NONE state and DIPS setting
+	 * is unknown.  Execute the requested PS state transition.
+	 */
+	if (ps_state && set_ps)
+		set_ps(ap, ps_state);
+
+	for (i = 0; i < ATA_MAX_DEVICES; i++) {
+		struct ata_device *dev = &ap->device[i];
+		int is_dips = ps_dynamic == ATA_PS_DIPS;
+		unsigned int err_mask;
+
+		if (!ata_dev_ready(dev) || !ata_id_has_dips(dev->id))
+			continue;
+
+		if (!!ata_id_dips_enabled(dev->id) == is_dips)
+			continue;
+
+		err_mask = ata_dev_set_dips(dev, is_dips);
+		if (err_mask) {
+			spin_lock_irqsave(ap->lock, flags);
+			ap->pflags &= ~ATA_PFLAG_PS_TIMER;
+			spin_unlock_irqrestore(ap->lock, flags);
+
+			if (ps_state && set_ps)
+				set_ps(ap, ATA_PS_NONE);
+
+			*r_failed_dev = dev;
+
+			return -EIO;
+		}
+	}
+
+	if (ap->ps_state != ps_state) {
+		ata_port_printk(ap, KERN_INFO,
+				"powersave mode changed, %s -> %s\n",
+				ps_strs[ap->ps_state], ps_strs[ps_state]);
+		ap->ps_state = ps_state;
+	}
+
+	return 0;
 }
 
 static int ata_eh_skip_recovery(struct ata_port *ap)
@@ -1931,6 +2039,9 @@ static int ata_eh_recover(struct ata_por
 
 	DPRINTK("ENTER\n");
 
+	/* power up for recovery */
+	ata_eh_set_powersave(ap, NULL);
+
 	/* prep for recovery */
 	for (i = 0; i < ATA_MAX_DEVICES; i++) {
 		dev = &ap->device[i];
@@ -1997,6 +2108,11 @@ static int ata_eh_recover(struct ata_por
 		ehc->i.flags &= ~ATA_EHI_SETMODE;
 	}
 
+	/* EH complete, configure powersave mode */
+	rc = ata_eh_set_powersave(ap, &dev);
+	if (rc)
+		goto dev_fail;
+
 	goto out;
 
  dev_fail:
diff -u -p --recursive linux-2.6.22-rc4/drivers/ata/libata.h linux-2.6.22-rc4-antu/drivers/ata/libata.h
--- linux-2.6.22-rc4/drivers/ata/libata.h	2007-06-10 06:03:38.000000000 +0000
+++ linux-2.6.22-rc4-antu/drivers/ata/libata.h	2007-06-10 06:49:19.000000000 +0000
@@ -58,6 +58,10 @@ extern int atapi_enabled;
 extern int atapi_dmadir;
 extern int libata_fua;
 extern int libata_noacpi;
+
+extern int ata_port_nr_vacant(struct ata_port *ap);
+extern int ata_port_nr_enabled(struct ata_port *ap);
+extern int ata_port_nr_ready(struct ata_port *ap);
 extern struct ata_queued_cmd *ata_qc_new_init(struct ata_device *dev);
 extern int ata_build_rw_tf(struct ata_taskfile *tf, struct ata_device *dev,
 			   u64 block, u32 n_block, unsigned int tf_flags,
@@ -91,6 +95,7 @@ extern void ata_dev_select(struct ata_po
                            unsigned int wait, unsigned int can_sleep);
 extern void swap_buf_le16(u16 *buf, unsigned int buf_words);
 extern int ata_flush_cache(struct ata_device *dev);
+extern void ata_kill_ps_timer(struct ata_port *ap);
 extern void ata_dev_init(struct ata_device *dev);
 extern int ata_task_ioctl(struct scsi_device *scsidev, void __user *arg);
 extern int ata_cmd_ioctl(struct scsi_device *scsidev, void __user *arg);
diff -u -p --recursive linux-2.6.22-rc4/drivers/ata/sata_mv.c linux-2.6.22-rc4-antu/drivers/ata/sata_mv.c
--- linux-2.6.22-rc4/drivers/ata/sata_mv.c	2007-06-10 06:03:38.000000000 +0000
+++ linux-2.6.22-rc4-antu/drivers/ata/sata_mv.c	2007-06-10 06:44:52.000000000 +0000
@@ -1974,10 +1974,10 @@ static void __mv_phy_reset(struct ata_po
 
 	/* Issue COMRESET via SControl */
 comreset_retry:
-	sata_scr_write_flush(ap, SCR_CONTROL, 0x301);
+	sata_update_scontrol(ap, ATA_SCTL_DET, 0x1);
 	__msleep(1, can_sleep);
 
-	sata_scr_write_flush(ap, SCR_CONTROL, 0x300);
+	sata_update_scontrol(ap, ATA_SCTL_DET, 0x0);
 	__msleep(20, can_sleep);
 
 	timeout = jiffies + msecs_to_jiffies(200);
diff -u -p --recursive linux-2.6.22-rc4/include/linux/ata.h linux-2.6.22-rc4-antu/include/linux/ata.h
--- linux-2.6.22-rc4/include/linux/ata.h	2007-06-10 06:04:46.000000000 +0000
+++ linux-2.6.22-rc4-antu/include/linux/ata.h	2007-06-10 06:41:12.000000000 +0000
@@ -204,6 +204,12 @@ enum {
 
 	SETFEATURES_SPINUP	= 0x07, /* Spin-up drive */
 
+	SETFEATURES_SATA_ON	= 0x10, /* Enable SATA feature */
+	SETFEATURES_SATA_OFF	= 0x90, /* Disable SATA feature */
+
+	/* SATA feature nsect values */
+	SETFEATURES_SATA_DIPS	= 0x03,
+
 	/* ATAPI stuff */
 	ATAPI_PKT_DMA		= (1 << 0),
 	ATAPI_DMADIR		= (1 << 2),	/* ATAPI data dir:
@@ -225,6 +231,13 @@ enum {
 	SCR_ACTIVE		= 3,
 	SCR_NOTIFICATION	= 4,
 
+	/* SControl subfields, each field is 4 bit wide */
+	ATA_SCTL_DET		= 0, /* lsb */
+	ATA_SCTL_SPD		= 1,
+	ATA_SCTL_IPM		= 2,
+	ATA_SCTL_SPM		= 3,
+	ATA_SCTL_PMP		= 4,
+
 	/* SError bits */
 	SERR_DATA_RECOVERED	= (1 << 0), /* recovered data error */
 	SERR_COMM_RECOVERED	= (1 << 1), /* recovered comm failure */
@@ -305,8 +318,12 @@ struct ata_taskfile {
 #define ata_id_has_pm(id)	((id)[82] & (1 << 3))
 #define ata_id_has_lba(id)	((id)[49] & (1 << 9))
 #define ata_id_has_dma(id)	((id)[49] & (1 << 8))
+#define ata_id_has_sata(id)	((id)[76] && (id)[76] != 0xffff)
 #define ata_id_has_ncq(id)	((id)[76] & (1 << 8))
 #define ata_id_queue_depth(id)	(((id)[75] & 0x1f) + 1)
+#define ata_id_has_hips(id)	(ata_id_has_sata(id) && ((id)[76] & (1 << 9)))
+#define ata_id_has_dips(id)	(ata_id_has_sata(id) && ((id)[78] & (1 << 3)))
+#define ata_id_dips_enabled(id)	(ata_id_has_sata(id) && ((id)[79] & (1 << 3)))
 #define ata_id_removeable(id)	((id)[0] & (1 << 7))
 #define ata_id_has_dword_io(id)	((id)[50] & (1 << 0))
 #define ata_id_iordy_disable(id) ((id)[49] & (1 << 10))
@@ -416,4 +433,9 @@ static inline int lba_48_ok(u64 block, u
 	return ((block + n_block - 1) < ((u64)1 << 48)) && (n_block <= 65536);
 }
 
+static inline u8 ata_scontrol_field(u32 scontrol, int sel)
+{
+	return (scontrol >> (sel * 4)) & 0xf;
+}
+
 #endif /* __LINUX_ATA_H__ */
diff -u -p --recursive linux-2.6.22-rc4/include/linux/libata.h linux-2.6.22-rc4-antu/include/linux/libata.h
--- linux-2.6.22-rc4/include/linux/libata.h	2007-06-10 06:04:47.000000000 +0000
+++ linux-2.6.22-rc4-antu/include/linux/libata.h	2007-06-10 07:59:22.000000000 +0000
@@ -141,6 +141,7 @@ enum {
 	ATA_DFLAG_PIO		= (1 << 8), /* device limited to PIO mode */
 	ATA_DFLAG_NCQ_OFF	= (1 << 9), /* device limited to non-NCQ mode */
 	ATA_DFLAG_SPUNDOWN	= (1 << 10), /* XXX: for spindown_compat */
+	ATA_DFLAG_SUSPENDED  = (1 << 11),  /* device suspended (so not ready) */
 	ATA_DFLAG_INIT_MASK	= (1 << 16) - 1,
 
 	ATA_DFLAG_DETACH	= (1 << 16),
@@ -174,6 +175,8 @@ enum {
 	ATA_FLAG_IGN_SIMPLEX	= (1 << 15), /* ignore SIMPLEX */
 	ATA_FLAG_NO_IORDY	= (1 << 16), /* controller lacks iordy */
 	ATA_FLAG_ACPI_SATA	= (1 << 17), /* need native SATA ACPI layout */
+ 	ATA_FLAG_HIPS		= (1 << 18), /* SATA host-initiated powersave */
+ 	ATA_FLAG_DIPS		= (1 << 19), /* SATA dev-initiated powersave */
 
 	/* The following flag belongs to ap->pflags but is kept in
 	 * ap->flags because it's referenced in many LLDs and will be
@@ -196,6 +199,7 @@ enum {
 	ATA_PFLAG_FLUSH_PORT_TASK = (1 << 16), /* flush port task */
 	ATA_PFLAG_SUSPENDED	= (1 << 17), /* port is suspended (power) */
 	ATA_PFLAG_PM_PENDING	= (1 << 18), /* PM operation pending */
+	ATA_PFLAG_PS_TIMER	= (1 << 19), /* PS timer active */
 
 	/* struct ata_queued_cmd flags */
 	ATA_QCFLAG_ACTIVE	= (1 << 0), /* cmd not yet ack'd to scsi lyer */
@@ -255,6 +259,15 @@ enum {
 	ATA_DMA_PAD_SZ		= 4,
 	ATA_DMA_PAD_BUF_SZ	= ATA_DMA_PAD_SZ * ATA_MAX_QUEUE,
 
+	/* powersave constants */
+	ATA_PS_NONE		= 0, /* no powersave */
+	ATA_PS_HIPS		= 1, /* SATA host-initiated powersave */
+	ATA_PS_DIPS		= 2, /* SATA device-initiated powersave */
+	ATA_PS_STATIC		= 3, /* turn off unoccupied PHY */
+	ATA_PS_HIPS_STATIC	= 4, /* HIPS + STATIC */
+	ATA_PS_DIPS_STATIC	= 5, /* DIPS + STATIC */
+	ATA_PS_NR_STATES	= 6,
+
 	/* ering size */
 	ATA_ERING_SIZE		= 32,
 
@@ -335,6 +348,9 @@ typedef int (*ata_prereset_fn_t)(struct 
 typedef int (*ata_reset_fn_t)(struct ata_port *ap, unsigned int *classes,
 			      unsigned long deadline);
 typedef void (*ata_postreset_fn_t)(struct ata_port *ap, unsigned int *classes);
+typedef unsigned long (*ata_ps_timer_fn_t)(struct ata_port *ap, int seq);
+typedef void (*ata_update_sctl_spm_fn_t)(struct ata_port *ap, u8 sctl_spm,
+					 int may_push_sctl);
 
 struct ata_ioports {
 	void __iomem		*cmd_addr;
@@ -513,6 +529,8 @@ struct ata_port {
 	unsigned int		mwdma_mask;
 	unsigned int		udma_mask;
 	unsigned int		cbl;	/* cable type; ATA_CBL_xxx */
+
+	u32			scontrol;	/* for update_scontrol */
 	unsigned int		hw_sata_spd_limit;
 	unsigned int		sata_spd_limit;	/* SATA PHY speed limit */
 
@@ -545,9 +563,23 @@ struct ata_port {
 	struct list_head	eh_done_q;
 	wait_queue_head_t	eh_wait_q;
 
+	/* powersave (dynamic link power management) */
+	int			target_ps_state;
+	int			ps_state;
+	/* powersave timer, protected by ap->lock */
+	ata_ps_timer_fn_t	ps_timer_fn;	/* initialized by LLD */
+	unsigned long		ps_timeout;	/* ditto */
+	unsigned long		ps_2nd_timeout;	/* owned by LLD */
+
+	int			ps_seq;		/* managed by libata */
+	struct timer_list	ps_timer;	/* ditto */
+
+	/* power management (host suspend and resume) */
 	pm_message_t		pm_mesg;
 	int			*pm_result;
 
+	struct list_head	all_ports_entry;
+
 	void			*private_data;
 
 	u8			sector_buf[ATA_SECT_SIZE]; /* owned by EH */
@@ -604,6 +636,8 @@ struct ata_port_operations {
 	void (*scr_write) (struct ata_port *ap, unsigned int sc_reg,
 			   u32 val);
 
+	void (*set_powersave) (struct ata_port *ap, int ps_state);
+
 	int (*port_suspend) (struct ata_port *ap, pm_message_t mesg);
 	int (*port_resume) (struct ata_port *ap);
 
@@ -680,6 +714,13 @@ extern int sata_port_hardreset(struct at
 extern int sata_std_hardreset(struct ata_port *ap, unsigned int *class,
 			      unsigned long deadline);
 extern void ata_std_postreset(struct ata_port *ap, unsigned int *classes);
+extern unsigned long sata_do_hips_timer_fn(struct ata_port *ap, int seq,
+			u8 sctl_ipm, ata_update_sctl_spm_fn_t update_sctl_spm);
+extern unsigned long sata_std_hips_timer_fn(struct ata_port *ap, int seq);
+extern void sata_do_set_powersave(struct ata_port *ap, int ps_state,
+			u8 sctl_ipm, ata_update_sctl_spm_fn_t update_sctl_spm);
+extern void sata_determine_hips_params(struct ata_port *ap, u8 *sctl_ipm);
+extern void sata_std_set_powersave(struct ata_port *ap, int ps_state);
 extern void ata_port_disable(struct ata_port *);
 extern void ata_std_ports(struct ata_ioports *ioaddr);
 #ifdef CONFIG_PCI
@@ -723,6 +764,8 @@ extern int sata_scr_valid(struct ata_por
 extern int sata_scr_read(struct ata_port *ap, int reg, u32 *val);
 extern int sata_scr_write(struct ata_port *ap, int reg, u32 val);
 extern int sata_scr_write_flush(struct ata_port *ap, int reg, u32 val);
+extern void sata_update_scontrol_push(struct ata_port *ap, int sel, u8 val);
+extern int sata_update_scontrol(struct ata_port *ap, int sel, u8 val);
 extern int ata_port_online(struct ata_port *ap);
 extern int ata_port_offline(struct ata_port *ap);
 #ifdef CONFIG_PM
@@ -985,16 +1028,18 @@ static inline unsigned int ata_dev_enabl
 {
 	return ata_class_enabled(dev->class);
 }
-
 static inline unsigned int ata_dev_disabled(const struct ata_device *dev)
 {
 	return ata_class_disabled(dev->class);
 }
-
 static inline unsigned int ata_dev_absent(const struct ata_device *dev)
 {
 	return ata_class_absent(dev->class);
 }
+static inline unsigned int ata_dev_ready(const struct ata_device *dev)
+{
+	return ata_dev_enabled(dev) && !(dev->flags & ATA_DFLAG_SUSPENDED);
+}
 
 /*
  * port helpers
@@ -1006,6 +1051,21 @@ static inline int ata_port_max_devices(c
 	return 1;
 }
 
+/*
+ * powersave helpers
+ */
+static inline int ata_ps_static(int ps_state)
+{
+	return ps_state >= ATA_PS_STATIC ? ATA_PS_STATIC : 0;
+}
+
+static inline int ata_ps_dynamic(int ps_state)
+{
+	if (ps_state >= ATA_PS_STATIC)
+		ps_state -= ATA_PS_STATIC;
+	return ps_state;
+}
+
 
 static inline u8 ata_chk_status(struct ata_port *ap)
 {


More information about the Power mailing list