ahci: implement aggressive SATA device sleep support
authorShane Huang <shane.huang@amd.com>
Fri, 7 Sep 2012 14:40:01 +0000 (22:40 +0800)
committerJeff Garzik <jgarzik@redhat.com>
Thu, 13 Sep 2012 05:08:53 +0000 (01:08 -0400)
Device Sleep is a feature as described in AHCI 1.3.1 Technical Proposal.
This feature enables an HBA and SATA storage device to enter the DevSleep
interface state, enabling lower power SATA-based systems.

Aggressive Device Sleep enables the HBA to assert the DEVSLP signal as
soon as there are no commands outstanding to the device and the port
specific Device Sleep idle timer has expired. This enables autonomous
entry into the DevSleep interface state without waiting for software
in power sensitive systems.

This patch enables Aggressive Device Sleep only if both host controller
and device support it.

Tested on AMD reference board together with Device Sleep supported device
sample.

Signed-off-by: Shane Huang <shane.huang@amd.com>
Reviewed-by: Aaron Lu <aaron.lwe@gmail.com>
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
drivers/ata/ahci.h
drivers/ata/libahci.c
drivers/ata/libata-core.c
drivers/ata/libata-eh.c
drivers/ata/libata.h
include/linux/ata.h
include/linux/libata.h

