From patchwork Fri Jan 8 00:03:32 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Fainelli X-Patchwork-Id: 7981051 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id B3E239F38D for ; Fri, 8 Jan 2016 00:06:57 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id B1E8C200E9 for ; Fri, 8 Jan 2016 00:06:56 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 9EFBB2014A for ; Fri, 8 Jan 2016 00:06:55 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1aHKYk-0007fl-BF; Fri, 08 Jan 2016 00:05:38 +0000 Received: from mail-pa0-x231.google.com ([2607:f8b0:400e:c03::231]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1aHKY1-0006BX-4L for linux-arm-kernel@lists.infradead.org; Fri, 08 Jan 2016 00:04:56 +0000 Received: by mail-pa0-x231.google.com with SMTP id yy13so178973966pab.3 for ; Thu, 07 Jan 2016 16:04:32 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=55V0z5wdCbojGTURbcPPUVaKRzyZQTtOylzqBAVWrD4=; b=kCjykyI7rO/pJfGtozYqweh8PtqpNNneHxAtAjfxnlvWE1uIK02x4ZvQXvYTe16rDy RqtJznyUVsrFDpTSY3FC6CDQwdwvIcYp2prtSs9ROhHi0rlW4rPjEUjvtIR4fnOu0ULj Wx+b9XYnLeE8Fopoq44XfBrp5hCq+ujVolqGbZB0EhP/ZgxRF/zskCk9bRz8+IcwS4jc IYs8q6I6/iyhbnF/z6pDaUY8QCxT9NwcdvY8578elD/AS2aAHqzqdNVacsmP8YJgvlDU uGou9rFsl3pGPpFU834h7PvbBgsGpVtQZtdAQcSf7D1Z1f/2bYnyFvW9XZWy5NZezXvf huRA== X-Received: by 10.66.55.39 with SMTP id o7mr152372545pap.13.1452211472479; Thu, 07 Jan 2016 16:04:32 -0800 (PST) Received: from fainelli-desktop.broadcom.com (5520-maca-inet1-outside.broadcom.com. [216.31.211.11]) by smtp.gmail.com with ESMTPSA id wa17sm94069670pac.38.2016.01.07.16.04.31 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Thu, 07 Jan 2016 16:04:31 -0800 (PST) From: Florian Fainelli To: linux-ide@vger.kernel.org Subject: [PATCH 3/4] drivers: ata: wake port before DMA stop for ALPM Date: Thu, 7 Jan 2016 16:03:32 -0800 Message-Id: <1452211413-1350-4-git-send-email-f.fainelli@gmail.com> X-Mailer: git-send-email 2.1.0 In-Reply-To: <1452211413-1350-1-git-send-email-f.fainelli@gmail.com> References: <1452211413-1350-1-git-send-email-f.fainelli@gmail.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20160107_160453_474694_462FEAB9 X-CRM114-Status: GOOD ( 15.48 ) X-Spam-Score: -2.7 (--) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Florian Fainelli , linux-kernel@vger.kernel.org, Danesh Petigara , bcm-kernel-feedback-list@broadcom.com, gregory.0xf0@gmail.com, computersforpeace@gmail.com, linux-arm-kernel@lists.infradead.org MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.1 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Danesh Petigara 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, it's 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 it's 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 it's 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. Signed-off-by: Danesh Petigara Signed-off-by: Florian Fainelli --- drivers/ata/ahci.h | 6 ++++++ drivers/ata/ahci_brcmstb.c | 1 + drivers/ata/libahci.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/ata/libata-core.c | 1 + include/linux/libata.h | 4 ++++ 5 files changed, 64 insertions(+) diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h index a4faa438889c..03453e6b7d9d 100644 --- a/drivers/ata/ahci.h +++ b/drivers/ata/ahci.h @@ -198,6 +198,12 @@ enum { PORT_CMD_ICC_PARTIAL = (0x2 << 28), /* Put i/f in partial state */ PORT_CMD_ICC_SLUMBER = (0x6 << 28), /* Put i/f in slumber state */ + /* PORT SCR STAT bits */ + PORT_SCR_STAT_IPM_MASK = (0xf << 8), /* i/f IPM state mask */ + PORT_SCR_STAT_IPM_ACTIVE = (0x1 << 8), /* i/f in active state */ + PORT_SCR_STAT_IPM_PARTIAL = (0x2 << 8), /* i/f in partial state */ + PORT_SCR_STAT_IPM_SLUMBER = (0x6 << 8), /* i/f in slumber state */ + /* PORT_FBS bits */ PORT_FBS_DWE_OFFSET = 16, /* FBS device with error offset */ PORT_FBS_ADO_OFFSET = 12, /* FBS active dev optimization offset */ diff --git a/drivers/ata/ahci_brcmstb.c b/drivers/ata/ahci_brcmstb.c index f51e3d9ba86e..e518a992e774 100644 --- a/drivers/ata/ahci_brcmstb.c +++ b/drivers/ata/ahci_brcmstb.c @@ -85,6 +85,7 @@ struct brcm_ahci_priv { static const struct ata_port_info ahci_brcm_port_info = { .flags = AHCI_FLAG_COMMON | ATA_FLAG_NO_DIPM, + .flags2 = ATA_FLAG2_WAKE_BEFORE_STOP, .pio_mask = ATA_PIO4, .udma_mask = ATA_UDMA6, .port_ops = &ahci_platform_ops, diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c index d61740e78d6d..b6664c579f9a 100644 --- a/drivers/ata/libahci.c +++ b/drivers/ata/libahci.c @@ -595,6 +595,58 @@ int ahci_stop_engine(struct ata_port *ap) void __iomem *port_mmio = ahci_port_base(ap); 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 it's DMA engine. + */ + if (ap->flags2 & ATA_FLAG2_WAKE_BEFORE_STOP) { + u32 sts, retries = 10; + struct ahci_host_priv *hpriv = ap->host->private_data; + + if (!(hpriv->cap & HOST_CAP_ALPM)) + goto skip_wake_up; + + tmp = readl(port_mmio + PORT_CMD); + if (!(tmp & PORT_CMD_ALPE)) + goto skip_wake_up; + + sts = readl(port_mmio + PORT_SCR_STAT) & + PORT_SCR_STAT_IPM_MASK; + if ((sts != PORT_SCR_STAT_IPM_PARTIAL) && + (sts != PORT_SCR_STAT_IPM_SLUMBER)) + goto skip_wake_up; + + tmp |= PORT_CMD_ICC_ACTIVE; + writel(tmp, port_mmio + PORT_CMD); + + do { + /* + * The exit latency for partial state is upto 10usec + * whereas it is upto 10msec for slumber state. Use + * this information to choose appropriate waiting + * calls so the time spent in the loop is minimized. + */ + if (sts == PORT_SCR_STAT_IPM_PARTIAL) + udelay(1); + else + ata_msleep(ap, 1); + sts = readl(port_mmio + PORT_SCR_STAT) & + PORT_SCR_STAT_IPM_MASK; + if (sts == PORT_SCR_STAT_IPM_ACTIVE) + break; + } while (--retries); + + if (!retries) { + dev_err(ap->host->dev, + "failed to wake up port\n"); + return -EIO; + } + } + +skip_wake_up: tmp = readl(port_mmio + PORT_CMD); /* check if the HBA is idle */ diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 60e368610c74..c6117e1ad373 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -5803,6 +5803,7 @@ struct ata_host *ata_host_alloc_pinfo(struct device *dev, ap->mwdma_mask = pi->mwdma_mask; ap->udma_mask = pi->udma_mask; ap->flags |= pi->flags; + ap->flags2 |= pi->flags2; ap->link.flags |= pi->link_flags; ap->ops = pi->port_ops; diff --git a/include/linux/libata.h b/include/linux/libata.h index 600c1e0626a5..f278ca897274 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -236,6 +236,8 @@ enum { /* bits 24:31 of ap->flags are reserved for LLD specific flags */ + /* struct ata_port flags2 */ + ATA_FLAG2_WAKE_BEFORE_STOP = (1 << 0), /* wake link before DMA stop */ /* struct ata_port pflags */ ATA_PFLAG_EH_PENDING = (1 << 0), /* EH pending */ @@ -812,6 +814,7 @@ struct ata_port { /* Flags owned by the EH context. Only EH should touch these once the port is active */ unsigned long flags; /* ATA_FLAG_xxx */ + unsigned long flags2; /* ATA_FLAG2_xxx */ /* Flags that change dynamically, protected by ap->lock */ unsigned int pflags; /* ATA_PFLAG_xxx */ unsigned int print_id; /* user visible unique port ID */ @@ -996,6 +999,7 @@ struct ata_port_operations { struct ata_port_info { unsigned long flags; + unsigned long flags2; unsigned long link_flags; unsigned long pio_mask; unsigned long mwdma_mask;