diff options
author | Andiry Xu <andiry.xu@amd.com> | 2010-10-14 10:23:03 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2010-10-22 13:22:13 -0400 |
commit | 9777e3ce907d4cb5a513902a87ecd03b52499569 (patch) | |
tree | a2b28eeeaddd39d7c8cdd59f6ddbfa694d40d5ba | |
parent | 561925318725a41189a69f36ebe99199b3fb84c4 (diff) |
USB: xHCI: bus power management implementation
This patch implements xHCI bus suspend/resume function hook.
In the patch it goes through all the ports and suspend/resume
the ports if needed.
If any port is in remote wakeup, abort bus suspend as what ehci/ohci do.
Signed-off-by: Libin Yang <libin.yang@amd.com>
Signed-off-by: Crane Cai <crane.cai@amd.com>
Signed-off-by: Andiry Xu <andiry.xu@amd.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r-- | drivers/usb/host/xhci-hub.c | 188 | ||||
-rw-r--r-- | drivers/usb/host/xhci-mem.c | 1 | ||||
-rw-r--r-- | drivers/usb/host/xhci-pci.c | 2 | ||||
-rw-r--r-- | drivers/usb/host/xhci.h | 9 |
4 files changed, 200 insertions, 0 deletions
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 8163f17e7043..7f2f63cb6c53 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c | |||
@@ -24,6 +24,10 @@ | |||
24 | 24 | ||
25 | #include "xhci.h" | 25 | #include "xhci.h" |
26 | 26 | ||
27 | #define PORT_WAKE_BITS (PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E) | ||
28 | #define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \ | ||
29 | PORT_RC | PORT_PLC | PORT_PE) | ||
30 | |||
27 | static void xhci_hub_descriptor(struct xhci_hcd *xhci, | 31 | static void xhci_hub_descriptor(struct xhci_hcd *xhci, |
28 | struct usb_hub_descriptor *desc) | 32 | struct usb_hub_descriptor *desc) |
29 | { | 33 | { |
@@ -560,3 +564,187 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) | |||
560 | spin_unlock_irqrestore(&xhci->lock, flags); | 564 | spin_unlock_irqrestore(&xhci->lock, flags); |
561 | return status ? retval : 0; | 565 | return status ? retval : 0; |
562 | } | 566 | } |
567 | |||
568 | #ifdef CONFIG_PM | ||
569 | |||
570 | int xhci_bus_suspend(struct usb_hcd *hcd) | ||
571 | { | ||
572 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); | ||
573 | int port; | ||
574 | unsigned long flags; | ||
575 | |||
576 | xhci_dbg(xhci, "suspend root hub\n"); | ||
577 | |||
578 | spin_lock_irqsave(&xhci->lock, flags); | ||
579 | |||
580 | if (hcd->self.root_hub->do_remote_wakeup) { | ||
581 | port = HCS_MAX_PORTS(xhci->hcs_params1); | ||
582 | while (port--) { | ||
583 | if (xhci->resume_done[port] != 0) { | ||
584 | spin_unlock_irqrestore(&xhci->lock, flags); | ||
585 | xhci_dbg(xhci, "suspend failed because " | ||
586 | "port %d is resuming\n", | ||
587 | port + 1); | ||
588 | return -EBUSY; | ||
589 | } | ||
590 | } | ||
591 | } | ||
592 | |||
593 | port = HCS_MAX_PORTS(xhci->hcs_params1); | ||
594 | xhci->bus_suspended = 0; | ||
595 | while (port--) { | ||
596 | /* suspend the port if the port is not suspended */ | ||
597 | u32 __iomem *addr; | ||
598 | u32 t1, t2; | ||
599 | int slot_id; | ||
600 | |||
601 | addr = &xhci->op_regs->port_status_base + | ||
602 | NUM_PORT_REGS * (port & 0xff); | ||
603 | t1 = xhci_readl(xhci, addr); | ||
604 | t2 = xhci_port_state_to_neutral(t1); | ||
605 | |||
606 | if ((t1 & PORT_PE) && !(t1 & PORT_PLS_MASK)) { | ||
607 | xhci_dbg(xhci, "port %d not suspended\n", port); | ||
608 | slot_id = xhci_find_slot_id_by_port(xhci, port + 1); | ||
609 | if (slot_id) { | ||
610 | spin_unlock_irqrestore(&xhci->lock, flags); | ||
611 | xhci_stop_device(xhci, slot_id, 1); | ||
612 | spin_lock_irqsave(&xhci->lock, flags); | ||
613 | } | ||
614 | t2 &= ~PORT_PLS_MASK; | ||
615 | t2 |= PORT_LINK_STROBE | XDEV_U3; | ||
616 | set_bit(port, &xhci->bus_suspended); | ||
617 | } | ||
618 | if (hcd->self.root_hub->do_remote_wakeup) { | ||
619 | if (t1 & PORT_CONNECT) { | ||
620 | t2 |= PORT_WKOC_E | PORT_WKDISC_E; | ||
621 | t2 &= ~PORT_WKCONN_E; | ||
622 | } else { | ||
623 | t2 |= PORT_WKOC_E | PORT_WKCONN_E; | ||
624 | t2 &= ~PORT_WKDISC_E; | ||
625 | } | ||
626 | } else | ||
627 | t2 &= ~PORT_WAKE_BITS; | ||
628 | |||
629 | t1 = xhci_port_state_to_neutral(t1); | ||
630 | if (t1 != t2) | ||
631 | xhci_writel(xhci, t2, addr); | ||
632 | |||
633 | if (DEV_HIGHSPEED(t1)) { | ||
634 | /* enable remote wake up for USB 2.0 */ | ||
635 | u32 __iomem *addr; | ||
636 | u32 tmp; | ||
637 | |||
638 | addr = &xhci->op_regs->port_power_base + | ||
639 | NUM_PORT_REGS * (port & 0xff); | ||
640 | tmp = xhci_readl(xhci, addr); | ||
641 | tmp |= PORT_RWE; | ||
642 | xhci_writel(xhci, tmp, addr); | ||
643 | } | ||
644 | } | ||
645 | hcd->state = HC_STATE_SUSPENDED; | ||
646 | xhci->next_statechange = jiffies + msecs_to_jiffies(10); | ||
647 | spin_unlock_irqrestore(&xhci->lock, flags); | ||
648 | return 0; | ||
649 | } | ||
650 | |||
651 | int xhci_bus_resume(struct usb_hcd *hcd) | ||
652 | { | ||
653 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); | ||
654 | int port; | ||
655 | u32 temp; | ||
656 | unsigned long flags; | ||
657 | |||
658 | xhci_dbg(xhci, "resume root hub\n"); | ||
659 | |||
660 | if (time_before(jiffies, xhci->next_statechange)) | ||
661 | msleep(5); | ||
662 | |||
663 | spin_lock_irqsave(&xhci->lock, flags); | ||
664 | if (!HCD_HW_ACCESSIBLE(hcd)) { | ||
665 | spin_unlock_irqrestore(&xhci->lock, flags); | ||
666 | return -ESHUTDOWN; | ||
667 | } | ||
668 | |||
669 | /* delay the irqs */ | ||
670 | temp = xhci_readl(xhci, &xhci->op_regs->command); | ||
671 | temp &= ~CMD_EIE; | ||
672 | xhci_writel(xhci, temp, &xhci->op_regs->command); | ||
673 | |||
674 | port = HCS_MAX_PORTS(xhci->hcs_params1); | ||
675 | while (port--) { | ||
676 | /* Check whether need resume ports. If needed | ||
677 | resume port and disable remote wakeup */ | ||
678 | u32 __iomem *addr; | ||
679 | u32 temp; | ||
680 | int slot_id; | ||
681 | |||
682 | addr = &xhci->op_regs->port_status_base + | ||
683 | NUM_PORT_REGS * (port & 0xff); | ||
684 | temp = xhci_readl(xhci, addr); | ||
685 | if (DEV_SUPERSPEED(temp)) | ||
686 | temp &= ~(PORT_RWC_BITS | PORT_CEC | PORT_WAKE_BITS); | ||
687 | else | ||
688 | temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); | ||
689 | if (test_bit(port, &xhci->bus_suspended) && | ||
690 | (temp & PORT_PLS_MASK)) { | ||
691 | if (DEV_SUPERSPEED(temp)) { | ||
692 | temp = xhci_port_state_to_neutral(temp); | ||
693 | temp &= ~PORT_PLS_MASK; | ||
694 | temp |= PORT_LINK_STROBE | XDEV_U0; | ||
695 | xhci_writel(xhci, temp, addr); | ||
696 | } else { | ||
697 | temp = xhci_port_state_to_neutral(temp); | ||
698 | temp &= ~PORT_PLS_MASK; | ||
699 | temp |= PORT_LINK_STROBE | XDEV_RESUME; | ||
700 | xhci_writel(xhci, temp, addr); | ||
701 | |||
702 | spin_unlock_irqrestore(&xhci->lock, flags); | ||
703 | msleep(20); | ||
704 | spin_lock_irqsave(&xhci->lock, flags); | ||
705 | |||
706 | temp = xhci_readl(xhci, addr); | ||
707 | temp = xhci_port_state_to_neutral(temp); | ||
708 | temp &= ~PORT_PLS_MASK; | ||
709 | temp |= PORT_LINK_STROBE | XDEV_U0; | ||
710 | xhci_writel(xhci, temp, addr); | ||
711 | } | ||
712 | slot_id = xhci_find_slot_id_by_port(xhci, port + 1); | ||
713 | if (slot_id) | ||
714 | xhci_ring_device(xhci, slot_id); | ||
715 | } else | ||
716 | xhci_writel(xhci, temp, addr); | ||
717 | |||
718 | if (DEV_HIGHSPEED(temp)) { | ||
719 | /* disable remote wake up for USB 2.0 */ | ||
720 | u32 __iomem *addr; | ||
721 | u32 tmp; | ||
722 | |||
723 | addr = &xhci->op_regs->port_power_base + | ||
724 | NUM_PORT_REGS * (port & 0xff); | ||
725 | tmp = xhci_readl(xhci, addr); | ||
726 | tmp &= ~PORT_RWE; | ||
727 | xhci_writel(xhci, tmp, addr); | ||
728 | } | ||
729 | } | ||
730 | |||
731 | (void) xhci_readl(xhci, &xhci->op_regs->command); | ||
732 | |||
733 | xhci->next_statechange = jiffies + msecs_to_jiffies(5); | ||
734 | hcd->state = HC_STATE_RUNNING; | ||
735 | /* re-enable irqs */ | ||
736 | temp = xhci_readl(xhci, &xhci->op_regs->command); | ||
737 | temp |= CMD_EIE; | ||
738 | xhci_writel(xhci, temp, &xhci->op_regs->command); | ||
739 | temp = xhci_readl(xhci, &xhci->op_regs->command); | ||
740 | |||
741 | spin_unlock_irqrestore(&xhci->lock, flags); | ||
742 | return 0; | ||
743 | } | ||
744 | |||
745 | #else | ||
746 | |||
747 | #define xhci_bus_suspend NULL | ||
748 | #define xhci_bus_resume NULL | ||
749 | |||
750 | #endif | ||
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index fd888bc0422b..202770676da3 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c | |||
@@ -1445,6 +1445,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) | |||
1445 | scratchpad_free(xhci); | 1445 | scratchpad_free(xhci); |
1446 | xhci->page_size = 0; | 1446 | xhci->page_size = 0; |
1447 | xhci->page_shift = 0; | 1447 | xhci->page_shift = 0; |
1448 | xhci->bus_suspended = 0; | ||
1448 | } | 1449 | } |
1449 | 1450 | ||
1450 | static int xhci_test_trb_in_td(struct xhci_hcd *xhci, | 1451 | static int xhci_test_trb_in_td(struct xhci_hcd *xhci, |
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index aefc3496376a..3865f8c6f647 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c | |||
@@ -162,6 +162,8 @@ static const struct hc_driver xhci_pci_hc_driver = { | |||
162 | /* Root hub support */ | 162 | /* Root hub support */ |
163 | .hub_control = xhci_hub_control, | 163 | .hub_control = xhci_hub_control, |
164 | .hub_status_data = xhci_hub_status_data, | 164 | .hub_status_data = xhci_hub_status_data, |
165 | .bus_suspend = xhci_bus_suspend, | ||
166 | .bus_resume = xhci_bus_resume, | ||
165 | }; | 167 | }; |
166 | 168 | ||
167 | /*-------------------------------------------------------------------------*/ | 169 | /*-------------------------------------------------------------------------*/ |
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index ca4a923dc810..196e21fb36ff 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h | |||
@@ -357,6 +357,8 @@ struct xhci_op_regs { | |||
357 | #define PORT_U2_TIMEOUT(p) (((p) & 0xff) << 8) | 357 | #define PORT_U2_TIMEOUT(p) (((p) & 0xff) << 8) |
358 | /* Bits 24:31 for port testing */ | 358 | /* Bits 24:31 for port testing */ |
359 | 359 | ||
360 | /* USB2 Protocol PORTSPMSC */ | ||
361 | #define PORT_RWE (1 << 0x3) | ||
360 | 362 | ||
361 | /** | 363 | /** |
362 | * struct xhci_intr_reg - Interrupt Register Set | 364 | * struct xhci_intr_reg - Interrupt Register Set |
@@ -1191,6 +1193,11 @@ struct xhci_hcd { | |||
1191 | #endif | 1193 | #endif |
1192 | /* Host controller watchdog timer structures */ | 1194 | /* Host controller watchdog timer structures */ |
1193 | unsigned int xhc_state; | 1195 | unsigned int xhc_state; |
1196 | |||
1197 | unsigned long bus_suspended; | ||
1198 | unsigned long next_statechange; | ||
1199 | |||
1200 | u32 command; | ||
1194 | /* Host controller is dying - not responding to commands. "I'm not dead yet!" | 1201 | /* Host controller is dying - not responding to commands. "I'm not dead yet!" |
1195 | * | 1202 | * |
1196 | * xHC interrupts have been disabled and a watchdog timer will (or has already) | 1203 | * xHC interrupts have been disabled and a watchdog timer will (or has already) |
@@ -1460,6 +1467,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id, | |||
1460 | int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, | 1467 | int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, |
1461 | char *buf, u16 wLength); | 1468 | char *buf, u16 wLength); |
1462 | int xhci_hub_status_data(struct usb_hcd *hcd, char *buf); | 1469 | int xhci_hub_status_data(struct usb_hcd *hcd, char *buf); |
1470 | int xhci_bus_suspend(struct usb_hcd *hcd); | ||
1471 | int xhci_bus_resume(struct usb_hcd *hcd); | ||
1463 | u32 xhci_port_state_to_neutral(u32 state); | 1472 | u32 xhci_port_state_to_neutral(u32 state); |
1464 | int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port); | 1473 | int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port); |
1465 | void xhci_ring_device(struct xhci_hcd *xhci, int slot_id); | 1474 | void xhci_ring_device(struct xhci_hcd *xhci, int slot_id); |