[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