index 57eb1c212a4ce8ee267d36b3f920d3011f96b832..6441cbecfa1d79d8083db8d47825c47f31e5de78 100644 (file)
@@ -115,6 +115,9 @@ enum {
        HOST_CAP2_BOH           = (1 << 0),  /* BIOS/OS handoff supported */
        HOST_CAP2_NVMHCI        = (1 << 1),  /* NVMHCI supported */
        HOST_CAP2_APST          = (1 << 2),  /* Automatic partial to slumber */
+       HOST_CAP2_SDS           = (1 << 3),  /* Support device sleep */
+       HOST_CAP2_SADM          = (1 << 4),  /* Support aggressive DevSlp */
+       HOST_CAP2_DESO          = (1 << 5),  /* DevSlp from slumber only */
 
        /* registers for each SATA port */
        PORT_LST_ADDR           = 0x00, /* command list DMA addr */
@@ -133,6 +136,7 @@ enum {
        PORT_SCR_ACT            = 0x34, /* SATA phy register: SActive */
        PORT_SCR_NTF            = 0x3c, /* SATA phy register: SNotification */
        PORT_FBS                = 0x40, /* FIS-based Switching */
+       PORT_DEVSLP             = 0x44, /* device sleep */
 
        /* PORT_IRQ_{STAT,MASK} bits */
        PORT_IRQ_COLD_PRES      = (1 << 31), /* cold presence detect */
@@ -186,6 +190,7 @@ 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_FBS bits */
        PORT_FBS_DWE_OFFSET     = 16, /* FBS device with error offset */
        PORT_FBS_ADO_OFFSET     = 12, /* FBS active dev optimization offset */
        PORT_FBS_DEV_OFFSET     = 8,  /* FBS device to issue offset */
@@ -194,6 +199,15 @@ enum {
        PORT_FBS_DEC            = (1 << 1), /* FBS device error clear */
        PORT_FBS_EN             = (1 << 0), /* Enable FBS */
 
+       /* PORT_DEVSLP bits */
+       PORT_DEVSLP_DM_OFFSET   = 25,             /* DITO multiplier offset */
+       PORT_DEVSLP_DM_MASK     = (0xf << 25),    /* DITO multiplier mask */
+       PORT_DEVSLP_DITO_OFFSET = 15,             /* DITO offset */
+       PORT_DEVSLP_MDAT_OFFSET = 10,             /* Minimum assertion time */
+       PORT_DEVSLP_DETO_OFFSET = 2,              /* DevSlp exit timeout */
+       PORT_DEVSLP_DSP         = (1 << 1),       /* DevSlp present */
+       PORT_DEVSLP_ADSE        = (1 << 0),       /* Aggressive DevSlp enable */
+
        /* hpriv->flags bits */
 
 #define AHCI_HFLAGS(flags)             .private_data   = (void *)(flags)
index 555c07afa05bc6f82d6cc0357684f1f8e8b9f8da..4201e535a8c897ce0acf5912f569f6c74448e7ec 100644 (file)
@@ -45,6 +45,7 @@
 #include <scsi/scsi_cmnd.h>
 #include <linux/libata.h>
 #include "ahci.h"
+#include "libata.h"
 
 static int ahci_skip_host_reset;
 int ahci_ignore_sss;
@@ -76,6 +77,7 @@ static void ahci_qc_prep(struct ata_queued_cmd *qc);
 static int ahci_pmp_qc_defer(struct ata_queued_cmd *qc);
 static void ahci_freeze(struct ata_port *ap);
 static void ahci_thaw(struct ata_port *ap);
+static void ahci_set_aggressive_devslp(struct ata_port *ap, bool sleep);
 static void ahci_enable_fbs(struct ata_port *ap);
 static void ahci_disable_fbs(struct ata_port *ap);
 static void ahci_pmp_attach(struct ata_port *ap);
@@ -193,6 +195,10 @@ module_param(ahci_em_messages, int, 0444);
 MODULE_PARM_DESC(ahci_em_messages,
        "AHCI Enclosure Management Message control (0 = off, 1 = on)");
 
+int devslp_idle_timeout = 1000;        /* device sleep idle timeout in ms */
+module_param(devslp_idle_timeout, int, 0644);
+MODULE_PARM_DESC(devslp_idle_timeout, "device sleep idle timeout");
+
 static void ahci_enable_ahci(void __iomem *mmio)
 {
        int i;
@@ -702,6 +708,16 @@ static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
                }
        }
 
+       /* set aggressive device sleep */
+       if ((hpriv->cap2 & HOST_CAP2_SDS) &&
+           (hpriv->cap2 & HOST_CAP2_SADM) &&
+           (link->device->flags & ATA_DFLAG_DEVSLP)) {
+               if (policy == ATA_LPM_MIN_POWER)
+                       ahci_set_aggressive_devslp(ap, true);
+               else
+                       ahci_set_aggressive_devslp(ap, false);
+       }
+
        if (policy == ATA_LPM_MAX_POWER) {
                sata_link_scr_lpm(link, policy, false);
 
@@ -1890,6 +1906,81 @@ static void ahci_post_internal_cmd(struct ata_queued_cmd *qc)
                ahci_kick_engine(ap);
 }
 
+static void ahci_set_aggressive_devslp(struct ata_port *ap, bool sleep)
+{
+       void __iomem *port_mmio = ahci_port_base(ap);
+       struct ata_device *dev = ap->link.device;
+       u32 devslp, dm, dito, mdat, deto;
+       int rc;
+       unsigned int err_mask;
+
+       devslp = readl(port_mmio + PORT_DEVSLP);
+       if (!(devslp & PORT_DEVSLP_DSP)) {
+               dev_err(ap->host->dev, "port does not support device sleep\n");
+               return;
+       }
+
+       /* disable device sleep */
+       if (!sleep) {
+               if (devslp & PORT_DEVSLP_ADSE) {
+                       writel(devslp & ~PORT_DEVSLP_ADSE,
+                              port_mmio + PORT_DEVSLP);
+                       err_mask = ata_dev_set_feature(dev,
+                                                      SETFEATURES_SATA_DISABLE,
+                                                      SATA_DEVSLP);
+                       if (err_mask && err_mask != AC_ERR_DEV)
+                               ata_dev_warn(dev, "failed to disable DEVSLP\n");
+               }
+               return;
+       }
+
+       /* device sleep was already enabled */
+       if (devslp & PORT_DEVSLP_ADSE)
+               return;
+
+       /* set DITO, MDAT, DETO and enable DevSlp, need to stop engine first */
+       rc = ahci_stop_engine(ap);
+       if (rc)
+               return;
+
+       dm = (devslp & PORT_DEVSLP_DM_MASK) >> PORT_DEVSLP_DM_OFFSET;
+       dito = devslp_idle_timeout / (dm + 1);
+       if (dito > 0x3ff)
+               dito = 0x3ff;
+
+       /* Use the nominal value 10 ms if the read MDAT is zero,
+        * the nominal value of DETO is 20 ms.
+        */
+       if (dev->sata_settings[ATA_LOG_DEVSLP_VALID] &
+           ATA_LOG_DEVSLP_VALID_MASK) {
+               mdat = dev->sata_settings[ATA_LOG_DEVSLP_MDAT] &
+                      ATA_LOG_DEVSLP_MDAT_MASK;
+               if (!mdat)
+                       mdat = 10;
+               deto = dev->sata_settings[ATA_LOG_DEVSLP_DETO];
+               if (!deto)
+                       deto = 20;
+       } else {
+               mdat = 10;
+               deto = 20;
+       }
+
+       devslp |= ((dito << PORT_DEVSLP_DITO_OFFSET) |
+                  (mdat << PORT_DEVSLP_MDAT_OFFSET) |
+                  (deto << PORT_DEVSLP_DETO_OFFSET) |
+                  PORT_DEVSLP_ADSE);
+       writel(devslp, port_mmio + PORT_DEVSLP);
+
+       ahci_start_engine(ap);
+
+       /* enable device sleep feature for the drive */
+       err_mask = ata_dev_set_feature(dev,
+                                      SETFEATURES_SATA_ENABLE,
+                                      SATA_DEVSLP);
+       if (err_mask && err_mask != AC_ERR_DEV)
+               ata_dev_warn(dev, "failed to enable DEVSLP\n");
+}
+
 static void ahci_enable_fbs(struct ata_port *ap)
 {
        struct ahci_port_priv *pp = ap->private_data;
@@ -2164,7 +2255,8 @@ void ahci_print_info(struct ata_host *host, const char *scc_s)
                "flags: "
                "%s%s%s%s%s%s%s"
                "%s%s%s%s%s%s%s"
-               "%s%s%s%s%s%s\n"
+               "%s%s%s%s%s%s%s"
+               "%s%s\n"
                ,
 
                cap & HOST_CAP_64 ? "64bit " : "",
@@ -2184,6 +2276,9 @@ void ahci_print_info(struct ata_host *host, const char *scc_s)
                cap & HOST_CAP_CCC ? "ccc " : "",
                cap & HOST_CAP_EMS ? "ems " : "",
                cap & HOST_CAP_SXS ? "sxs " : "",
+               cap2 & HOST_CAP2_DESO ? "deso " : "",
+               cap2 & HOST_CAP2_SADM ? "sadm " : "",
+               cap2 & HOST_CAP2_SDS ? "sds " : "",
                cap2 & HOST_CAP2_APST ? "apst " : "",
                cap2 & HOST_CAP2_NVMHCI ? "nvmp " : "",
                cap2 & HOST_CAP2_BOH ? "boh " : ""
index 5eee1c1537d22896386df1a10c38aa33b82be658..25daf39368f6ca66108b0eecce62c6c64aea31ad 100644 (file)
@@ -2155,6 +2155,7 @@ int ata_dev_configure(struct ata_device *dev)
        int print_info = ehc->i.flags & ATA_EHI_PRINTINFO;
        const u16 *id = dev->id;
        unsigned long xfer_mask;
+       unsigned int err_mask;
        char revbuf[7];         /* XYZ-99\0 */
        char fwrevbuf[ATA_ID_FW_REV_LEN+1];
        char modelbuf[ATA_ID_PROD_LEN+1];
@@ -2323,6 +2324,26 @@ int ata_dev_configure(struct ata_device *dev)
                        }
                }
 
+               /* check and mark DevSlp capability */
+               if (ata_id_has_devslp(dev->id))
+                       dev->flags |= ATA_DFLAG_DEVSLP;
+
+               /* Obtain SATA Settings page from Identify Device Data Log,
+                * which contains DevSlp timing variables etc.
+                * Exclude old devices with ata_id_has_ncq()
+                */
+               if (ata_id_has_ncq(dev->id)) {
+                       err_mask = ata_read_log_page(dev,
+                                                    ATA_LOG_SATA_ID_DEV_DATA,
+                                                    ATA_LOG_SATA_SETTINGS,
+                                                    dev->sata_settings,
+                                                    1);
+                       if (err_mask)
+                               ata_dev_dbg(dev,
+                                           "failed to get Identify Device Data, Emask 0x%x\n",
+                                           err_mask);
+               }
+
                dev->cdb_len = 16;
        }
 
