Merge branch 'alpm' of master.kernel.org:/pub/scm/linux/kernel/git/jgarzik/libata-dev
* 'alpm' of master.kernel.org:/pub/scm/linux/kernel/git/jgarzik/libata-dev: [libata] AHCI: add hw link power management support [libata] Link power management infrastructure
This commit is contained in:
commit
3529a23342
8 changed files with 484 additions and 4 deletions
19
Documentation/scsi/link_power_management_policy.txt
Normal file
19
Documentation/scsi/link_power_management_policy.txt
Normal file
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,9 @@
|
||||||
#define DRV_NAME "ahci"
|
#define DRV_NAME "ahci"
|
||||||
#define DRV_VERSION "3.0"
|
#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 {
|
enum {
|
||||||
AHCI_PCI_BAR = 5,
|
AHCI_PCI_BAR = 5,
|
||||||
|
@ -99,6 +102,7 @@ enum {
|
||||||
HOST_CAP_SSC = (1 << 14), /* Slumber capable */
|
HOST_CAP_SSC = (1 << 14), /* Slumber capable */
|
||||||
HOST_CAP_PMP = (1 << 17), /* Port Multiplier support */
|
HOST_CAP_PMP = (1 << 17), /* Port Multiplier support */
|
||||||
HOST_CAP_CLO = (1 << 24), /* Command List Override 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_SSS = (1 << 27), /* Staggered Spin-up */
|
||||||
HOST_CAP_SNTF = (1 << 29), /* SNotification register */
|
HOST_CAP_SNTF = (1 << 29), /* SNotification register */
|
||||||
HOST_CAP_NCQ = (1 << 30), /* Native Command Queueing */
|
HOST_CAP_NCQ = (1 << 30), /* Native Command Queueing */
|
||||||
|
@ -155,6 +159,8 @@ enum {
|
||||||
PORT_IRQ_PIOS_FIS | PORT_IRQ_D2H_REG_FIS,
|
PORT_IRQ_PIOS_FIS | PORT_IRQ_D2H_REG_FIS,
|
||||||
|
|
||||||
/* PORT_CMD bits */
|
/* 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_ATAPI = (1 << 24), /* Device is ATAPI */
|
||||||
PORT_CMD_PMP = (1 << 17), /* PMP attached */
|
PORT_CMD_PMP = (1 << 17), /* PMP attached */
|
||||||
PORT_CMD_LIST_ON = (1 << 15), /* cmd list DMA engine running */
|
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_MV_PATA = (1 << 4), /* PATA port */
|
||||||
AHCI_HFLAG_NO_MSI = (1 << 5), /* no PCI MSI */
|
AHCI_HFLAG_NO_MSI = (1 << 5), /* no PCI MSI */
|
||||||
AHCI_HFLAG_NO_PMP = (1 << 6), /* no PMP */
|
AHCI_HFLAG_NO_PMP = (1 << 6), /* no PMP */
|
||||||
|
AHCI_HFLAG_NO_HOTPLUG = (1 << 7), /* ignore PxSERR.DIAG.N */
|
||||||
|
|
||||||
/* ap->flags bits */
|
/* ap->flags bits */
|
||||||
AHCI_FLAG_NO_HOTPLUG = (1 << 24), /* ignore PxSERR.DIAG.N */
|
|
||||||
|
|
||||||
AHCI_FLAG_COMMON = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY |
|
AHCI_FLAG_COMMON = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY |
|
||||||
ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA |
|
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,
|
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);
|
static int ahci_pci_device_resume(struct pci_dev *pdev);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static struct class_device_attribute *ahci_shost_attrs[] = {
|
||||||
|
&class_device_attr_link_power_management_policy,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
static struct scsi_host_template ahci_sht = {
|
static struct scsi_host_template ahci_sht = {
|
||||||
.module = THIS_MODULE,
|
.module = THIS_MODULE,
|
||||||
.name = DRV_NAME,
|
.name = DRV_NAME,
|
||||||
|
@ -271,6 +283,7 @@ static struct scsi_host_template ahci_sht = {
|
||||||
.slave_configure = ata_scsi_slave_config,
|
.slave_configure = ata_scsi_slave_config,
|
||||||
.slave_destroy = ata_scsi_slave_destroy,
|
.slave_destroy = ata_scsi_slave_destroy,
|
||||||
.bios_param = ata_std_bios_param,
|
.bios_param = ata_std_bios_param,
|
||||||
|
.shost_attrs = ahci_shost_attrs,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct ata_port_operations ahci_ops = {
|
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_suspend = ahci_port_suspend,
|
||||||
.port_resume = ahci_port_resume,
|
.port_resume = ahci_port_resume,
|
||||||
#endif
|
#endif
|
||||||
|
.enable_pm = ahci_enable_alpm,
|
||||||
|
.disable_pm = ahci_disable_alpm,
|
||||||
|
|
||||||
.port_start = ahci_port_start,
|
.port_start = ahci_port_start,
|
||||||
.port_stop = ahci_port_stop,
|
.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);
|
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
|
#ifdef CONFIG_PM
|
||||||
static void ahci_power_down(struct ata_port *ap)
|
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))
|
if (unlikely(resetting))
|
||||||
status &= ~PORT_IRQ_BAD_PMP;
|
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)) {
|
if (unlikely(status & PORT_IRQ_ERROR)) {
|
||||||
ahci_error_intr(ap, status);
|
ahci_error_intr(ap, status);
|
||||||
return;
|
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,
|
ata_port_pbar_desc(ap, AHCI_PCI_BAR,
|
||||||
0x100 + ap->port_no * 0x80, "port");
|
0x100 + ap->port_no * 0x80, "port");
|
||||||
|
|
||||||
|
/* set initial link pm policy */
|
||||||
|
ap->pm_policy = NOT_AVAILABLE;
|
||||||
|
|
||||||
/* standard SATA port setup */
|
/* standard SATA port setup */
|
||||||
if (hpriv->port_map & (1 << i))
|
if (hpriv->port_map & (1 << i))
|
||||||
ap->ioaddr.cmd_addr = port_mmio;
|
ap->ioaddr.cmd_addr = port_mmio;
|
||||||
|
|
|
@ -620,6 +620,177 @@ void ata_dev_disable(struct ata_device *dev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ata_dev_set_dipm(struct ata_device *dev, enum link_pm policy)
|
||||||
|
{
|
||||||
|
struct ata_link *link = dev->link;
|
||||||
|
struct ata_port *ap = link->ap;
|
||||||
|
u32 scontrol;
|
||||||
|
unsigned int err_mask;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* disallow DIPM for drivers which haven't set
|
||||||
|
* ATA_FLAG_IPM. This is because when DIPM is enabled,
|
||||||
|
* phy ready will be set in the interrupt status on
|
||||||
|
* state changes, which will cause some drivers to
|
||||||
|
* think there are errors - additionally drivers will
|
||||||
|
* need to disable hot plug.
|
||||||
|
*/
|
||||||
|
if (!(ap->flags & ATA_FLAG_IPM) || !ata_dev_enabled(dev)) {
|
||||||
|
ap->pm_policy = NOT_AVAILABLE;
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For DIPM, we will only enable it for the
|
||||||
|
* min_power setting.
|
||||||
|
*
|
||||||
|
* Why? Because Disks are too stupid to know that
|
||||||
|
* If the host rejects a request to go to SLUMBER
|
||||||
|
* they should retry at PARTIAL, and instead it
|
||||||
|
* just would give up. So, for medium_power to
|
||||||
|
* work at all, we need to only allow HIPM.
|
||||||
|
*/
|
||||||
|
rc = sata_scr_read(link, SCR_CONTROL, &scontrol);
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
switch (policy) {
|
||||||
|
case MIN_POWER:
|
||||||
|
/* no restrictions on IPM transitions */
|
||||||
|
scontrol &= ~(0x3 << 8);
|
||||||
|
rc = sata_scr_write(link, SCR_CONTROL, scontrol);
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
/* enable DIPM */
|
||||||
|
if (dev->flags & ATA_DFLAG_DIPM)
|
||||||
|
err_mask = ata_dev_set_feature(dev,
|
||||||
|
SETFEATURES_SATA_ENABLE, SATA_DIPM);
|
||||||
|
break;
|
||||||
|
case MEDIUM_POWER:
|
||||||
|
/* allow IPM to PARTIAL */
|
||||||
|
scontrol &= ~(0x1 << 8);
|
||||||
|
scontrol |= (0x2 << 8);
|
||||||
|
rc = sata_scr_write(link, SCR_CONTROL, scontrol);
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
/* disable DIPM */
|
||||||
|
if (ata_dev_enabled(dev) && (dev->flags & ATA_DFLAG_DIPM))
|
||||||
|
err_mask = ata_dev_set_feature(dev,
|
||||||
|
SETFEATURES_SATA_DISABLE, SATA_DIPM);
|
||||||
|
break;
|
||||||
|
case NOT_AVAILABLE:
|
||||||
|
case MAX_PERFORMANCE:
|
||||||
|
/* disable all IPM transitions */
|
||||||
|
scontrol |= (0x3 << 8);
|
||||||
|
rc = sata_scr_write(link, SCR_CONTROL, scontrol);
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
/* disable DIPM */
|
||||||
|
if (ata_dev_enabled(dev) && (dev->flags & ATA_DFLAG_DIPM))
|
||||||
|
err_mask = ata_dev_set_feature(dev,
|
||||||
|
SETFEATURES_SATA_DISABLE, SATA_DIPM);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FIXME: handle SET FEATURES failure */
|
||||||
|
(void) err_mask;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ata_dev_enable_pm - enable SATA interface power management
|
||||||
|
* @device - device to enable ipm for
|
||||||
|
* @policy - the link power management policy
|
||||||
|
*
|
||||||
|
* Enable SATA Interface power management. This will enable
|
||||||
|
* Device Interface Power Management (DIPM) for min_power
|
||||||
|
* policy, and then call driver specific callbacks for
|
||||||
|
* enabling Host Initiated Power management.
|
||||||
|
*
|
||||||
|
* Locking: Caller.
|
||||||
|
* Returns: -EINVAL if IPM is not supported, 0 otherwise.
|
||||||
|
*/
|
||||||
|
void ata_dev_enable_pm(struct ata_device *dev, enum link_pm policy)
|
||||||
|
{
|
||||||
|
int rc = 0;
|
||||||
|
struct ata_port *ap = dev->link->ap;
|
||||||
|
|
||||||
|
/* set HIPM first, then DIPM */
|
||||||
|
if (ap->ops->enable_pm)
|
||||||
|
rc = ap->ops->enable_pm(ap, policy);
|
||||||
|
if (rc)
|
||||||
|
goto enable_pm_out;
|
||||||
|
rc = ata_dev_set_dipm(dev, policy);
|
||||||
|
|
||||||
|
enable_pm_out:
|
||||||
|
if (rc)
|
||||||
|
ap->pm_policy = MAX_PERFORMANCE;
|
||||||
|
else
|
||||||
|
ap->pm_policy = policy;
|
||||||
|
return /* rc */; /* hopefully we can use 'rc' eventually */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ata_dev_disable_pm - disable SATA interface power management
|
||||||
|
* @device - device to enable ipm for
|
||||||
|
*
|
||||||
|
* Disable SATA Interface power management. This will disable
|
||||||
|
* Device Interface Power Management (DIPM) without changing
|
||||||
|
* policy, call driver specific callbacks for disabling Host
|
||||||
|
* Initiated Power management.
|
||||||
|
*
|
||||||
|
* Locking: Caller.
|
||||||
|
* Returns: void
|
||||||
|
*/
|
||||||
|
static void ata_dev_disable_pm(struct ata_device *dev)
|
||||||
|
{
|
||||||
|
struct ata_port *ap = dev->link->ap;
|
||||||
|
|
||||||
|
ata_dev_set_dipm(dev, MAX_PERFORMANCE);
|
||||||
|
if (ap->ops->disable_pm)
|
||||||
|
ap->ops->disable_pm(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ata_lpm_schedule(struct ata_port *ap, enum link_pm policy)
|
||||||
|
{
|
||||||
|
ap->pm_policy = policy;
|
||||||
|
ap->link.eh_info.action |= ATA_EHI_LPM;
|
||||||
|
ap->link.eh_info.flags |= ATA_EHI_NO_AUTOPSY;
|
||||||
|
ata_port_schedule_eh(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ata_lpm_enable(struct ata_host *host)
|
||||||
|
{
|
||||||
|
struct ata_link *link;
|
||||||
|
struct ata_port *ap;
|
||||||
|
struct ata_device *dev;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < host->n_ports; i++) {
|
||||||
|
ap = host->ports[i];
|
||||||
|
ata_port_for_each_link(link, ap) {
|
||||||
|
ata_link_for_each_dev(dev, link)
|
||||||
|
ata_dev_disable_pm(dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ata_lpm_disable(struct ata_host *host)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < host->n_ports; i++) {
|
||||||
|
struct ata_port *ap = host->ports[i];
|
||||||
|
ata_lpm_schedule(ap, ap->pm_policy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ata_devchk - PATA device presence detection
|
* ata_devchk - PATA device presence detection
|
||||||
* @ap: ATA channel to examine
|
* @ap: ATA channel to examine
|
||||||
|
@ -2101,6 +2272,13 @@ int ata_dev_configure(struct ata_device *dev)
|
||||||
if (dev->flags & ATA_DFLAG_LBA48)
|
if (dev->flags & ATA_DFLAG_LBA48)
|
||||||
dev->max_sectors = ATA_MAX_SECTORS_LBA48;
|
dev->max_sectors = ATA_MAX_SECTORS_LBA48;
|
||||||
|
|
||||||
|
if (!(dev->horkage & ATA_HORKAGE_IPM)) {
|
||||||
|
if (ata_id_has_hipm(dev->id))
|
||||||
|
dev->flags |= ATA_DFLAG_HIPM;
|
||||||
|
if (ata_id_has_dipm(dev->id))
|
||||||
|
dev->flags |= ATA_DFLAG_DIPM;
|
||||||
|
}
|
||||||
|
|
||||||
if (dev->horkage & ATA_HORKAGE_DIAGNOSTIC) {
|
if (dev->horkage & ATA_HORKAGE_DIAGNOSTIC) {
|
||||||
/* Let the user know. We don't want to disallow opens for
|
/* Let the user know. We don't want to disallow opens for
|
||||||
rescue purposes, or in case the vendor is just a blithering
|
rescue purposes, or in case the vendor is just a blithering
|
||||||
|
@ -2126,6 +2304,13 @@ int ata_dev_configure(struct ata_device *dev)
|
||||||
dev->max_sectors = min_t(unsigned int, ATA_MAX_SECTORS_128,
|
dev->max_sectors = min_t(unsigned int, ATA_MAX_SECTORS_128,
|
||||||
dev->max_sectors);
|
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)
|
if (ap->ops->dev_config)
|
||||||
ap->ops->dev_config(dev);
|
ap->ops->dev_config(dev);
|
||||||
|
|
||||||
|
@ -6362,6 +6547,12 @@ int ata_host_suspend(struct ata_host *host, pm_message_t mesg)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* disable link pm on all ports before requesting
|
||||||
|
* any pm activity
|
||||||
|
*/
|
||||||
|
ata_lpm_enable(host);
|
||||||
|
|
||||||
rc = ata_host_request_pm(host, mesg, 0, ATA_EHI_QUIET, 1);
|
rc = ata_host_request_pm(host, mesg, 0, ATA_EHI_QUIET, 1);
|
||||||
if (rc == 0)
|
if (rc == 0)
|
||||||
host->dev->power.power_state = mesg;
|
host->dev->power.power_state = mesg;
|
||||||
|
@ -6384,6 +6575,9 @@ void ata_host_resume(struct ata_host *host)
|
||||||
ata_host_request_pm(host, PMSG_ON, ATA_EH_SOFTRESET,
|
ata_host_request_pm(host, PMSG_ON, ATA_EH_SOFTRESET,
|
||||||
ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET, 0);
|
ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET, 0);
|
||||||
host->dev->power.power_state = PMSG_ON;
|
host->dev->power.power_state = PMSG_ON;
|
||||||
|
|
||||||
|
/* reenable link pm */
|
||||||
|
ata_lpm_disable(host);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -6926,6 +7120,7 @@ int ata_host_register(struct ata_host *host, struct scsi_host_template *sht)
|
||||||
struct ata_port *ap = host->ports[i];
|
struct ata_port *ap = host->ports[i];
|
||||||
|
|
||||||
ata_scsi_scan_host(ap, 1);
|
ata_scsi_scan_host(ap, 1);
|
||||||
|
ata_lpm_schedule(ap, ap->pm_policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -7322,7 +7517,6 @@ const struct ata_port_info ata_dummy_port_info = {
|
||||||
* likely to change as new drivers are added and updated.
|
* likely to change as new drivers are added and updated.
|
||||||
* Do not depend on ABI/API stability.
|
* Do not depend on ABI/API stability.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
EXPORT_SYMBOL_GPL(sata_deb_timing_normal);
|
EXPORT_SYMBOL_GPL(sata_deb_timing_normal);
|
||||||
EXPORT_SYMBOL_GPL(sata_deb_timing_hotplug);
|
EXPORT_SYMBOL_GPL(sata_deb_timing_hotplug);
|
||||||
EXPORT_SYMBOL_GPL(sata_deb_timing_long);
|
EXPORT_SYMBOL_GPL(sata_deb_timing_long);
|
||||||
|
|
|
@ -2628,6 +2628,10 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
|
||||||
ehc->i.flags &= ~ATA_EHI_SETMODE;
|
ehc->i.flags &= ~ATA_EHI_SETMODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ehc->i.action & ATA_EHI_LPM)
|
||||||
|
ata_link_for_each_dev(dev, link)
|
||||||
|
ata_dev_enable_pm(dev, ap->pm_policy);
|
||||||
|
|
||||||
/* this link is okay now */
|
/* this link is okay now */
|
||||||
ehc->i.flags = 0;
|
ehc->i.flags = 0;
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -110,6 +110,74 @@ static struct scsi_transport_template ata_scsi_transport_template = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static const struct {
|
||||||
|
enum link_pm value;
|
||||||
|
const 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_lpm_get(enum link_pm policy)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(link_pm_policy); i++)
|
||||||
|
if (link_pm_policy[i].value == policy)
|
||||||
|
return link_pm_policy[i].name;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ata_scsi_lpm_put(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;
|
||||||
|
|
||||||
|
ata_lpm_schedule(ap, policy);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
ata_scsi_lpm_show(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_lpm_get(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,
|
||||||
|
ata_scsi_lpm_show, ata_scsi_lpm_put);
|
||||||
|
EXPORT_SYMBOL_GPL(class_device_attr_link_power_management_policy);
|
||||||
|
|
||||||
static void ata_scsi_invalid_field(struct scsi_cmnd *cmd,
|
static void ata_scsi_invalid_field(struct scsi_cmnd *cmd,
|
||||||
void (*done)(struct scsi_cmnd *))
|
void (*done)(struct scsi_cmnd *))
|
||||||
{
|
{
|
||||||
|
|
|
@ -101,6 +101,8 @@ extern int sata_link_init_spd(struct ata_link *link);
|
||||||
extern int ata_task_ioctl(struct scsi_device *scsidev, void __user *arg);
|
extern int ata_task_ioctl(struct scsi_device *scsidev, void __user *arg);
|
||||||
extern int ata_cmd_ioctl(struct scsi_device *scsidev, void __user *arg);
|
extern int ata_cmd_ioctl(struct scsi_device *scsidev, void __user *arg);
|
||||||
extern struct ata_port *ata_port_alloc(struct ata_host *host);
|
extern struct ata_port *ata_port_alloc(struct ata_host *host);
|
||||||
|
extern void ata_dev_enable_pm(struct ata_device *dev, enum link_pm policy);
|
||||||
|
extern void ata_lpm_schedule(struct ata_port *ap, enum link_pm);
|
||||||
|
|
||||||
/* libata-acpi.c */
|
/* libata-acpi.c */
|
||||||
#ifdef CONFIG_ATA_ACPI
|
#ifdef CONFIG_ATA_ACPI
|
||||||
|
|
|
@ -236,6 +236,7 @@ enum {
|
||||||
|
|
||||||
/* SETFEATURE Sector counts for SATA features */
|
/* SETFEATURE Sector counts for SATA features */
|
||||||
SATA_AN = 0x05, /* Asynchronous Notification */
|
SATA_AN = 0x05, /* Asynchronous Notification */
|
||||||
|
SATA_DIPM = 0x03, /* Device Initiated Power Management */
|
||||||
|
|
||||||
/* ATAPI stuff */
|
/* ATAPI stuff */
|
||||||
ATAPI_PKT_DMA = (1 << 0),
|
ATAPI_PKT_DMA = (1 << 0),
|
||||||
|
@ -378,6 +379,26 @@ struct ata_taskfile {
|
||||||
|
|
||||||
#define ata_id_cdb_intr(id) (((id)[0] & 0x60) == 0x20)
|
#define ata_id_cdb_intr(id) (((id)[0] & 0x60) == 0x20)
|
||||||
|
|
||||||
|
static inline bool ata_id_has_hipm(const u16 *id)
|
||||||
|
{
|
||||||
|
u16 val = id[76];
|
||||||
|
|
||||||
|
if (val == 0 || val == 0xffff)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return val & (1 << 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool ata_id_has_dipm(const u16 *id)
|
||||||
|
{
|
||||||
|
u16 val = id[78];
|
||||||
|
|
||||||
|
if (val == 0 || val == 0xffff)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return val & (1 << 3);
|
||||||
|
}
|
||||||
|
|
||||||
static inline int ata_id_has_fua(const u16 *id)
|
static inline int ata_id_has_fua(const u16 *id)
|
||||||
{
|
{
|
||||||
if ((id[84] & 0xC000) != 0x4000)
|
if ((id[84] & 0xC000) != 0x4000)
|
||||||
|
|
|
@ -133,6 +133,8 @@ enum {
|
||||||
ATA_DFLAG_ACPI_PENDING = (1 << 5), /* ACPI resume action pending */
|
ATA_DFLAG_ACPI_PENDING = (1 << 5), /* ACPI resume action pending */
|
||||||
ATA_DFLAG_ACPI_FAILED = (1 << 6), /* ACPI on devcfg has failed */
|
ATA_DFLAG_ACPI_FAILED = (1 << 6), /* ACPI on devcfg has failed */
|
||||||
ATA_DFLAG_AN = (1 << 7), /* AN configured */
|
ATA_DFLAG_AN = (1 << 7), /* AN configured */
|
||||||
|
ATA_DFLAG_HIPM = (1 << 8), /* device supports HIPM */
|
||||||
|
ATA_DFLAG_DIPM = (1 << 9), /* device supports DIPM */
|
||||||
ATA_DFLAG_CFG_MASK = (1 << 12) - 1,
|
ATA_DFLAG_CFG_MASK = (1 << 12) - 1,
|
||||||
|
|
||||||
ATA_DFLAG_PIO = (1 << 12), /* device limited to PIO mode */
|
ATA_DFLAG_PIO = (1 << 12), /* device limited to PIO mode */
|
||||||
|
@ -186,6 +188,7 @@ enum {
|
||||||
ATA_FLAG_ACPI_SATA = (1 << 17), /* need native SATA ACPI layout */
|
ATA_FLAG_ACPI_SATA = (1 << 17), /* need native SATA ACPI layout */
|
||||||
ATA_FLAG_AN = (1 << 18), /* controller supports AN */
|
ATA_FLAG_AN = (1 << 18), /* controller supports AN */
|
||||||
ATA_FLAG_PMP = (1 << 19), /* controller supports PMP */
|
ATA_FLAG_PMP = (1 << 19), /* controller supports PMP */
|
||||||
|
ATA_FLAG_IPM = (1 << 20), /* driver can handle IPM */
|
||||||
|
|
||||||
/* The following flag belongs to ap->pflags but is kept in
|
/* The following flag belongs to ap->pflags but is kept in
|
||||||
* ap->flags because it's referenced in many LLDs and will be
|
* ap->flags because it's referenced in many LLDs and will be
|
||||||
|
@ -302,6 +305,7 @@ enum {
|
||||||
ATA_EHI_RESUME_LINK = (1 << 1), /* resume link (reset modifier) */
|
ATA_EHI_RESUME_LINK = (1 << 1), /* resume link (reset modifier) */
|
||||||
ATA_EHI_NO_AUTOPSY = (1 << 2), /* no autopsy */
|
ATA_EHI_NO_AUTOPSY = (1 << 2), /* no autopsy */
|
||||||
ATA_EHI_QUIET = (1 << 3), /* be quiet */
|
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_SOFTRESET = (1 << 16), /* already soft-reset this port */
|
||||||
ATA_EHI_DID_HARDRESET = (1 << 17), /* already soft-reset this port */
|
ATA_EHI_DID_HARDRESET = (1 << 17), /* already soft-reset this port */
|
||||||
|
@ -333,6 +337,7 @@ enum {
|
||||||
ATA_HORKAGE_BROKEN_HPA = (1 << 4), /* Broken HPA */
|
ATA_HORKAGE_BROKEN_HPA = (1 << 4), /* Broken HPA */
|
||||||
ATA_HORKAGE_SKIP_PM = (1 << 5), /* Skip PM operations */
|
ATA_HORKAGE_SKIP_PM = (1 << 5), /* Skip PM operations */
|
||||||
ATA_HORKAGE_HPA_SIZE = (1 << 6), /* native size off by one */
|
ATA_HORKAGE_HPA_SIZE = (1 << 6), /* native size off by one */
|
||||||
|
ATA_HORKAGE_IPM = (1 << 7), /* Link PM problems */
|
||||||
|
|
||||||
/* DMA mask for user DMA control: User visible values; DO NOT
|
/* DMA mask for user DMA control: User visible values; DO NOT
|
||||||
renumber */
|
renumber */
|
||||||
|
@ -378,6 +383,18 @@ typedef int (*ata_reset_fn_t)(struct ata_link *link, unsigned int *classes,
|
||||||
unsigned long deadline);
|
unsigned long deadline);
|
||||||
typedef void (*ata_postreset_fn_t)(struct ata_link *link, unsigned int *classes);
|
typedef void (*ata_postreset_fn_t)(struct ata_link *link, unsigned int *classes);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* host pm policy: If you alter this, you also need to alter libata-scsi.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 {
|
struct ata_ioports {
|
||||||
void __iomem *cmd_addr;
|
void __iomem *cmd_addr;
|
||||||
void __iomem *data_addr;
|
void __iomem *data_addr;
|
||||||
|
@ -624,6 +641,7 @@ struct ata_port {
|
||||||
|
|
||||||
pm_message_t pm_mesg;
|
pm_message_t pm_mesg;
|
||||||
int *pm_result;
|
int *pm_result;
|
||||||
|
enum link_pm pm_policy;
|
||||||
|
|
||||||
struct timer_list fastdrain_timer;
|
struct timer_list fastdrain_timer;
|
||||||
unsigned long fastdrain_cnt;
|
unsigned long fastdrain_cnt;
|
||||||
|
@ -691,7 +709,8 @@ struct ata_port_operations {
|
||||||
|
|
||||||
int (*port_suspend) (struct ata_port *ap, pm_message_t mesg);
|
int (*port_suspend) (struct ata_port *ap, pm_message_t mesg);
|
||||||
int (*port_resume) (struct ata_port *ap);
|
int (*port_resume) (struct ata_port *ap);
|
||||||
|
int (*enable_pm) (struct ata_port *ap, enum link_pm policy);
|
||||||
|
void (*disable_pm) (struct ata_port *ap);
|
||||||
int (*port_start) (struct ata_port *ap);
|
int (*port_start) (struct ata_port *ap);
|
||||||
void (*port_stop) (struct ata_port *ap);
|
void (*port_stop) (struct ata_port *ap);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue