|
@@ -49,6 +49,9 @@
|
|
|
#define DRV_NAME "ahci"
|
|
|
#define DRV_VERSION "3.0"
|
|
|
|
|
|
+static int ahci_enable_alpm(struct ata_port *ap,
|
|
|
+ enum link_pm policy);
|
|
|
+static void ahci_disable_alpm(struct ata_port *ap);
|
|
|
|
|
|
enum {
|
|
|
AHCI_PCI_BAR = 5,
|
|
@@ -99,6 +102,7 @@ enum {
|
|
|
HOST_CAP_SSC = (1 << 14), /* Slumber capable */
|
|
|
HOST_CAP_PMP = (1 << 17), /* Port Multiplier support */
|
|
|
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 */
|
|
@@ -155,6 +159,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_PMP = (1 << 17), /* PMP attached */
|
|
|
PORT_CMD_LIST_ON = (1 << 15), /* cmd list DMA engine running */
|
|
@@ -178,13 +184,14 @@ enum {
|
|
|
AHCI_HFLAG_MV_PATA = (1 << 4), /* PATA port */
|
|
|
AHCI_HFLAG_NO_MSI = (1 << 5), /* no PCI MSI */
|
|
|
AHCI_HFLAG_NO_PMP = (1 << 6), /* no PMP */
|
|
|
+ AHCI_HFLAG_NO_HOTPLUG = (1 << 7), /* ignore PxSERR.DIAG.N */
|
|
|
|
|
|
/* ap->flags bits */
|
|
|
- AHCI_FLAG_NO_HOTPLUG = (1 << 24), /* ignore PxSERR.DIAG.N */
|
|
|
|
|
|
AHCI_FLAG_COMMON = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY |
|
|
|
ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA |
|
|
|
- ATA_FLAG_ACPI_SATA | ATA_FLAG_AN,
|
|
|
+ ATA_FLAG_ACPI_SATA | ATA_FLAG_AN |
|
|
|
+ ATA_FLAG_IPM,
|
|
|
AHCI_LFLAG_COMMON = ATA_LFLAG_SKIP_D2H_BSY,
|
|
|
};
|
|
|
|
|
@@ -254,6 +261,11 @@ static int ahci_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg);
|
|
|
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,
|
|
@@ -271,6 +283,7 @@ static struct scsi_host_template ahci_sht = {
|
|
|
.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 = {
|
|
@@ -302,6 +315,8 @@ static const struct ata_port_operations ahci_ops = {
|
|
|
.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,
|
|
@@ -836,6 +851,130 @@ static void ahci_power_up(struct ata_port *ap)
|
|
|
writel(cmd | PORT_CMD_ICC_ACTIVE, port_mmio + PORT_CMD);
|
|
|
}
|
|
|
|
|
|
+static void ahci_disable_alpm(struct ata_port *ap)
|
|
|
+{
|
|
|
+ struct ahci_host_priv *hpriv = ap->host->private_data;
|
|
|
+ void __iomem *port_mmio = ahci_port_base(ap);
|
|
|
+ u32 cmd;
|
|
|
+ struct ahci_port_priv *pp = ap->private_data;
|
|
|
+
|
|
|
+ /* IPM bits should be disabled by libata-core */
|
|
|
+ /* 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
|
|
|
+ */
|
|
|
+ hpriv->flags &= ~AHCI_HFLAG_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
|
|
|
+ */
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+ 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))
|
|
|
+ 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);
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 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
|
|
|
+ */
|
|
|
+ hpriv->flags |= AHCI_HFLAG_NO_HOTPLUG;
|
|
|
+
|
|
|
+ /* get the existing command bits */
|
|
|
+ cmd = readl(port_mmio + PORT_CMD);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 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);
|
|
|
+
|
|
|
+ /* IPM bits should be set by libata-core */
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
#ifdef CONFIG_PM
|
|
|
static void ahci_power_down(struct ata_port *ap)
|
|
|
{
|
|
@@ -1504,6 +1643,17 @@ static void ahci_port_intr(struct ata_port *ap)
|
|
|
if (unlikely(resetting))
|
|
|
status &= ~PORT_IRQ_BAD_PMP;
|
|
|
|
|
|
+ /* 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 ((hpriv->flags & AHCI_HFLAG_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;
|
|
@@ -2151,6 +2301,9 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|
|
ata_port_pbar_desc(ap, AHCI_PCI_BAR,
|
|
|
0x100 + ap->port_no * 0x80, "port");
|
|
|
|
|
|
+ /* 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;
|