* [patch 1/4] Store interrupt value
[not found] <20070809211800.022150464@intel.com>
@ 2007-08-09 21:23 ` Kristen Carlson Accardi
2007-08-15 8:15 ` Jeff Garzik
2007-08-09 21:23 ` [patch 2/4] increase number of allowable device flags Kristen Carlson Accardi
` (2 subsequent siblings)
3 siblings, 1 reply; 11+ messages in thread
From: Kristen Carlson Accardi @ 2007-08-09 21:23 UTC (permalink / raw)
To: jeff; +Cc: htejun, akpm, arjan, linux-kernel, linux-ide,
Kristen Carlson Accardi
Use a stored value for which interrupts to enable. Changing this allows
us to selectively turn off certain interrupts later and have them
stay off.
Signed-off-by: Kristen Carlson Accardi <kristen.c.accardi@intel.com>
Index: 2.6-git/drivers/ata/ahci.c
===================================================================
--- 2.6-git.orig/drivers/ata/ahci.c
+++ 2.6-git/drivers/ata/ahci.c
@@ -175,6 +175,7 @@ enum {
AHCI_FLAG_32BIT_ONLY = (1 << 28), /* force 32bit */
AHCI_FLAG_MV_PATA = (1 << 29), /* PATA port */
AHCI_FLAG_NO_MSI = (1 << 30), /* no PCI MSI */
+ AHCI_FLAG_NO_HOTPLUG = (1 << 31), /* ignore PxSERR.DIAG.N */
AHCI_FLAG_COMMON = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY |
ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA |
@@ -215,6 +216,7 @@ struct ahci_port_priv {
unsigned int ncq_saw_d2h:1;
unsigned int ncq_saw_dmas:1;
unsigned int ncq_saw_sdb:1;
+ u32 intr_mask; /* interrupts to enable */
};
static int ahci_scr_read(struct ata_port *ap, unsigned int sc_reg, u32 *val);
@@ -1518,6 +1520,7 @@ static void ahci_thaw(struct ata_port *a
void __iomem *mmio = ap->host->iomap[AHCI_PCI_BAR];
void __iomem *port_mmio = ahci_port_base(ap);
u32 tmp;
+ struct ahci_port_priv *pp = ap->private_data;
/* clear IRQ */
tmp = readl(port_mmio + PORT_IRQ_STAT);
@@ -1525,7 +1528,7 @@ static void ahci_thaw(struct ata_port *a
writel(1 << ap->port_no, mmio + HOST_IRQ_STAT);
/* turn IRQ back on */
- writel(DEF_PORT_IRQ, port_mmio + PORT_IRQ_MASK);
+ writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK);
}
static void ahci_error_handler(struct ata_port *ap)
@@ -1679,6 +1682,12 @@ static int ahci_port_start(struct ata_po
pp->cmd_tbl = mem;
pp->cmd_tbl_dma = mem_dma;
+ /*
+ * Save off initial list of interrupts to be enabled.
+ * This could be changed later
+ */
+ pp->intr_mask = DEF_PORT_IRQ;
+
ap->private_data = pp;
/* engage engines, captain */
--
^ permalink raw reply [flat|nested] 11+ messages in thread* [patch 3/4] Enable link power management for ata drivers
[not found] <20070809211800.022150464@intel.com>
2007-08-09 21:23 ` [patch 1/4] Store interrupt value Kristen Carlson Accardi
2007-08-09 21:23 ` [patch 2/4] increase number of allowable device flags Kristen Carlson Accardi
@ 2007-08-09 21:24 ` Kristen Carlson Accardi
2007-08-11 4:58 ` Valdis.Kletnieks
2007-08-09 21:24 ` [patch 4/4] Enable Aggressive Link Power management for AHCI controllers Kristen Carlson Accardi
3 siblings, 1 reply; 11+ messages in thread
From: Kristen Carlson Accardi @ 2007-08-09 21:24 UTC (permalink / raw)
To: jeff; +Cc: htejun, akpm, arjan, linux-kernel, linux-ide,
Kristen Carlson Accardi
libata drivers can define a function (enable_pm) that will
perform hardware specific actions to enable whatever power
management policy the user set up from the scsi sysfs
interface if the driver supports it. This power management
policy will be activated after all disks have been
enumerated and intialized. Drivers should also define
disable_pm, which will turn off link power management, but
not change link power management policy.
Signed-off-by: Kristen Carlson Accardi <kristen.c.accardi@intel.com>
Index: 2.6-git/drivers/ata/libata-scsi.c
===================================================================
--- 2.6-git.orig/drivers/ata/libata-scsi.c
+++ 2.6-git/drivers/ata/libata-scsi.c
@@ -110,6 +110,78 @@ static struct scsi_transport_template at
.user_scan = ata_scsi_user_scan,
};
+static const struct {
+ enum link_pm value;
+ char *name;
+} link_pm_policy[] = {
+ { NOT_AVAILABLE, "max_performance" },
+ { MIN_POWER, "min_power" },
+ { MAX_PERFORMANCE, "max_performance" },
+ { MEDIUM_POWER, "medium_power" },
+};
+
+const char *ata_scsi_link_pm_policy(enum link_pm policy)
+{
+ int i;
+ char *name = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(link_pm_policy); i++) {
+ if (link_pm_policy[i].value == policy) {
+ name = link_pm_policy[i].name;
+ break;
+ }
+ }
+ return name;
+}
+
+static ssize_t store_link_pm_policy(struct class_device *class_dev,
+ const char *buf, size_t count)
+{
+ struct Scsi_Host *shost = class_to_shost(class_dev);
+ struct ata_port *ap = ata_shost_to_port(shost);
+ enum link_pm policy = 0;
+ int i;
+
+ /*
+ * we are skipping array location 0 on purpose - this
+ * is because a value of NOT_AVAILABLE is displayed
+ * to the user as max_performance, but when the user
+ * writes "max_performance", they actually want the
+ * value to match MAX_PERFORMANCE.
+ */
+ for (i = 1; i < ARRAY_SIZE(link_pm_policy); i++) {
+ const int len = strlen(link_pm_policy[i].name);
+ if (strncmp(link_pm_policy[i].name, buf, len) == 0 &&
+ buf[len] == '\n') {
+ policy = link_pm_policy[i].value;
+ break;
+ }
+ }
+ if (!policy)
+ return -EINVAL;
+
+ if (ata_scsi_set_link_pm_policy(ap, policy))
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t
+show_link_pm_policy(struct class_device *class_dev, char *buf)
+{
+ struct Scsi_Host *shost = class_to_shost(class_dev);
+ struct ata_port *ap = ata_shost_to_port(shost);
+ const char *policy =
+ ata_scsi_link_pm_policy(ap->pm_policy);
+
+ if (!policy)
+ return -EINVAL;
+
+ return snprintf(buf, 23, "%s\n", policy);
+}
+CLASS_DEVICE_ATTR(link_power_management_policy, S_IRUGO | S_IWUSR,
+ show_link_pm_policy, store_link_pm_policy);
+EXPORT_SYMBOL_GPL(class_device_attr_link_power_management_policy);
+
static void ata_scsi_invalid_field(struct scsi_cmnd *cmd,
void (*done)(struct scsi_cmnd *))
{
@@ -2904,6 +2976,52 @@ void ata_scsi_simulate(struct ata_device
}
}
+int ata_scsi_set_link_pm_policy(struct ata_port *ap,
+ enum link_pm policy)
+{
+ int rc = -EINVAL;
+ int i;
+
+ /*
+ * make sure no broken devices are on this port,
+ * and that all devices support interface power
+ * management
+ */
+ for (i = 0; i < ATA_MAX_DEVICES; i++) {
+ struct ata_device *dev = &ap->device[i];
+
+ /* only check drives which exist */
+ if (!ata_dev_enabled(dev))
+ continue;
+
+ /*
+ * do we need to handle the case where we've hotplugged
+ * a broken drive (since hotplug and ALPM are mutually
+ * exclusive) ?
+ *
+ * If so, if we detect a broken drive on a port with
+ * alpm already enabled, then we should reset the policy
+ * to off for the entire port.
+ */
+ if ((dev->horkage & ATA_HORKAGE_IPM) ||
+ !(dev->flags & ATA_DFLAG_IPM)) {
+ ata_dev_printk(dev, KERN_ERR,
+ "Unable to set Link PM policy\n");
+ ap->pm_policy = MAX_PERFORMANCE;
+ }
+ }
+
+ if (ap->ops->enable_pm) {
+ ap->pm_policy = policy;
+ ap->eh_info.action |= ATA_EHI_LPM;
+ ap->eh_info.flags |= ATA_EHI_NO_AUTOPSY;
+ ata_port_schedule_eh(ap);
+ rc = 0;
+ }
+ return rc;
+}
+EXPORT_SYMBOL_GPL(ata_scsi_set_link_pm_policy);
+
int ata_scsi_add_hosts(struct ata_host *host, struct scsi_host_template *sht)
{
int i, rc;
Index: 2.6-git/include/linux/libata.h
===================================================================
--- 2.6-git.orig/include/linux/libata.h
+++ 2.6-git/include/linux/libata.h
@@ -139,6 +139,7 @@ enum {
ATA_DFLAG_FLUSH_EXT = (1 << 4), /* do FLUSH_EXT instead of FLUSH */
ATA_DFLAG_ACPI_PENDING = (1 << 5), /* ACPI resume action pending */
ATA_DFLAG_ACPI_FAILED = (1 << 6), /* ACPI on devcfg has failed */
+ ATA_DFLAG_IPM = (1 << 8), /* device supports IPM */
ATA_DFLAG_CFG_MASK = (1 << 12) - 1,
ATA_DFLAG_PIO = (1 << 8), /* device limited to PIO mode */
@@ -279,6 +280,7 @@ enum {
ATA_EHI_RESUME_LINK = (1 << 1), /* resume link (reset modifier) */
ATA_EHI_NO_AUTOPSY = (1 << 2), /* no autopsy */
ATA_EHI_QUIET = (1 << 3), /* be quiet */
+ ATA_EHI_LPM = (1 << 4), /* link power management action */
ATA_EHI_DID_SOFTRESET = (1 << 16), /* already soft-reset this port */
ATA_EHI_DID_HARDRESET = (1 << 17), /* already soft-reset this port */
@@ -303,6 +305,7 @@ enum {
ATA_HORKAGE_NODMA = (1 << 1), /* DMA problems */
ATA_HORKAGE_NONCQ = (1 << 2), /* Don't use NCQ */
ATA_HORKAGE_MAX_SEC_128 = (1 << 3), /* Limit max sects to 128 */
+ ATA_HORKAGE_IPM = (1 << 4), /* LPM problems */
};
enum hsm_task_states {
@@ -341,6 +344,18 @@ typedef int (*ata_reset_fn_t)(struct ata
unsigned long deadline);
typedef void (*ata_postreset_fn_t)(struct ata_port *ap, unsigned int *classes);
+/*
+ * host pm policy: If you alter this, you also need to alter scsi_sysfs.c
+ * (for the ascii descriptions)
+ */
+enum link_pm {
+ NOT_AVAILABLE,
+ MIN_POWER,
+ MAX_PERFORMANCE,
+ MEDIUM_POWER,
+};
+extern struct class_device_attribute class_device_attr_link_power_management_policy;
+
struct ata_ioports {
void __iomem *cmd_addr;
void __iomem *data_addr;
@@ -567,6 +582,7 @@ struct ata_port {
pm_message_t pm_mesg;
int *pm_result;
+ enum link_pm pm_policy;
struct timer_list fastdrain_timer;
unsigned long fastdrain_cnt;
@@ -632,7 +648,8 @@ struct ata_port_operations {
int (*port_suspend) (struct ata_port *ap, pm_message_t mesg);
int (*port_resume) (struct ata_port *ap);
-
+ int (*enable_pm) (struct ata_port *ap, enum link_pm policy);
+ int (*disable_pm) (struct ata_port *ap);
int (*port_start) (struct ata_port *ap);
void (*port_stop) (struct ata_port *ap);
@@ -839,7 +856,7 @@ extern int ata_cable_40wire(struct ata_p
extern int ata_cable_80wire(struct ata_port *ap);
extern int ata_cable_sata(struct ata_port *ap);
extern int ata_cable_unknown(struct ata_port *ap);
-
+extern int ata_scsi_set_link_pm_policy(struct ata_port *ap, enum link_pm);
/*
* Timing helpers
*/
Index: 2.6-git/drivers/ata/libata-core.c
===================================================================
--- 2.6-git.orig/drivers/ata/libata-core.c
+++ 2.6-git/drivers/ata/libata-core.c
@@ -1993,6 +1993,9 @@ int ata_dev_configure(struct ata_device
if (dev->flags & ATA_DFLAG_LBA48)
dev->max_sectors = ATA_MAX_SECTORS_LBA48;
+ if (ata_id_has_hipm(dev->id) || ata_id_has_dipm(dev->id))
+ dev->flags |= ATA_DFLAG_IPM;
+
if (dev->horkage & ATA_HORKAGE_DIAGNOSTIC) {
/* Let the user know. We don't want to disallow opens for
rescue purposes, or in case the vendor is just a blithering
@@ -2018,6 +2021,13 @@ int ata_dev_configure(struct ata_device
dev->max_sectors = min_t(unsigned int, ATA_MAX_SECTORS_128,
dev->max_sectors);
+ if (ata_dev_blacklisted(dev) & ATA_HORKAGE_IPM) {
+ dev->horkage |= ATA_HORKAGE_IPM;
+
+ /* reset link pm_policy for this port to no pm */
+ ap->pm_policy = MAX_PERFORMANCE;
+ }
+
if (ap->ops->dev_config)
ap->ops->dev_config(dev);
@@ -5873,6 +5883,27 @@ int ata_flush_cache(struct ata_device *d
return 0;
}
+static void ata_host_disable_link_pm(struct ata_host *host)
+{
+ int i;
+
+ for (i = 0; i < host->n_ports; i++) {
+ struct ata_port *ap = host->ports[i];
+ if (ap->ops->disable_pm)
+ ap->ops->disable_pm(ap);
+ }
+}
+
+static void ata_host_enable_link_pm(struct ata_host *host)
+{
+ int i;
+
+ for (i = 0; i < host->n_ports; i++) {
+ struct ata_port *ap = host->ports[i];
+ ata_scsi_set_link_pm_policy(ap, ap->pm_policy);
+ }
+}
+
#ifdef CONFIG_PM
static int ata_host_request_pm(struct ata_host *host, pm_message_t mesg,
unsigned int action, unsigned int ehi_flags,
@@ -5940,6 +5971,12 @@ int ata_host_suspend(struct ata_host *ho
{
int rc;
+ /*
+ * disable link pm on all ports before requesting
+ * any pm activity
+ */
+ ata_host_disable_link_pm(host);
+
rc = ata_host_request_pm(host, mesg, 0, ATA_EHI_QUIET, 1);
if (rc == 0)
host->dev->power.power_state = mesg;
@@ -5962,6 +5999,9 @@ void ata_host_resume(struct ata_host *ho
ata_host_request_pm(host, PMSG_ON, ATA_EH_SOFTRESET,
ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET, 0);
host->dev->power.power_state = PMSG_ON;
+
+ /* reenable link pm */
+ ata_host_enable_link_pm(host);
}
#endif
@@ -6458,6 +6498,7 @@ int ata_host_register(struct ata_host *h
struct ata_port *ap = host->ports[i];
ata_scsi_scan_host(ap, 1);
+ ata_scsi_set_link_pm_policy(ap, ap->pm_policy);
}
return 0;
Index: 2.6-git/include/linux/ata.h
===================================================================
--- 2.6-git.orig/include/linux/ata.h
+++ 2.6-git/include/linux/ata.h
@@ -355,6 +355,12 @@ struct ata_taskfile {
((u64) (id)[(n) + 0]) )
#define ata_id_cdb_intr(id) (((id)[0] & 0x60) == 0x20)
+#define ata_id_has_hipm(id) \
+ ( (((id)[76] != 0x0000) && ((id)[76] != 0xffff)) && \
+ ((id)[76] & (1 << 9)) )
+#define ata_id_has_dipm(id) \
+ ( (((id)[76] != 0x0000) && ((id)[76] != 0xffff)) && \
+ ((id)[78] & (1 << 3)) )
static inline unsigned int ata_id_major_version(const u16 *id)
{
Index: 2.6-git/Documentation/scsi/link_power_management_policy.txt
===================================================================
--- /dev/null
+++ 2.6-git/Documentation/scsi/link_power_management_policy.txt
@@ -0,0 +1,19 @@
+This parameter allows the user to set the link (interface) power management.
+There are 3 possible options:
+
+Value Effect
+----------------------------------------------------------------------------
+min_power Tell the controller to try to make the link use the
+ least possible power when possible. This may
+ sacrifice some performance due to increased latency
+ when coming out of lower power states.
+
+max_performance Generally, this means no power management. Tell
+ the controller to have performance be a priority
+ over power management.
+
+medium_power Tell the controller to enter a lower power state
+ when possible, but do not enter the lowest power
+ state, thus improving latency over min_power setting.
+
+
Index: 2.6-git/drivers/ata/libata-eh.c
===================================================================
--- 2.6-git.orig/drivers/ata/libata-eh.c
+++ 2.6-git/drivers/ata/libata-eh.c
@@ -2221,6 +2221,9 @@ static int ata_eh_recover(struct ata_por
ehc->i.flags &= ~ATA_EHI_SETMODE;
}
+ if (ehc->i.action & ATA_EHI_LPM)
+ ap->ops->enable_pm(ap, ap->pm_policy);
+
goto out;
dev_fail:
--
^ permalink raw reply [flat|nested] 11+ messages in thread* [patch 4/4] Enable Aggressive Link Power management for AHCI controllers.
[not found] <20070809211800.022150464@intel.com>
` (2 preceding siblings ...)
2007-08-09 21:24 ` [patch 3/4] Enable link power management for ata drivers Kristen Carlson Accardi
@ 2007-08-09 21:24 ` Kristen Carlson Accardi
3 siblings, 0 replies; 11+ messages in thread
From: Kristen Carlson Accardi @ 2007-08-09 21:24 UTC (permalink / raw)
To: jeff; +Cc: htejun, akpm, arjan, linux-kernel, linux-ide,
Kristen Carlson Accardi
This patch will set the correct bits to turn on Aggressive
Link Power Management (ALPM) for the ahci driver. This
will cause the controller and disk to negotiate a lower
power state for the link when there is no activity (see
the AHCI 1.x spec for details). This feature is mutually
exclusive with Hot Plug, so when ALPM is enabled, Hot Plug
is disabled. ALPM will be enabled by default, but it is
settable via the scsi host syfs interface. Possible
settings for this feature are:
Setting Effect
----------------------------------------------------------
min_power ALPM is enabled, and link set to enter
lowest power state (SLUMBER) when idle
Hot plug not allowed.
max_performance ALPM is disabled, Hot Plug is allowed
medium_power ALPM is enabled, and link set to enter
second lowest power state (PARTIAL) when
idle. Hot plug not allowed.
Signed-off-by: Kristen Carlson Accardi <kristen.c.accardi@intel.com>
Index: 2.6-git/drivers/ata/ahci.c
===================================================================
--- 2.6-git.orig/drivers/ata/ahci.c
+++ 2.6-git/drivers/ata/ahci.c
@@ -48,6 +48,9 @@
#define DRV_NAME "ahci"
#define DRV_VERSION "2.3"
+static int ahci_enable_alpm(struct ata_port *ap,
+ enum link_pm policy);
+static int ahci_disable_alpm(struct ata_port *ap);
enum {
AHCI_PCI_BAR = 5,
@@ -98,6 +101,7 @@ enum {
/* HOST_CAP bits */
HOST_CAP_SSC = (1 << 14), /* Slumber capable */
HOST_CAP_CLO = (1 << 24), /* Command List Override support */
+ HOST_CAP_ALPM = (1 << 26), /* Aggressive Link PM support */
HOST_CAP_SSS = (1 << 27), /* Staggered Spin-up */
HOST_CAP_SNTF = (1 << 29), /* SNotification register */
HOST_CAP_NCQ = (1 << 30), /* Native Command Queueing */
@@ -153,6 +157,8 @@ enum {
PORT_IRQ_PIOS_FIS | PORT_IRQ_D2H_REG_FIS,
/* PORT_CMD bits */
+ PORT_CMD_ASP = (1 << 27), /* Aggressive Slumber/Partial */
+ PORT_CMD_ALPE = (1 << 26), /* Aggressive Link PM enable */
PORT_CMD_ATAPI = (1 << 24), /* Device is ATAPI */
PORT_CMD_LIST_ON = (1 << 15), /* cmd list DMA engine running */
PORT_CMD_FIS_ON = (1 << 14), /* FIS DMA engine running */
@@ -244,6 +250,11 @@ static int ahci_pci_device_suspend(struc
static int ahci_pci_device_resume(struct pci_dev *pdev);
#endif
+static struct class_device_attribute *ahci_shost_attrs[] = {
+ &class_device_attr_link_power_management_policy,
+ NULL
+};
+
static struct scsi_host_template ahci_sht = {
.module = THIS_MODULE,
.name = DRV_NAME,
@@ -261,6 +272,7 @@ static struct scsi_host_template ahci_sh
.slave_configure = ata_scsi_slave_config,
.slave_destroy = ata_scsi_slave_destroy,
.bios_param = ata_std_bios_param,
+ .shost_attrs = ahci_shost_attrs,
};
static const struct ata_port_operations ahci_ops = {
@@ -292,6 +304,8 @@ static const struct ata_port_operations
.port_suspend = ahci_port_suspend,
.port_resume = ahci_port_resume,
#endif
+ .enable_pm = ahci_enable_alpm,
+ .disable_pm = ahci_disable_alpm,
.port_start = ahci_port_start,
.port_stop = ahci_port_stop,
@@ -778,6 +792,156 @@ static void ahci_power_up(struct ata_por
writel(cmd | PORT_CMD_ICC_ACTIVE, port_mmio + PORT_CMD);
}
+static int ahci_disable_alpm(struct ata_port *ap)
+{
+ void __iomem *port_mmio = ahci_port_base(ap);
+ u32 cmd, scontrol;
+ struct ahci_port_priv *pp = ap->private_data;
+
+ /*
+ * disable Interface Power Management State Transitions
+ * This is accomplished by setting bits 8:11 of the
+ * SATA Control register
+ */
+ scontrol = readl(port_mmio + PORT_SCR_CTL);
+ scontrol |= (0x3 << 8);
+ writel(scontrol, port_mmio + PORT_SCR_CTL);
+
+ /* get the existing command bits */
+ cmd = readl(port_mmio + PORT_CMD);
+
+ /* disable ALPM and ASP */
+ cmd &= ~PORT_CMD_ASP;
+ cmd &= ~PORT_CMD_ALPE;
+
+ /* force the interface back to active */
+ cmd |= PORT_CMD_ICC_ACTIVE;
+
+ /* write out new cmd value */
+ writel(cmd, port_mmio + PORT_CMD);
+ cmd = readl(port_mmio + PORT_CMD);
+
+ /* wait 10ms to be sure we've come out of any low power state */
+ msleep(10);
+
+ /* clear out any PhyRdy stuff from interrupt status */
+ writel(PORT_IRQ_PHYRDY, port_mmio + PORT_IRQ_STAT);
+
+ /* go ahead and clean out PhyRdy Change from Serror too */
+ ahci_scr_write(ap, SCR_ERROR, ((1 << 16) | (1 << 18)));
+
+ /*
+ * Clear flag to indicate that we should ignore all PhyRdy
+ * state changes
+ */
+ ap->flags &= ~AHCI_FLAG_NO_HOTPLUG;
+
+ /*
+ * Enable interrupts on Phy Ready.
+ */
+ pp->intr_mask |= PORT_IRQ_PHYRDY;
+ writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK);
+
+ /*
+ * don't change the link pm policy - we can be called
+ * just to turn of link pm temporarily
+ */
+ return 0;
+}
+
+static int ahci_enable_alpm(struct ata_port *ap,
+ enum link_pm policy)
+{
+ struct ahci_host_priv *hpriv = ap->host->private_data;
+ void __iomem *port_mmio = ahci_port_base(ap);
+ u32 cmd, scontrol, sstatus;
+ struct ahci_port_priv *pp = ap->private_data;
+ u32 asp;
+
+ /* Make sure the host is capable of link power management */
+ if (!(hpriv->cap & HOST_CAP_ALPM)) {
+ ap->pm_policy = NOT_AVAILABLE;
+ return -EINVAL;
+ }
+
+ /* make sure we have a device attached */
+ sstatus = readl(port_mmio + PORT_SCR_STAT);
+ if (!(sstatus & 0xf00)) {
+ ap->pm_policy = NOT_AVAILABLE;
+ return -EINVAL;
+ }
+
+ switch (policy) {
+ case MAX_PERFORMANCE:
+ case NOT_AVAILABLE:
+ /*
+ * if we came here with NOT_AVAILABLE,
+ * it just means this is the first time we
+ * have tried to enable - default to max performance,
+ * and let the user go to lower power modes on request.
+ */
+ ahci_disable_alpm(ap);
+ ap->pm_policy = MAX_PERFORMANCE;
+ return 0;
+ case MIN_POWER:
+ /* configure HBA to enter SLUMBER */
+ asp = PORT_CMD_ASP;
+ break;
+ case MEDIUM_POWER:
+ /* configure HBA to enter PARTIAL */
+ asp = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ap->pm_policy = policy;
+
+ /*
+ * Disable interrupts on Phy Ready. This keeps us from
+ * getting woken up due to spurious phy ready interrupts
+ * TBD - Hot plug should be done via polling now, is
+ * that even supported?
+ */
+ pp->intr_mask &= ~PORT_IRQ_PHYRDY;
+ writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK);
+
+ /*
+ * Set a flag to indicate that we should ignore all PhyRdy
+ * state changes since these can happen now whenever we
+ * change link state
+ */
+ ap->flags |= AHCI_FLAG_NO_HOTPLUG;
+
+ /* get the existing command bits */
+ cmd = readl(port_mmio + PORT_CMD);
+
+ /*
+ * enable Interface Power Management State Transitions
+ * This is accomplished by clearing bits 8:11 of the
+ * SATA Control register
+ */
+ scontrol = readl(port_mmio + PORT_SCR_CTL);
+ scontrol &= ~(0x3 << 8);
+ writel(scontrol, port_mmio + PORT_SCR_CTL);
+
+ /*
+ * Set ASP based on Policy
+ */
+ cmd |= asp;
+
+ /*
+ * Setting this bit will instruct the HBA to aggressively
+ * enter a lower power link state when it's appropriate and
+ * based on the value set above for ASP
+ */
+ cmd |= PORT_CMD_ALPE;
+
+ /* write out new cmd value */
+ writel(cmd, port_mmio + PORT_CMD);
+ cmd = readl(port_mmio + PORT_CMD);
+ return 0;
+}
+
#ifdef CONFIG_PM
static void ahci_power_down(struct ata_port *ap)
{
@@ -1355,6 +1519,17 @@ static void ahci_port_intr(struct ata_po
status = readl(port_mmio + PORT_IRQ_STAT);
writel(status, port_mmio + PORT_IRQ_STAT);
+ /* If we are getting PhyRdy, this is
+ * just a power state change, we should
+ * clear out this, plus the PhyRdy/Comm
+ * Wake bits from Serror
+ */
+ if ((ap->flags & AHCI_FLAG_NO_HOTPLUG) &&
+ (status & PORT_IRQ_PHYRDY)) {
+ status &= ~PORT_IRQ_PHYRDY;
+ ahci_scr_write(ap, SCR_ERROR, ((1 << 16) | (1 << 18)));
+ }
+
if (unlikely(status & PORT_IRQ_ERROR)) {
ahci_error_intr(ap, status);
return;
@@ -1861,6 +2036,9 @@ static int ahci_init_one(struct pci_dev
struct ata_port *ap = host->ports[i];
void __iomem *port_mmio = ahci_port_base(ap);
+ /* set initial link pm policy */
+ ap->pm_policy = NOT_AVAILABLE;
+
/* standard SATA port setup */
if (hpriv->port_map & (1 << i))
ap->ioaddr.cmd_addr = port_mmio;
--
^ permalink raw reply [flat|nested] 11+ messages in thread