[PATCH 11/12] ahci: implement link powersave

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Implement link powersave.  AHCI can initiate transition to powersave
mode but doesn't use standard SControl SPM field.  It uses ICC field
in PORT_CMD register.  As PHY RDY changed interrupt can trigger during
dynamic link powersave, it needs to be turned off while dynamic
powersave is active.  All other can be taken care of by standard
helpers.

Signed-off-by: Tejun Heo <htejun@xxxxxxxxx>

---

 drivers/scsi/ahci.c |   66 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 66 insertions(+), 0 deletions(-)

14143d016b88395ffcec7be07ffa99d652a3353d
diff --git a/drivers/scsi/ahci.c b/drivers/scsi/ahci.c
index 77e7202..b5fe19a 100644
--- a/drivers/scsi/ahci.c
+++ b/drivers/scsi/ahci.c
@@ -92,6 +92,8 @@ enum {
 	HOST_AHCI_EN		= (1 << 31), /* AHCI enabled */
 
 	/* HOST_CAP bits */
+	HOST_CAP_PARTIAL	= (1 << 13), /* Partial state support */
+	HOST_CAP_SLUMBER	= (1 << 14), /* Slumber state support */
 	HOST_CAP_CLO		= (1 << 24), /* Command List Override support */
 	HOST_CAP_NCQ		= (1 << 30), /* Native Command Queueing */
 	HOST_CAP_64		= (1 << 31), /* PCI DAC (64-bit DMA) support */
@@ -158,6 +160,7 @@ enum {
 	PORT_CMD_ICC_ACTIVE	= (0x1 << 28), /* Put i/f in active state */
 	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_CMD_ICC_MASK	= (0xf << 28),
 
 	/* hpriv->flags bits */
 	AHCI_FLAG_MSI		= (1 << 0),
@@ -212,6 +215,7 @@ static void ahci_freeze(struct ata_port 
 static void ahci_thaw(struct ata_port *ap);
 static void ahci_error_handler(struct ata_port *ap);
 static void ahci_post_internal_cmd(struct ata_queued_cmd *qc);
+static void ahci_set_powersave(struct ata_port *ap, int ps_state);
 static void ahci_remove_one (struct pci_dev *pdev);
 
 static struct scsi_host_template ahci_sht = {
@@ -257,6 +261,8 @@ static const struct ata_port_operations 
 	.error_handler		= ahci_error_handler,
 	.post_internal_cmd	= ahci_post_internal_cmd,
 
+	.set_powersave		= ahci_set_powersave,
+
 	.port_start		= ahci_port_start,
 	.port_stop		= ahci_port_stop,
 };
@@ -913,6 +919,9 @@ static void ahci_host_intr(struct ata_po
 	status = readl(port_mmio + PORT_IRQ_STAT);
 	writel(status, port_mmio + PORT_IRQ_STAT);
 
+	if (ata_ps_dynamic(ap->ps_state))
+		status &= ~PORT_IRQ_PHYRDY;
+
 	if (unlikely(status & PORT_IRQ_ERROR)) {
 		ahci_error_intr(ap, status);
 		return;
@@ -1077,6 +1086,60 @@ static void ahci_post_internal_cmd(struc
 	}
 }
 
+static void ahci_update_sctl_spm(struct ata_port *ap, u8 sctl_spm,
+				 int may_push_sctl)
+{
+	static const u32 icc_map[] = {
+		[0x1]	= PORT_CMD_ICC_PARTIAL,
+		[0x2]	= PORT_CMD_ICC_SLUMBER,
+		[0x4]	= PORT_CMD_ICC_ACTIVE,
+	};
+	void __iomem *mmio = ap->host_set->mmio_base;
+	void __iomem *port_mmio = ahci_port_base(mmio, ap->port_no);
+	u32 tmp;
+
+	tmp = readl(port_mmio + PORT_CMD);
+
+	/* proceed only if idle */
+	if (!(tmp & PORT_CMD_ICC_MASK))
+		writel(tmp | icc_map[sctl_spm], port_mmio + PORT_CMD);
+}
+
+static unsigned long ahci_hips_timer_fn(struct ata_port *ap, int seq)
+{
+	u8 sctl_ipm = ata_scontrol_field(ap->scontrol, ATA_SCTL_IPM);
+
+	return sata_do_hips_timer_fn(ap, seq, sctl_ipm, ahci_update_sctl_spm);
+}
+
+static void ahci_set_powersave(struct ata_port *ap, int ps_state)
+{
+	void __iomem *mmio = ap->host_set->mmio_base;
+	void __iomem *port_mmio = ahci_port_base(mmio, ap->port_no);
+	struct ahci_host_priv *hpriv = ap->host_set->private_data;
+	u8 sctl_ipm;
+
+	/* determine IPM */
+	sctl_ipm = 0x3;
+	if (hpriv->cap & HOST_CAP_PARTIAL)
+		sctl_ipm &= ~0x1;
+	if (hpriv->cap & HOST_CAP_SLUMBER)
+		sctl_ipm &= ~0x2;
+
+	/* turn off SError.N IRQ if entering dynamic powersave mode */
+	if (ata_ps_dynamic(ps_state))
+		writel(DEF_PORT_IRQ & ~PORT_IRQ_PHYRDY,
+		       port_mmio + PORT_IRQ_MASK);
+
+	/* do standard SATA set_powersave */
+	if (ata_ps_dynamic(ps_state) == ATA_PS_HIPS) {
+		ap->ps_timer_fn = ahci_hips_timer_fn;
+		sata_determine_hips_params(ap, &sctl_ipm);
+	}
+
+	sata_do_set_powersave(ap, ps_state, sctl_ipm, ahci_update_sctl_spm);
+}
+
 static void ahci_setup_port(struct ata_ioports *port, unsigned long base,
 			    unsigned int port_idx)
 {
@@ -1398,6 +1461,9 @@ static int ahci_init_one (struct pci_dev
 	    (hpriv->cap & HOST_CAP_NCQ))
 		probe_ent->host_flags |= ATA_FLAG_NCQ;
 
+	if (hpriv->cap & (HOST_CAP_PARTIAL | HOST_CAP_SLUMBER))
+		probe_ent->host_flags |= ATA_FLAG_HIPS | ATA_FLAG_DIPS;
+
 	ahci_print_info(probe_ent);
 
 	/* FIXME: check ata_device_add return value */
-- 
1.3.2


-
: send the line "unsubscribe linux-ide" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Filesystems]     [Linux SCSI]     [Linux RAID]     [Git]     [Kernel Newbies]     [Linux Newbie]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Samba]     [Device Mapper]

  Powered by Linux