libata/ahci: enclosure management support
[linux-2.6.git] / drivers / ata / ahci.c
index 5e6468a..65d4e96 100644 (file)
@@ -56,6 +56,12 @@ MODULE_PARM_DESC(skip_host_reset, "skip global host reset (0=don't skip, 1=skip)
 static int ahci_enable_alpm(struct ata_port *ap,
                enum link_pm policy);
 static void ahci_disable_alpm(struct ata_port *ap);
+static ssize_t ahci_led_show(struct ata_port *ap, char *buf);
+static ssize_t ahci_led_store(struct ata_port *ap, const char *buf,
+                             size_t size);
+static ssize_t ahci_transmit_led_message(struct ata_port *ap, u32 state,
+                                       ssize_t size);
+#define MAX_SLOTS 8
 
 enum {
        AHCI_PCI_BAR            = 5,
@@ -98,6 +104,8 @@ enum {
        HOST_IRQ_STAT           = 0x08, /* interrupt status */
        HOST_PORTS_IMPL         = 0x0c, /* bitmap of implemented ports */
        HOST_VERSION            = 0x10, /* AHCI spec. version compliancy */
+       HOST_EM_LOC             = 0x1c, /* Enclosure Management location */
+       HOST_EM_CTL             = 0x20, /* Enclosure Management Control */
 
        /* HOST_CTL bits */
        HOST_RESET              = (1 << 0),  /* reset controller; self-clear */
@@ -105,6 +113,7 @@ enum {
        HOST_AHCI_EN            = (1 << 31), /* AHCI enabled */
 
        /* HOST_CAP bits */
+       HOST_CAP_EMS            = (1 << 6),  /* Enclosure Management support */
        HOST_CAP_SSC            = (1 << 14), /* Slumber capable */
        HOST_CAP_PMP            = (1 << 17), /* Port Multiplier support */
        HOST_CAP_CLO            = (1 << 24), /* Command List Override support */
@@ -202,6 +211,11 @@ enum {
                                          ATA_FLAG_IPM,
 
        ICH_MAP                         = 0x90, /* ICH MAP register */
+
+       /* em_ctl bits */
+       EM_CTL_RST                      = (1 << 9), /* Reset */
+       EM_CTL_TM                       = (1 << 8), /* Transmit Message */
+       EM_CTL_ALHD                     = (1 << 26), /* Activity LED */
 };
 
 struct ahci_cmd_hdr {
@@ -219,12 +233,21 @@ struct ahci_sg {
        __le32                  flags_size;
 };
 
+struct ahci_em_priv {
+       enum sw_activity blink_policy;
+       struct timer_list timer;
+       unsigned long saved_activity;
+       unsigned long activity;
+       unsigned long led_state;
+};
+
 struct ahci_host_priv {
        unsigned int            flags;          /* AHCI_HFLAG_* */
        u32                     cap;            /* cap to use */
        u32                     port_map;       /* port map to use */
        u32                     saved_cap;      /* saved initial cap */
        u32                     saved_port_map; /* saved initial port_map */
+       u32                     em_loc; /* enclosure management location */
 };
 
 struct ahci_port_priv {
@@ -240,6 +263,8 @@ struct ahci_port_priv {
        unsigned int            ncq_saw_dmas:1;
        unsigned int            ncq_saw_sdb:1;
        u32                     intr_mask;      /* interrupts to enable */
+       struct ahci_em_priv     em_priv[MAX_SLOTS];/* enclosure management info
+                                                * per PM slot */
 };
 
 static int ahci_scr_read(struct ata_port *ap, unsigned int sc_reg, u32 *val);
@@ -277,9 +302,20 @@ static int ahci_port_suspend(struct ata_port *ap, pm_message_t mesg);
 static int ahci_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg);
 static int ahci_pci_device_resume(struct pci_dev *pdev);
 #endif
+static ssize_t ahci_activity_show(struct ata_device *dev, char *buf);
+static ssize_t ahci_activity_store(struct ata_device *dev,
+                                  enum sw_activity val);
+static void ahci_init_sw_activity(struct ata_link *link);
 
 static struct device_attribute *ahci_shost_attrs[] = {
        &dev_attr_link_power_management_policy,
+       &dev_attr_em_message_type,
+       &dev_attr_em_message,
+       NULL
+};
+
+static struct device_attribute *ahci_sdev_attrs[] = {
+       &dev_attr_sw_activity,
        NULL
 };
 
@@ -289,6 +325,7 @@ static struct scsi_host_template ahci_sht = {
        .sg_tablesize           = AHCI_MAX_SG,
        .dma_boundary           = AHCI_DMA_BOUNDARY,
        .shost_attrs            = ahci_shost_attrs,
+       .sdev_attrs             = ahci_sdev_attrs,
 };
 
 static struct ata_port_operations ahci_ops = {
@@ -316,6 +353,10 @@ static struct ata_port_operations ahci_ops = {
 
        .enable_pm              = ahci_enable_alpm,
        .disable_pm             = ahci_disable_alpm,
+       .em_show                = ahci_led_show,
+       .em_store               = ahci_led_store,
+       .sw_activity_show       = ahci_activity_show,
+       .sw_activity_store      = ahci_activity_store,
 #ifdef CONFIG_PM
        .port_suspend           = ahci_port_suspend,
        .port_resume            = ahci_port_resume,
@@ -561,6 +602,11 @@ static struct pci_driver ahci_pci_driver = {
 #endif
 };
 
+static int ahci_em_messages = 1;
+module_param(ahci_em_messages, int, 0444);
+/* add other LED protocol types when they become supported */
+MODULE_PARM_DESC(ahci_em_messages,
+       "Set AHCI Enclosure Management Message type (0 = disabled, 1 = LED");
 
 static inline int ahci_nr_ports(u32 cap)
 {
@@ -1031,11 +1077,28 @@ static void ahci_power_down(struct ata_port *ap)
 
 static void ahci_start_port(struct ata_port *ap)
 {
+       struct ahci_port_priv *pp = ap->private_data;
+       struct ata_link *link;
+       struct ahci_em_priv *emp;
+
        /* enable FIS reception */
        ahci_start_fis_rx(ap);
 
        /* enable DMA */
        ahci_start_engine(ap);
+
+       /* turn on LEDs */
+       if (ap->flags & ATA_FLAG_EM) {
+               ata_port_for_each_link(link, ap) {
+                       emp = &pp->em_priv[link->pmp];
+                       ahci_transmit_led_message(ap, emp->led_state, 4);
+               }
+       }
+
+       if (ap->flags & ATA_FLAG_SW_ACTIVITY)
+               ata_port_for_each_link(link, ap)
+                       ahci_init_sw_activity(link);
+
 }
 
 static int ahci_deinit_port(struct ata_port *ap, const char **emsg)
@@ -1116,6 +1179,230 @@ static int ahci_reset_controller(struct ata_host *host)
        return 0;
 }
 
+static void ahci_sw_activity(struct ata_link *link)
+{
+       struct ata_port *ap = link->ap;
+       struct ahci_port_priv *pp = ap->private_data;
+       struct ahci_em_priv *emp = &pp->em_priv[link->pmp];
+
+       if (!(link->flags & ATA_LFLAG_SW_ACTIVITY))
+               return;
+
+       emp->activity++;
+       if (!timer_pending(&emp->timer))
+               mod_timer(&emp->timer, jiffies + msecs_to_jiffies(10));
+}
+
+static void ahci_sw_activity_blink(unsigned long arg)
+{
+       struct ata_link *link = (struct ata_link *)arg;
+       struct ata_port *ap = link->ap;
+       struct ahci_port_priv *pp = ap->private_data;
+       struct ahci_em_priv *emp = &pp->em_priv[link->pmp];
+       unsigned long led_message = emp->led_state;
+       u32 activity_led_state;
+
+       led_message &= 0xffff0000;
+       led_message |= ap->port_no | (link->pmp << 8);
+
+       /* check to see if we've had activity.  If so,
+        * toggle state of LED and reset timer.  If not,
+        * turn LED to desired idle state.
+        */
+       if (emp->saved_activity != emp->activity) {
+               emp->saved_activity = emp->activity;
+               /* get the current LED state */
+               activity_led_state = led_message & 0x00010000;
+
+               if (activity_led_state)
+                       activity_led_state = 0;
+               else
+                       activity_led_state = 1;
+
+               /* clear old state */
+               led_message &= 0xfff8ffff;
+
+               /* toggle state */
+               led_message |= (activity_led_state << 16);
+               mod_timer(&emp->timer, jiffies + msecs_to_jiffies(100));
+       } else {
+               /* switch to idle */
+               led_message &= 0xfff8ffff;
+               if (emp->blink_policy == BLINK_OFF)
+                       led_message |= (1 << 16);
+       }
+       ahci_transmit_led_message(ap, led_message, 4);
+}
+
+static void ahci_init_sw_activity(struct ata_link *link)
+{
+       struct ata_port *ap = link->ap;
+       struct ahci_port_priv *pp = ap->private_data;
+       struct ahci_em_priv *emp = &pp->em_priv[link->pmp];
+
+       /* init activity stats, setup timer */
+       emp->saved_activity = emp->activity = 0;
+       setup_timer(&emp->timer, ahci_sw_activity_blink, (unsigned long)link);
+
+       /* check our blink policy and set flag for link if it's enabled */
+       if (emp->blink_policy)
+               link->flags |= ATA_LFLAG_SW_ACTIVITY;
+}
+
+static int ahci_reset_em(struct ata_host *host)
+{
+       void __iomem *mmio = host->iomap[AHCI_PCI_BAR];
+       u32 em_ctl;
+
+       em_ctl = readl(mmio + HOST_EM_CTL);
+       if ((em_ctl & EM_CTL_TM) || (em_ctl & EM_CTL_RST))
+               return -EINVAL;
+
+       writel(em_ctl | EM_CTL_RST, mmio + HOST_EM_CTL);
+       return 0;
+}
+
+static ssize_t ahci_transmit_led_message(struct ata_port *ap, u32 state,
+                                       ssize_t size)
+{
+       struct ahci_host_priv *hpriv = ap->host->private_data;
+       struct ahci_port_priv *pp = ap->private_data;
+       void __iomem *mmio = ap->host->iomap[AHCI_PCI_BAR];
+       u32 em_ctl;
+       u32 message[] = {0, 0};
+       unsigned int flags;
+       int pmp;
+       struct ahci_em_priv *emp;
+
+       /* get the slot number from the message */
+       pmp = (state & 0x0000ff00) >> 8;
+       if (pmp < MAX_SLOTS)
+               emp = &pp->em_priv[pmp];
+       else
+               return -EINVAL;
+
+       spin_lock_irqsave(ap->lock, flags);
+
+       /*
+        * if we are still busy transmitting a previous message,
+        * do not allow
+        */
+       em_ctl = readl(mmio + HOST_EM_CTL);
+       if (em_ctl & EM_CTL_TM) {
+               spin_unlock_irqrestore(ap->lock, flags);
+               return -EINVAL;
+       }
+
+       /*
+        * create message header - this is all zero except for
+        * the message size, which is 4 bytes.
+        */
+       message[0] |= (4 << 8);
+
+       /* ignore 0:4 of byte zero, fill in port info yourself */
+       message[1] = ((state & 0xfffffff0) | ap->port_no);
+
+       /* write message to EM_LOC */
+       writel(message[0], mmio + hpriv->em_loc);
+       writel(message[1], mmio + hpriv->em_loc+4);
+
+       /* save off new led state for port/slot */
+       emp->led_state = message[1];
+
+       /*
+        * tell hardware to transmit the message
+        */
+       writel(em_ctl | EM_CTL_TM, mmio + HOST_EM_CTL);
+
+       spin_unlock_irqrestore(ap->lock, flags);
+       return size;
+}
+
+static ssize_t ahci_led_show(struct ata_port *ap, char *buf)
+{
+       struct ahci_port_priv *pp = ap->private_data;
+       struct ata_link *link;
+       struct ahci_em_priv *emp;
+       int rc = 0;
+
+       ata_port_for_each_link(link, ap) {
+               emp = &pp->em_priv[link->pmp];
+               rc += sprintf(buf, "%lx\n", emp->led_state);
+       }
+       return rc;
+}
+
+static ssize_t ahci_led_store(struct ata_port *ap, const char *buf,
+                               size_t size)
+{
+       int state;
+       int pmp;
+       struct ahci_port_priv *pp = ap->private_data;
+       struct ahci_em_priv *emp;
+
+       state = simple_strtoul(buf, NULL, 0);
+
+       /* get the slot number from the message */
+       pmp = (state & 0x0000ff00) >> 8;
+       if (pmp < MAX_SLOTS)
+               emp = &pp->em_priv[pmp];
+       else
+               return -EINVAL;
+
+       /* mask off the activity bits if we are in sw_activity
+        * mode, user should turn off sw_activity before setting
+        * activity led through em_message
+        */
+       if (emp->blink_policy)
+               state &= 0xfff8ffff;
+
+       return ahci_transmit_led_message(ap, state, size);
+}
+
+static ssize_t ahci_activity_store(struct ata_device *dev, enum sw_activity val)
+{
+       struct ata_link *link = dev->link;
+       struct ata_port *ap = link->ap;
+       struct ahci_port_priv *pp = ap->private_data;
+       struct ahci_em_priv *emp = &pp->em_priv[link->pmp];
+       u32 port_led_state = emp->led_state;
+
+       /* save the desired Activity LED behavior */
+       if (val == OFF) {
+               /* clear LFLAG */
+               link->flags &= ~(ATA_LFLAG_SW_ACTIVITY);
+
+               /* set the LED to OFF */
+               port_led_state &= 0xfff80000;
+               port_led_state |= (ap->port_no | (link->pmp << 8));
+               ahci_transmit_led_message(ap, port_led_state, 4);
+       } else {
+               link->flags |= ATA_LFLAG_SW_ACTIVITY;
+               if (val == BLINK_OFF) {
+                       /* set LED to ON for idle */
+                       port_led_state &= 0xfff80000;
+                       port_led_state |= (ap->port_no | (link->pmp << 8));
+                       port_led_state |= 0x00010000; /* check this */
+                       ahci_transmit_led_message(ap, port_led_state, 4);
+               }
+       }
+       emp->blink_policy = val;
+       return 0;
+}
+
+static ssize_t ahci_activity_show(struct ata_device *dev, char *buf)
+{
+       struct ata_link *link = dev->link;
+       struct ata_port *ap = link->ap;
+       struct ahci_port_priv *pp = ap->private_data;
+       struct ahci_em_priv *emp = &pp->em_priv[link->pmp];
+
+       /* display the saved value of activity behavior for this
+        * disk.
+        */
+       return sprintf(buf, "%d\n", emp->blink_policy);
+}
+
 static void ahci_port_init(struct pci_dev *pdev, struct ata_port *ap,
                           int port_no, void __iomem *mmio,
                           void __iomem *port_mmio)
@@ -1848,6 +2135,8 @@ static unsigned int ahci_qc_issue(struct ata_queued_cmd *qc)
        writel(1 << qc->tag, port_mmio + PORT_CMD_ISSUE);
        readl(port_mmio + PORT_CMD_ISSUE);      /* flush */
 
+       ahci_sw_activity(qc->dev->link);
+
        return 0;
 }
 
@@ -2154,7 +2443,8 @@ static void ahci_print_info(struct ata_host *host)
        dev_printk(KERN_INFO, &pdev->dev,
                "flags: "
                "%s%s%s%s%s%s%s"
-               "%s%s%s%s%s%s%s\n"
+               "%s%s%s%s%s%s%s"
+               "%s\n"
                ,
 
                cap & (1 << 31) ? "64bit " : "",
@@ -2171,7 +2461,8 @@ static void ahci_print_info(struct ata_host *host)
                cap & (1 << 17) ? "pmp " : "",
                cap & (1 << 15) ? "pio " : "",
                cap & (1 << 14) ? "slum " : "",
-               cap & (1 << 13) ? "part " : ""
+               cap & (1 << 13) ? "part " : "",
+               cap & (1 << 6) ? "ems ": ""
                );
 }
 
@@ -2291,6 +2582,24 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
        if (hpriv->cap & HOST_CAP_PMP)
                pi.flags |= ATA_FLAG_PMP;
 
+       if (ahci_em_messages && (hpriv->cap & HOST_CAP_EMS)) {
+               u8 messages;
+               void __iomem *mmio = pcim_iomap_table(pdev)[AHCI_PCI_BAR];
+               u32 em_loc = readl(mmio + HOST_EM_LOC);
+               u32 em_ctl = readl(mmio + HOST_EM_CTL);
+
+               messages = (em_ctl & 0x000f0000) >> 16;
+
+               /* we only support LED message type right now */
+               if ((messages & 0x01) && (ahci_em_messages == 1)) {
+                       /* store em_loc */
+                       hpriv->em_loc = ((em_loc >> 16) * 4);
+                       pi.flags |= ATA_FLAG_EM;
+                       if (!(em_ctl & EM_CTL_ALHD))
+                               pi.flags |= ATA_FLAG_SW_ACTIVITY;
+               }
+       }
+
        /* CAP.NP sometimes indicate the index of the last enabled
         * port, at other times, that of the last possible port, so
         * determining the maximum port number requires looking at
@@ -2304,6 +2613,9 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
        host->iomap = pcim_iomap_table(pdev);
        host->private_data = hpriv;
 
+       if (pi.flags & ATA_FLAG_EM)
+               ahci_reset_em(host);
+
        for (i = 0; i < host->n_ports; i++) {
                struct ata_port *ap = host->ports[i];
 
@@ -2314,6 +2626,11 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
                /* set initial link pm policy */
                ap->pm_policy = NOT_AVAILABLE;
 
+               /* set enclosure management message type */
+               if (ap->flags & ATA_FLAG_EM)
+                       ap->em_message_type = ahci_em_messages;
+
+
                /* disabled/not-implemented port */
                if (!(hpriv->port_map & (1 << i)))
                        ap->ops = &ata_dummy_port_ops;