aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/ata/libahci.c
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2010-09-01 11:50:06 -0400
committerJeff Garzik <jgarzik@redhat.com>2010-10-21 20:21:04 -0400
commit6b7ae9545ad9875a289f4191c0216b473e313cb9 (patch)
tree216b4db276202d727ba134d256144a6670497180 /drivers/ata/libahci.c
parent1152b2617a6e1943b6b82e07c962950e56f1000c (diff)
libata: reimplement link power management
The current LPM implementation has the following issues. * Operation order isn't well thought-out. e.g. HIPM should be configured after IPM in SControl is properly configured. Not the other way around. * Suspend/resume paths call ata_lpm_enable/disable() which must only be called from EH context directly. Also, ata_lpm_enable/disable() were called whether LPM was in use or not. * Implementation is per-port when it should be per-link. As a result, it can't be used for controllers with slave links or PMP. * LPM state isn't managed consistently. After a link reset for whatever reason including suspend/resume the actual LPM state would be reset leaving ap->lpm_policy inconsistent. * Generic/driver-specific logic boundary isn't clear. Currently, libahci has to mangle stuff which libata EH proper should be handling. This makes the implementation unnecessarily complex and fragile. * Tied to ALPM. Doesn't consider DIPM only cases and doesn't check whether the device allows HIPM. * Error handling isn't implemented. Given the extent of mismatch with the rest of libata, I don't think trying to fix it piecewise makes much sense. This patch reimplements LPM support. * The new implementation is per-link. The target policy is still port-wide (ap->target_lpm_policy) but all the mechanisms and states are per-link and integrate well with the rest of link abstraction and can work with slave and PMP links. * Core EH has proper control of LPM state. LPM state is reconfigured when and only when reconfiguration is necessary. It makes sure that LPM state is reset when probing for new device on the link. Controller agnostic logic is now implemented in libata EH proper and driver implementation only has to deal with controller specifics. * Proper error handling. LPM config failure is attributed to the device on the link and LPM is disabled for the link if it fails repeatedly. * ops->enable/disable_pm() are replaced with single ops->set_lpm() which takes @policy and @hints. This simplifies driver specific implementation. Signed-off-by: Tejun Heo <tj@kernel.org> Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
Diffstat (limited to 'drivers/ata/libahci.c')
-rw-r--r--drivers/ata/libahci.c158
1 files changed, 41 insertions, 117 deletions
diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c
index ed7803f2b4f1..437f92597788 100644
--- a/drivers/ata/libahci.c
+++ b/drivers/ata/libahci.c
@@ -56,8 +56,8 @@ MODULE_PARM_DESC(skip_host_reset, "skip global host reset (0=don't skip, 1=skip)
56module_param_named(ignore_sss, ahci_ignore_sss, int, 0444); 56module_param_named(ignore_sss, ahci_ignore_sss, int, 0444);
57MODULE_PARM_DESC(ignore_sss, "Ignore staggered spinup flag (0=don't ignore, 1=ignore)"); 57MODULE_PARM_DESC(ignore_sss, "Ignore staggered spinup flag (0=don't ignore, 1=ignore)");
58 58
59static int ahci_enable_alpm(struct ata_port *ap, enum ata_lpm_policy policy); 59static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
60static void ahci_disable_alpm(struct ata_port *ap); 60 unsigned hints);
61static ssize_t ahci_led_show(struct ata_port *ap, char *buf); 61static ssize_t ahci_led_show(struct ata_port *ap, char *buf);
62static ssize_t ahci_led_store(struct ata_port *ap, const char *buf, 62static ssize_t ahci_led_store(struct ata_port *ap, const char *buf,
63 size_t size); 63 size_t size);
@@ -163,8 +163,7 @@ struct ata_port_operations ahci_ops = {
163 .pmp_attach = ahci_pmp_attach, 163 .pmp_attach = ahci_pmp_attach,
164 .pmp_detach = ahci_pmp_detach, 164 .pmp_detach = ahci_pmp_detach,
165 165
166 .enable_pm = ahci_enable_alpm, 166 .set_lpm = ahci_set_lpm,
167 .disable_pm = ahci_disable_alpm,
168 .em_show = ahci_led_show, 167 .em_show = ahci_led_show,
169 .em_store = ahci_led_store, 168 .em_store = ahci_led_store,
170 .sw_activity_show = ahci_activity_show, 169 .sw_activity_show = ahci_activity_show,
@@ -641,126 +640,56 @@ static void ahci_power_up(struct ata_port *ap)
641 writel(cmd | PORT_CMD_ICC_ACTIVE, port_mmio + PORT_CMD); 640 writel(cmd | PORT_CMD_ICC_ACTIVE, port_mmio + PORT_CMD);
642} 641}
643 642
644static void ahci_disable_alpm(struct ata_port *ap) 643static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
644 unsigned int hints)
645{ 645{
646 struct ata_port *ap = link->ap;
646 struct ahci_host_priv *hpriv = ap->host->private_data; 647 struct ahci_host_priv *hpriv = ap->host->private_data;
647 void __iomem *port_mmio = ahci_port_base(ap);
648 u32 cmd;
649 struct ahci_port_priv *pp = ap->private_data; 648 struct ahci_port_priv *pp = ap->private_data;
650
651 /* LPM bits should be disabled by libata-core */
652 /* get the existing command bits */
653 cmd = readl(port_mmio + PORT_CMD);
654
655 /* disable ALPM and ASP */
656 cmd &= ~PORT_CMD_ASP;
657 cmd &= ~PORT_CMD_ALPE;
658
659 /* force the interface back to active */
660 cmd |= PORT_CMD_ICC_ACTIVE;
661
662 /* write out new cmd value */
663 writel(cmd, port_mmio + PORT_CMD);
664 cmd = readl(port_mmio + PORT_CMD);
665
666 /* wait 10ms to be sure we've come out of any low power state */
667 msleep(10);
668
669 /* clear out any PhyRdy stuff from interrupt status */
670 writel(PORT_IRQ_PHYRDY, port_mmio + PORT_IRQ_STAT);
671
672 /* go ahead and clean out PhyRdy Change from Serror too */
673 ahci_scr_write(&ap->link, SCR_ERROR, ((1 << 16) | (1 << 18)));
674
675 /*
676 * Clear flag to indicate that we should ignore all PhyRdy
677 * state changes
678 */
679 hpriv->flags &= ~AHCI_HFLAG_NO_HOTPLUG;
680
681 /*
682 * Enable interrupts on Phy Ready.
683 */
684 pp->intr_mask |= PORT_IRQ_PHYRDY;
685 writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK);
686
687 /*
688 * don't change the link pm policy - we can be called
689 * just to turn of link pm temporarily
690 */
691}
692
693static int ahci_enable_alpm(struct ata_port *ap, enum ata_lpm_policy policy)
694{
695 struct ahci_host_priv *hpriv = ap->host->private_data;
696 void __iomem *port_mmio = ahci_port_base(ap); 649 void __iomem *port_mmio = ahci_port_base(ap);
697 u32 cmd;
698 struct ahci_port_priv *pp = ap->private_data;
699 u32 asp;
700
701 /* Make sure the host is capable of link power management */
702 if (!(hpriv->cap & HOST_CAP_ALPM))
703 return -EINVAL;
704 650
705 switch (policy) { 651 if (policy != ATA_LPM_MAX_POWER) {
706 case ATA_LPM_MAX_POWER:
707 case ATA_LPM_UNKNOWN:
708 /* 652 /*
709 * if we came here with ATA_LPM_UNKNOWN, 653 * Disable interrupts on Phy Ready. This keeps us from
710 * it just means this is the first time we 654 * getting woken up due to spurious phy ready
711 * have tried to enable - default to max performance, 655 * interrupts.
712 * and let the user go to lower power modes on request.
713 */ 656 */
714 ahci_disable_alpm(ap); 657 pp->intr_mask &= ~PORT_IRQ_PHYRDY;
715 return 0; 658 writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK);
716 case ATA_LPM_MIN_POWER: 659
717 /* configure HBA to enter SLUMBER */ 660 sata_link_scr_lpm(link, policy, false);
718 asp = PORT_CMD_ASP;
719 break;
720 case ATA_LPM_MED_POWER:
721 /* configure HBA to enter PARTIAL */
722 asp = 0;
723 break;
724 default:
725 return -EINVAL;
726 } 661 }
727 662
728 /* 663 if (hpriv->cap & HOST_CAP_ALPM) {
729 * Disable interrupts on Phy Ready. This keeps us from 664 u32 cmd = readl(port_mmio + PORT_CMD);
730 * getting woken up due to spurious phy ready interrupts
731 * TBD - Hot plug should be done via polling now, is
732 * that even supported?
733 */
734 pp->intr_mask &= ~PORT_IRQ_PHYRDY;
735 writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK);
736 665
737 /* 666 if (policy == ATA_LPM_MAX_POWER || !(hints & ATA_LPM_HIPM)) {
738 * Set a flag to indicate that we should ignore all PhyRdy 667 cmd &= ~(PORT_CMD_ASP | PORT_CMD_ALPE);
739 * state changes since these can happen now whenever we 668 cmd |= PORT_CMD_ICC_ACTIVE;
740 * change link state
741 */
742 hpriv->flags |= AHCI_HFLAG_NO_HOTPLUG;
743 669
744 /* get the existing command bits */ 670 writel(cmd, port_mmio + PORT_CMD);
745 cmd = readl(port_mmio + PORT_CMD); 671 readl(port_mmio + PORT_CMD);
746 672
747 /* 673 /* wait 10ms to be sure we've come out of LPM state */
748 * Set ASP based on Policy 674 msleep(10);
749 */ 675 } else {
750 cmd |= asp; 676 cmd |= PORT_CMD_ALPE;
677 if (policy == ATA_LPM_MIN_POWER)
678 cmd |= PORT_CMD_ASP;
751 679
752 /* 680 /* write out new cmd value */
753 * Setting this bit will instruct the HBA to aggressively 681 writel(cmd, port_mmio + PORT_CMD);
754 * enter a lower power link state when it's appropriate and 682 }
755 * based on the value set above for ASP 683 }
756 */
757 cmd |= PORT_CMD_ALPE;
758 684
759 /* write out new cmd value */ 685 if (policy == ATA_LPM_MAX_POWER) {
760 writel(cmd, port_mmio + PORT_CMD); 686 sata_link_scr_lpm(link, policy, false);
761 cmd = readl(port_mmio + PORT_CMD); 687
688 /* turn PHYRDY IRQ back on */
689 pp->intr_mask |= PORT_IRQ_PHYRDY;
690 writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK);
691 }
762 692
763 /* LPM bits should be set by libata-core */
764 return 0; 693 return 0;
765} 694}
766 695
@@ -1658,15 +1587,10 @@ static void ahci_port_intr(struct ata_port *ap)
1658 if (unlikely(resetting)) 1587 if (unlikely(resetting))
1659 status &= ~PORT_IRQ_BAD_PMP; 1588 status &= ~PORT_IRQ_BAD_PMP;
1660 1589
1661 /* If we are getting PhyRdy, this is 1590 /* if LPM is enabled, PHYRDY doesn't mean anything */
1662 * just a power state change, we should 1591 if (ap->link.lpm_policy > ATA_LPM_MAX_POWER) {
1663 * clear out this, plus the PhyRdy/Comm
1664 * Wake bits from Serror
1665 */
1666 if ((hpriv->flags & AHCI_HFLAG_NO_HOTPLUG) &&
1667 (status & PORT_IRQ_PHYRDY)) {
1668 status &= ~PORT_IRQ_PHYRDY; 1592 status &= ~PORT_IRQ_PHYRDY;
1669 ahci_scr_write(&ap->link, SCR_ERROR, ((1 << 16) | (1 << 18))); 1593 ahci_scr_write(&ap->link, SCR_ERROR, SERR_PHYRDY_CHG);
1670 } 1594 }
1671 1595
1672 if (unlikely(status & PORT_IRQ_ERROR)) { 1596 if (unlikely(status & PORT_IRQ_ERROR)) {