@@ -2351,8 +2372,6 @@ int ata_dev_configure(struct ata_device *dev)
                    (ap->flags & ATA_FLAG_AN) && ata_id_has_atapi_AN(id) &&
                    (!sata_pmp_attached(ap) ||
                     sata_scr_read(&ap->link, SCR_NOTIFICATION, &sntf) == 0)) {
-                       unsigned int err_mask;
-
                        /* issue SET feature command to turn this on */
                        err_mask = ata_dev_set_feature(dev,
                                        SETFEATURES_SATA_ENABLE, SATA_AN);
@@ -3598,7 +3617,7 @@ int sata_link_scr_lpm(struct ata_link *link, enum ata_lpm_policy policy,
        switch (policy) {
        case ATA_LPM_MAX_POWER:
                /* disable all LPM transitions */
-               scontrol |= (0x3 << 8);
+               scontrol |= (0x7 << 8);
                /* initiate transition to active state */
                if (spm_wakeup) {
                        scontrol |= (0x4 << 12);
@@ -3608,12 +3627,12 @@ int sata_link_scr_lpm(struct ata_link *link, enum ata_lpm_policy policy,
        case ATA_LPM_MED_POWER:
                /* allow LPM to PARTIAL */
                scontrol &= ~(0x1 << 8);
-               scontrol |= (0x2 << 8);
+               scontrol |= (0x6 << 8);
                break;
        case ATA_LPM_MIN_POWER:
                if (ata_link_nr_enabled(link) > 0)
                        /* no restrictions on LPM transitions */
-                       scontrol &= ~(0x3 << 8);
+                       scontrol &= ~(0x7 << 8);
                else {
                        /* empty port, power off */
                        scontrol &= ~0xf;
index 7d4535e989bf5580d76909c2402460898ff86062..26598941e1b3a3e7a0830fc4db6518c32359e6a9 100644 (file)
@@ -1487,6 +1487,7 @@ static const char *ata_err_string(unsigned int err_mask)
 /**
  *     ata_read_log_page - read a specific log page
  *     @dev: target device
+ *     @log: log to read
  *     @page: page to read
  *     @buf: buffer to store read page
  *     @sectors: number of sectors to read
@@ -1499,17 +1500,18 @@ static const char *ata_err_string(unsigned int err_mask)
  *     RETURNS:
  *     0 on success, AC_ERR_* mask otherwise.
  */
-static unsigned int ata_read_log_page(struct ata_device *dev,
-                                     u8 page, void *buf, unsigned int sectors)
+unsigned int ata_read_log_page(struct ata_device *dev, u8 log,
+                              u8 page, void *buf, unsigned int sectors)
 {
        struct ata_taskfile tf;
        unsigned int err_mask;
 
-       DPRINTK("read log page - page %d\n", page);
+       DPRINTK("read log page - log 0x%x, page 0x%x\n", log, page);
 
        ata_tf_init(dev, &tf);
        tf.command = ATA_CMD_READ_LOG_EXT;
-       tf.lbal = page;
+       tf.lbal = log;
+       tf.lbam = page;
        tf.nsect = sectors;
        tf.hob_nsect = sectors >> 8;
        tf.flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_LBA48 | ATA_TFLAG_DEVICE;
@@ -1545,7 +1547,7 @@ static int ata_eh_read_log_10h(struct ata_device *dev,
        u8 csum;
        int i;
 
-       err_mask = ata_read_log_page(dev, ATA_LOG_SATA_NCQ, buf, 1);
+       err_mask = ata_read_log_page(dev, ATA_LOG_SATA_NCQ, 0, buf, 1);
        if (err_mask)
                return -EIO;
 
index 50e4dff0604ed449862f0f2595bf428038d49b1d..7148a58020b91d4cd1adb7a7f74e3224ac12d6e1 100644 (file)
@@ -165,6 +165,8 @@ extern void ata_eh_about_to_do(struct ata_link *link, struct ata_device *dev,
                               unsigned int action);
 extern void ata_eh_done(struct ata_link *link, struct ata_device *dev,
                        unsigned int action);
+extern unsigned int ata_read_log_page(struct ata_device *dev, u8 log,
+                                     u8 page, void *buf, unsigned int sectors);
 extern void ata_eh_autopsy(struct ata_port *ap);
 const char *ata_get_cmd_descript(u8 command);
 extern void ata_eh_report(struct ata_port *ap);
index 47a9dbf0215984fa060b88b7e43b7353defcb112..408da9502177bd18a3ba88e2831701e916e800aa 100644 (file)
@@ -295,6 +295,13 @@ enum {
 
        /* READ_LOG_EXT pages */
        ATA_LOG_SATA_NCQ        = 0x10,
+       ATA_LOG_SATA_ID_DEV_DATA  = 0x30,
+       ATA_LOG_SATA_SETTINGS     = 0x08,
+       ATA_LOG_DEVSLP_MDAT       = 0x30,
+       ATA_LOG_DEVSLP_MDAT_MASK  = 0x1F,
+       ATA_LOG_DEVSLP_DETO       = 0x31,
+       ATA_LOG_DEVSLP_VALID      = 0x37,
+       ATA_LOG_DEVSLP_VALID_MASK = 0x80,
 
        /* READ/WRITE LONG (obsolete) */
        ATA_CMD_READ_LONG       = 0x22,
@@ -348,6 +355,7 @@ enum {
        SATA_FPDMA_IN_ORDER     = 0x04, /* FPDMA in-order data delivery */
        SATA_AN                 = 0x05, /* Asynchronous Notification */
        SATA_SSP                = 0x06, /* Software Settings Preservation */
+       SATA_DEVSLP             = 0x09, /* Device Sleep */
 
        /* feature values for SET_MAX */
        ATA_SET_MAX_ADDR        = 0x00,
@@ -584,6 +592,7 @@ static inline int ata_is_data(u8 prot)
 
 #define ata_id_cdb_intr(id)    (((id)[ATA_ID_CONFIG] & 0x60) == 0x20)
 #define ata_id_has_da(id)      ((id)[ATA_ID_SATA_CAPABILITY_2] & (1 << 4))
+#define ata_id_has_devslp(id)  ((id)[ATA_ID_FEATURE_SUPP] & (1 << 8))
 
 static inline bool ata_id_has_hipm(const u16 *id)
 {
index 64f90e17e51d5a87f3aa997e664c132542ad8029..464e67c2e77a75e0802e0c804b0ce2cce96ec4d5 100644 (file)
@@ -162,6 +162,7 @@ enum {
        ATA_DFLAG_DETACHED      = (1 << 25),
 
        ATA_DFLAG_DA            = (1 << 26), /* device supports Device Attention */
+       ATA_DFLAG_DEVSLP        = (1 << 27), /* device supports Device Sleep */
 
        ATA_DEV_UNKNOWN         = 0,    /* unknown device */
        ATA_DEV_ATA             = 1,    /* ATA device */
@@ -649,6 +650,9 @@ struct ata_device {
                u32             gscr[SATA_PMP_GSCR_DWORDS]; /* PMP GSCR block */
        };
 
+       /* Identify Device Data Log (30h), SATA Settings (page 08h) */
+       u8                      sata_settings[ATA_SECT_SIZE];
+
        /* error history */
        int                     spdn_cnt;
        /* ering is CLEAR_END, read comment above CLEAR_END */