diff options
author | Andiry Xu <andiry.xu@amd.com> | 2010-10-14 10:23:06 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2010-10-22 13:22:13 -0400 |
commit | 5535b1d5f8885695c6ded783c692e3c0d0eda8ca (patch) | |
tree | f5454493a1c50e4a6254d904578dc3ecfd1d6e63 /drivers/usb | |
parent | 9777e3ce907d4cb5a513902a87ecd03b52499569 (diff) |
USB: xHCI: PCI power management implementation
This patch implements the PCI suspend/resume.
Please refer to xHCI spec for doing the suspend/resume operation.
For S3, CSS/SRS in USBCMD is used to save/restore the internal state.
However, an error maybe occurs while restoring the internal state.
In this case, it means that HC internal state is wrong and HC will be
re-initialized.
Signed-off-by: Libin Yang <libin.yang@amd.com>
Signed-off-by: Dong Nguyen <dong.nguyen@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>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/host/xhci-pci.c | 34 | ||||
-rw-r--r-- | drivers/usb/host/xhci.c | 210 | ||||
-rw-r--r-- | drivers/usb/host/xhci.h | 16 |
3 files changed, 258 insertions, 2 deletions
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 3865f8c6f647..bb668a894ab9 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c | |||
@@ -116,6 +116,30 @@ static int xhci_pci_setup(struct usb_hcd *hcd) | |||
116 | return xhci_pci_reinit(xhci, pdev); | 116 | return xhci_pci_reinit(xhci, pdev); |
117 | } | 117 | } |
118 | 118 | ||
119 | #ifdef CONFIG_PM | ||
120 | static int xhci_pci_suspend(struct usb_hcd *hcd, bool do_wakeup) | ||
121 | { | ||
122 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); | ||
123 | int retval = 0; | ||
124 | |||
125 | if (hcd->state != HC_STATE_SUSPENDED) | ||
126 | return -EINVAL; | ||
127 | |||
128 | retval = xhci_suspend(xhci); | ||
129 | |||
130 | return retval; | ||
131 | } | ||
132 | |||
133 | static int xhci_pci_resume(struct usb_hcd *hcd, bool hibernated) | ||
134 | { | ||
135 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); | ||
136 | int retval = 0; | ||
137 | |||
138 | retval = xhci_resume(xhci, hibernated); | ||
139 | return retval; | ||
140 | } | ||
141 | #endif /* CONFIG_PM */ | ||
142 | |||
119 | static const struct hc_driver xhci_pci_hc_driver = { | 143 | static const struct hc_driver xhci_pci_hc_driver = { |
120 | .description = hcd_name, | 144 | .description = hcd_name, |
121 | .product_desc = "xHCI Host Controller", | 145 | .product_desc = "xHCI Host Controller", |
@@ -132,7 +156,10 @@ static const struct hc_driver xhci_pci_hc_driver = { | |||
132 | */ | 156 | */ |
133 | .reset = xhci_pci_setup, | 157 | .reset = xhci_pci_setup, |
134 | .start = xhci_run, | 158 | .start = xhci_run, |
135 | /* suspend and resume implemented later */ | 159 | #ifdef CONFIG_PM |
160 | .pci_suspend = xhci_pci_suspend, | ||
161 | .pci_resume = xhci_pci_resume, | ||
162 | #endif | ||
136 | .stop = xhci_stop, | 163 | .stop = xhci_stop, |
137 | .shutdown = xhci_shutdown, | 164 | .shutdown = xhci_shutdown, |
138 | 165 | ||
@@ -188,6 +215,11 @@ static struct pci_driver xhci_pci_driver = { | |||
188 | /* suspend and resume implemented later */ | 215 | /* suspend and resume implemented later */ |
189 | 216 | ||
190 | .shutdown = usb_hcd_pci_shutdown, | 217 | .shutdown = usb_hcd_pci_shutdown, |
218 | #ifdef CONFIG_PM_SLEEP | ||
219 | .driver = { | ||
220 | .pm = &usb_hcd_pci_pm_ops | ||
221 | }, | ||
222 | #endif | ||
191 | }; | 223 | }; |
192 | 224 | ||
193 | int xhci_register_pci(void) | 225 | int xhci_register_pci(void) |
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 3d2af688157a..33d0034d8a6f 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c | |||
@@ -551,6 +551,216 @@ void xhci_shutdown(struct usb_hcd *hcd) | |||
551 | xhci_readl(xhci, &xhci->op_regs->status)); | 551 | xhci_readl(xhci, &xhci->op_regs->status)); |
552 | } | 552 | } |
553 | 553 | ||
554 | static void xhci_save_registers(struct xhci_hcd *xhci) | ||
555 | { | ||
556 | xhci->s3.command = xhci_readl(xhci, &xhci->op_regs->command); | ||
557 | xhci->s3.dev_nt = xhci_readl(xhci, &xhci->op_regs->dev_notification); | ||
558 | xhci->s3.dcbaa_ptr = xhci_read_64(xhci, &xhci->op_regs->dcbaa_ptr); | ||
559 | xhci->s3.config_reg = xhci_readl(xhci, &xhci->op_regs->config_reg); | ||
560 | xhci->s3.irq_pending = xhci_readl(xhci, &xhci->ir_set->irq_pending); | ||
561 | xhci->s3.irq_control = xhci_readl(xhci, &xhci->ir_set->irq_control); | ||
562 | xhci->s3.erst_size = xhci_readl(xhci, &xhci->ir_set->erst_size); | ||
563 | xhci->s3.erst_base = xhci_read_64(xhci, &xhci->ir_set->erst_base); | ||
564 | xhci->s3.erst_dequeue = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); | ||
565 | } | ||
566 | |||
567 | static void xhci_restore_registers(struct xhci_hcd *xhci) | ||
568 | { | ||
569 | xhci_writel(xhci, xhci->s3.command, &xhci->op_regs->command); | ||
570 | xhci_writel(xhci, xhci->s3.dev_nt, &xhci->op_regs->dev_notification); | ||
571 | xhci_write_64(xhci, xhci->s3.dcbaa_ptr, &xhci->op_regs->dcbaa_ptr); | ||
572 | xhci_writel(xhci, xhci->s3.config_reg, &xhci->op_regs->config_reg); | ||
573 | xhci_writel(xhci, xhci->s3.irq_pending, &xhci->ir_set->irq_pending); | ||
574 | xhci_writel(xhci, xhci->s3.irq_control, &xhci->ir_set->irq_control); | ||
575 | xhci_writel(xhci, xhci->s3.erst_size, &xhci->ir_set->erst_size); | ||
576 | xhci_write_64(xhci, xhci->s3.erst_base, &xhci->ir_set->erst_base); | ||
577 | } | ||
578 | |||
579 | /* | ||
580 | * Stop HC (not bus-specific) | ||
581 | * | ||
582 | * This is called when the machine transition into S3/S4 mode. | ||
583 | * | ||
584 | */ | ||
585 | int xhci_suspend(struct xhci_hcd *xhci) | ||
586 | { | ||
587 | int rc = 0; | ||
588 | struct usb_hcd *hcd = xhci_to_hcd(xhci); | ||
589 | u32 command; | ||
590 | |||
591 | spin_lock_irq(&xhci->lock); | ||
592 | clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); | ||
593 | /* step 1: stop endpoint */ | ||
594 | /* skipped assuming that port suspend has done */ | ||
595 | |||
596 | /* step 2: clear Run/Stop bit */ | ||
597 | command = xhci_readl(xhci, &xhci->op_regs->command); | ||
598 | command &= ~CMD_RUN; | ||
599 | xhci_writel(xhci, command, &xhci->op_regs->command); | ||
600 | if (handshake(xhci, &xhci->op_regs->status, | ||
601 | STS_HALT, STS_HALT, 100*100)) { | ||
602 | xhci_warn(xhci, "WARN: xHC CMD_RUN timeout\n"); | ||
603 | spin_unlock_irq(&xhci->lock); | ||
604 | return -ETIMEDOUT; | ||
605 | } | ||
606 | |||
607 | /* step 3: save registers */ | ||
608 | xhci_save_registers(xhci); | ||
609 | |||
610 | /* step 4: set CSS flag */ | ||
611 | command = xhci_readl(xhci, &xhci->op_regs->command); | ||
612 | command |= CMD_CSS; | ||
613 | xhci_writel(xhci, command, &xhci->op_regs->command); | ||
614 | if (handshake(xhci, &xhci->op_regs->status, STS_SAVE, 0, 10*100)) { | ||
615 | xhci_warn(xhci, "WARN: xHC CMD_CSS timeout\n"); | ||
616 | spin_unlock_irq(&xhci->lock); | ||
617 | return -ETIMEDOUT; | ||
618 | } | ||
619 | /* step 5: remove core well power */ | ||
620 | xhci_cleanup_msix(xhci); | ||
621 | spin_unlock_irq(&xhci->lock); | ||
622 | |||
623 | return rc; | ||
624 | } | ||
625 | |||
626 | /* | ||
627 | * start xHC (not bus-specific) | ||
628 | * | ||
629 | * This is called when the machine transition from S3/S4 mode. | ||
630 | * | ||
631 | */ | ||
632 | int xhci_resume(struct xhci_hcd *xhci, bool hibernated) | ||
633 | { | ||
634 | u32 command, temp = 0; | ||
635 | struct usb_hcd *hcd = xhci_to_hcd(xhci); | ||
636 | struct pci_dev *pdev = to_pci_dev(hcd->self.controller); | ||
637 | u64 val_64; | ||
638 | int old_state, retval; | ||
639 | |||
640 | old_state = hcd->state; | ||
641 | if (time_before(jiffies, xhci->next_statechange)) | ||
642 | msleep(100); | ||
643 | |||
644 | spin_lock_irq(&xhci->lock); | ||
645 | |||
646 | if (!hibernated) { | ||
647 | /* step 1: restore register */ | ||
648 | xhci_restore_registers(xhci); | ||
649 | /* step 2: initialize command ring buffer */ | ||
650 | val_64 = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); | ||
651 | val_64 = (val_64 & (u64) CMD_RING_RSVD_BITS) | | ||
652 | (xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, | ||
653 | xhci->cmd_ring->dequeue) & | ||
654 | (u64) ~CMD_RING_RSVD_BITS) | | ||
655 | xhci->cmd_ring->cycle_state; | ||
656 | xhci_dbg(xhci, "// Setting command ring address to 0x%llx\n", | ||
657 | (long unsigned long) val_64); | ||
658 | xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring); | ||
659 | /* step 3: restore state and start state*/ | ||
660 | /* step 3: set CRS flag */ | ||
661 | command = xhci_readl(xhci, &xhci->op_regs->command); | ||
662 | command |= CMD_CRS; | ||
663 | xhci_writel(xhci, command, &xhci->op_regs->command); | ||
664 | if (handshake(xhci, &xhci->op_regs->status, | ||
665 | STS_RESTORE, 0, 10*100)) { | ||
666 | xhci_dbg(xhci, "WARN: xHC CMD_CSS timeout\n"); | ||
667 | spin_unlock_irq(&xhci->lock); | ||
668 | return -ETIMEDOUT; | ||
669 | } | ||
670 | temp = xhci_readl(xhci, &xhci->op_regs->status); | ||
671 | } | ||
672 | |||
673 | /* If restore operation fails, re-initialize the HC during resume */ | ||
674 | if ((temp & STS_SRE) || hibernated) { | ||
675 | usb_root_hub_lost_power(hcd->self.root_hub); | ||
676 | |||
677 | xhci_dbg(xhci, "Stop HCD\n"); | ||
678 | xhci_halt(xhci); | ||
679 | xhci_reset(xhci); | ||
680 | if (hibernated) | ||
681 | xhci_cleanup_msix(xhci); | ||
682 | spin_unlock_irq(&xhci->lock); | ||
683 | |||
684 | #ifdef CONFIG_USB_XHCI_HCD_DEBUGGING | ||
685 | /* Tell the event ring poll function not to reschedule */ | ||
686 | xhci->zombie = 1; | ||
687 | del_timer_sync(&xhci->event_ring_timer); | ||
688 | #endif | ||
689 | |||
690 | xhci_dbg(xhci, "// Disabling event ring interrupts\n"); | ||
691 | temp = xhci_readl(xhci, &xhci->op_regs->status); | ||
692 | xhci_writel(xhci, temp & ~STS_EINT, &xhci->op_regs->status); | ||
693 | temp = xhci_readl(xhci, &xhci->ir_set->irq_pending); | ||
694 | xhci_writel(xhci, ER_IRQ_DISABLE(temp), | ||
695 | &xhci->ir_set->irq_pending); | ||
696 | xhci_print_ir_set(xhci, xhci->ir_set, 0); | ||
697 | |||
698 | xhci_dbg(xhci, "cleaning up memory\n"); | ||
699 | xhci_mem_cleanup(xhci); | ||
700 | xhci_dbg(xhci, "xhci_stop completed - status = %x\n", | ||
701 | xhci_readl(xhci, &xhci->op_regs->status)); | ||
702 | |||
703 | xhci_dbg(xhci, "Initialize the HCD\n"); | ||
704 | retval = xhci_init(hcd); | ||
705 | if (retval) | ||
706 | return retval; | ||
707 | |||
708 | xhci_dbg(xhci, "Start the HCD\n"); | ||
709 | retval = xhci_run(hcd); | ||
710 | if (!retval) | ||
711 | set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); | ||
712 | hcd->state = HC_STATE_SUSPENDED; | ||
713 | return retval; | ||
714 | } | ||
715 | |||
716 | /* Re-setup MSI-X */ | ||
717 | if (hcd->irq) | ||
718 | free_irq(hcd->irq, hcd); | ||
719 | hcd->irq = -1; | ||
720 | |||
721 | retval = xhci_setup_msix(xhci); | ||
722 | if (retval) | ||
723 | /* fall back to msi*/ | ||
724 | retval = xhci_setup_msi(xhci); | ||
725 | |||
726 | if (retval) { | ||
727 | /* fall back to legacy interrupt*/ | ||
728 | retval = request_irq(pdev->irq, &usb_hcd_irq, IRQF_SHARED, | ||
729 | hcd->irq_descr, hcd); | ||
730 | if (retval) { | ||
731 | xhci_err(xhci, "request interrupt %d failed\n", | ||
732 | pdev->irq); | ||
733 | return retval; | ||
734 | } | ||
735 | hcd->irq = pdev->irq; | ||
736 | } | ||
737 | |||
738 | /* step 4: set Run/Stop bit */ | ||
739 | command = xhci_readl(xhci, &xhci->op_regs->command); | ||
740 | command |= CMD_RUN; | ||
741 | xhci_writel(xhci, command, &xhci->op_regs->command); | ||
742 | handshake(xhci, &xhci->op_regs->status, STS_HALT, | ||
743 | 0, 250 * 1000); | ||
744 | |||
745 | /* step 5: walk topology and initialize portsc, | ||
746 | * portpmsc and portli | ||
747 | */ | ||
748 | /* this is done in bus_resume */ | ||
749 | |||
750 | /* step 6: restart each of the previously | ||
751 | * Running endpoints by ringing their doorbells | ||
752 | */ | ||
753 | |||
754 | set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); | ||
755 | if (!hibernated) | ||
756 | hcd->state = old_state; | ||
757 | else | ||
758 | hcd->state = HC_STATE_SUSPENDED; | ||
759 | |||
760 | spin_unlock_irq(&xhci->lock); | ||
761 | return 0; | ||
762 | } | ||
763 | |||
554 | /*-------------------------------------------------------------------------*/ | 764 | /*-------------------------------------------------------------------------*/ |
555 | 765 | ||
556 | /** | 766 | /** |
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 196e21fb36ff..c08928adc524 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h | |||
@@ -191,7 +191,7 @@ struct xhci_op_regs { | |||
191 | /* bits 4:6 are reserved (and should be preserved on writes). */ | 191 | /* bits 4:6 are reserved (and should be preserved on writes). */ |
192 | /* light reset (port status stays unchanged) - reset completed when this is 0 */ | 192 | /* light reset (port status stays unchanged) - reset completed when this is 0 */ |
193 | #define CMD_LRESET (1 << 7) | 193 | #define CMD_LRESET (1 << 7) |
194 | /* FIXME: ignoring host controller save/restore state for now. */ | 194 | /* host controller save/restore state. */ |
195 | #define CMD_CSS (1 << 8) | 195 | #define CMD_CSS (1 << 8) |
196 | #define CMD_CRS (1 << 9) | 196 | #define CMD_CRS (1 << 9) |
197 | /* Enable Wrap Event - '1' means xHC generates an event when MFINDEX wraps. */ | 197 | /* Enable Wrap Event - '1' means xHC generates an event when MFINDEX wraps. */ |
@@ -1130,6 +1130,17 @@ struct urb_priv { | |||
1130 | #define XHCI_STOP_EP_CMD_TIMEOUT 5 | 1130 | #define XHCI_STOP_EP_CMD_TIMEOUT 5 |
1131 | /* XXX: Make these module parameters */ | 1131 | /* XXX: Make these module parameters */ |
1132 | 1132 | ||
1133 | struct s3_save { | ||
1134 | u32 command; | ||
1135 | u32 dev_nt; | ||
1136 | u64 dcbaa_ptr; | ||
1137 | u32 config_reg; | ||
1138 | u32 irq_pending; | ||
1139 | u32 irq_control; | ||
1140 | u32 erst_size; | ||
1141 | u64 erst_base; | ||
1142 | u64 erst_dequeue; | ||
1143 | }; | ||
1133 | 1144 | ||
1134 | /* There is one ehci_hci structure per controller */ | 1145 | /* There is one ehci_hci structure per controller */ |
1135 | struct xhci_hcd { | 1146 | struct xhci_hcd { |
@@ -1198,6 +1209,7 @@ struct xhci_hcd { | |||
1198 | unsigned long next_statechange; | 1209 | unsigned long next_statechange; |
1199 | 1210 | ||
1200 | u32 command; | 1211 | u32 command; |
1212 | struct s3_save s3; | ||
1201 | /* Host controller is dying - not responding to commands. "I'm not dead yet!" | 1213 | /* Host controller is dying - not responding to commands. "I'm not dead yet!" |
1202 | * | 1214 | * |
1203 | * xHC interrupts have been disabled and a watchdog timer will (or has already) | 1215 | * xHC interrupts have been disabled and a watchdog timer will (or has already) |
@@ -1393,6 +1405,8 @@ int xhci_init(struct usb_hcd *hcd); | |||
1393 | int xhci_run(struct usb_hcd *hcd); | 1405 | int xhci_run(struct usb_hcd *hcd); |
1394 | void xhci_stop(struct usb_hcd *hcd); | 1406 | void xhci_stop(struct usb_hcd *hcd); |
1395 | void xhci_shutdown(struct usb_hcd *hcd); | 1407 | void xhci_shutdown(struct usb_hcd *hcd); |
1408 | int xhci_suspend(struct xhci_hcd *xhci); | ||
1409 | int xhci_resume(struct xhci_hcd *xhci, bool hibernated); | ||
1396 | int xhci_get_frame(struct usb_hcd *hcd); | 1410 | int xhci_get_frame(struct usb_hcd *hcd); |
1397 | irqreturn_t xhci_irq(struct usb_hcd *hcd); | 1411 | irqreturn_t xhci_irq(struct usb_hcd *hcd); |
1398 | irqreturn_t xhci_msi_irq(int irq, struct usb_hcd *hcd); | 1412 | irqreturn_t xhci_msi_irq(int irq, struct usb_hcd *hcd); |