drivers: ata: wake port before DMA stop for ALPM
The AHCI driver code stops and starts port DMA engines at will without considering the power state of the particular port. The AHCI specification isn't very clear on how to handle this scenario, leaving implementation open to interpretation. Broadcom's STB SATA host controller is unable to handle port DMA controller restarts when the port in question is in low power mode. When a port enters partial or slumber mode, its PHY is powered down. When a controller restart is requested, the controller's internal state machine expects the PHY to be brought back up by software which never happens in this case, resulting in failures. To avoid this situation, logic is added to manually wake up the port just before its DMA engine is stopped, if the port happens to be in a low power state. HBA initiated power management ensures that the port eventually returns to its configured low power state, when the link is idle (as per the conditions listed in the spec). A new host flag is also added to ensure this logic is only exercised for hosts with the above limitation. tj: Formatting changes. Signed-off-by: Danesh Petigara <dpetigara@broadcom.com> Reviewed-by: Markus Mayer <mmayer@broadcom.com> Signed-off-by: Tejun Heo <tj@kernel.org>
This commit is contained in:
parent
92e963f50f
commit
fb32963355
|
@ -250,6 +250,7 @@ enum {
|
||||||
AHCI_HFLAG_MULTI_MSI = 0,
|
AHCI_HFLAG_MULTI_MSI = 0,
|
||||||
AHCI_HFLAG_MULTI_MSIX = 0,
|
AHCI_HFLAG_MULTI_MSIX = 0,
|
||||||
#endif
|
#endif
|
||||||
|
AHCI_HFLAG_WAKE_BEFORE_STOP = (1 << 22), /* wake before DMA stop */
|
||||||
|
|
||||||
/* ap->flags bits */
|
/* ap->flags bits */
|
||||||
|
|
||||||
|
|
|
@ -317,6 +317,7 @@ static int brcm_ahci_probe(struct platform_device *pdev)
|
||||||
if (IS_ERR(hpriv))
|
if (IS_ERR(hpriv))
|
||||||
return PTR_ERR(hpriv);
|
return PTR_ERR(hpriv);
|
||||||
hpriv->plat_data = priv;
|
hpriv->plat_data = priv;
|
||||||
|
hpriv->flags = AHCI_HFLAG_WAKE_BEFORE_STOP;
|
||||||
|
|
||||||
brcm_sata_alpm_init(hpriv);
|
brcm_sata_alpm_init(hpriv);
|
||||||
|
|
||||||
|
|
|
@ -593,8 +593,22 @@ EXPORT_SYMBOL_GPL(ahci_start_engine);
|
||||||
int ahci_stop_engine(struct ata_port *ap)
|
int ahci_stop_engine(struct ata_port *ap)
|
||||||
{
|
{
|
||||||
void __iomem *port_mmio = ahci_port_base(ap);
|
void __iomem *port_mmio = ahci_port_base(ap);
|
||||||
|
struct ahci_host_priv *hpriv = ap->host->private_data;
|
||||||
u32 tmp;
|
u32 tmp;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On some controllers, stopping a port's DMA engine while the port
|
||||||
|
* is in ALPM state (partial or slumber) results in failures on
|
||||||
|
* subsequent DMA engine starts. For those controllers, put the
|
||||||
|
* port back in active state before stopping its DMA engine.
|
||||||
|
*/
|
||||||
|
if ((hpriv->flags & AHCI_HFLAG_WAKE_BEFORE_STOP) &&
|
||||||
|
(ap->link.lpm_policy > ATA_LPM_MAX_POWER) &&
|
||||||
|
ahci_set_lpm(&ap->link, ATA_LPM_MAX_POWER, ATA_LPM_WAKE_ONLY)) {
|
||||||
|
dev_err(ap->host->dev, "Failed to wake up port before engine stop\n");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
tmp = readl(port_mmio + PORT_CMD);
|
tmp = readl(port_mmio + PORT_CMD);
|
||||||
|
|
||||||
/* check if the HBA is idle */
|
/* check if the HBA is idle */
|
||||||
|
@ -689,6 +703,9 @@ static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
|
||||||
void __iomem *port_mmio = ahci_port_base(ap);
|
void __iomem *port_mmio = ahci_port_base(ap);
|
||||||
|
|
||||||
if (policy != ATA_LPM_MAX_POWER) {
|
if (policy != ATA_LPM_MAX_POWER) {
|
||||||
|
/* wakeup flag only applies to the max power policy */
|
||||||
|
hints &= ~ATA_LPM_WAKE_ONLY;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Disable interrupts on Phy Ready. This keeps us from
|
* Disable interrupts on Phy Ready. This keeps us from
|
||||||
* getting woken up due to spurious phy ready
|
* getting woken up due to spurious phy ready
|
||||||
|
@ -704,7 +721,8 @@ static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
|
||||||
u32 cmd = readl(port_mmio + PORT_CMD);
|
u32 cmd = readl(port_mmio + PORT_CMD);
|
||||||
|
|
||||||
if (policy == ATA_LPM_MAX_POWER || !(hints & ATA_LPM_HIPM)) {
|
if (policy == ATA_LPM_MAX_POWER || !(hints & ATA_LPM_HIPM)) {
|
||||||
cmd &= ~(PORT_CMD_ASP | PORT_CMD_ALPE);
|
if (!(hints & ATA_LPM_WAKE_ONLY))
|
||||||
|
cmd &= ~(PORT_CMD_ASP | PORT_CMD_ALPE);
|
||||||
cmd |= PORT_CMD_ICC_ACTIVE;
|
cmd |= PORT_CMD_ICC_ACTIVE;
|
||||||
|
|
||||||
writel(cmd, port_mmio + PORT_CMD);
|
writel(cmd, port_mmio + PORT_CMD);
|
||||||
|
@ -712,6 +730,9 @@ static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
|
||||||
|
|
||||||
/* wait 10ms to be sure we've come out of LPM state */
|
/* wait 10ms to be sure we've come out of LPM state */
|
||||||
ata_msleep(ap, 10);
|
ata_msleep(ap, 10);
|
||||||
|
|
||||||
|
if (hints & ATA_LPM_WAKE_ONLY)
|
||||||
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
cmd |= PORT_CMD_ALPE;
|
cmd |= PORT_CMD_ALPE;
|
||||||
if (policy == ATA_LPM_MIN_POWER)
|
if (policy == ATA_LPM_MIN_POWER)
|
||||||
|
|
|
@ -526,6 +526,7 @@ enum ata_lpm_policy {
|
||||||
enum ata_lpm_hints {
|
enum ata_lpm_hints {
|
||||||
ATA_LPM_EMPTY = (1 << 0), /* port empty/probing */
|
ATA_LPM_EMPTY = (1 << 0), /* port empty/probing */
|
||||||
ATA_LPM_HIPM = (1 << 1), /* may use HIPM */
|
ATA_LPM_HIPM = (1 << 1), /* may use HIPM */
|
||||||
|
ATA_LPM_WAKE_ONLY = (1 << 2), /* only wake up link */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* forward declarations */
|
/* forward declarations */
|
||||||
|
|
Loading…
Reference in New Issue