diff options
Diffstat (limited to 'drivers/usb/host/xhci.c')
| -rw-r--r-- | drivers/usb/host/xhci.c | 73 | 
1 files changed, 63 insertions, 10 deletions
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 5d7d4e951ea4..06fca0835b52 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -577,6 +577,65 @@ static void xhci_restore_registers(struct xhci_hcd *xhci)  	xhci_write_64(xhci, xhci->s3.erst_base, &xhci->ir_set->erst_base);  } +static void xhci_set_cmd_ring_deq(struct xhci_hcd *xhci) +{ +	u64	val_64; + +	/* step 2: initialize command ring buffer */ +	val_64 = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); +	val_64 = (val_64 & (u64) CMD_RING_RSVD_BITS) | +		(xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, +				      xhci->cmd_ring->dequeue) & +		 (u64) ~CMD_RING_RSVD_BITS) | +		xhci->cmd_ring->cycle_state; +	xhci_dbg(xhci, "// Setting command ring address to 0x%llx\n", +			(long unsigned long) val_64); +	xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring); +} + +/* + * The whole command ring must be cleared to zero when we suspend the host. + * + * The host doesn't save the command ring pointer in the suspend well, so we + * need to re-program it on resume.  Unfortunately, the pointer must be 64-byte + * aligned, because of the reserved bits in the command ring dequeue pointer + * register.  Therefore, we can't just set the dequeue pointer back in the + * middle of the ring (TRBs are 16-byte aligned). + */ +static void xhci_clear_command_ring(struct xhci_hcd *xhci) +{ +	struct xhci_ring *ring; +	struct xhci_segment *seg; + +	ring = xhci->cmd_ring; +	seg = ring->deq_seg; +	do { +		memset(seg->trbs, 0, SEGMENT_SIZE); +		seg = seg->next; +	} while (seg != ring->deq_seg); + +	/* Reset the software enqueue and dequeue pointers */ +	ring->deq_seg = ring->first_seg; +	ring->dequeue = ring->first_seg->trbs; +	ring->enq_seg = ring->deq_seg; +	ring->enqueue = ring->dequeue; + +	/* +	 * Ring is now zeroed, so the HW should look for change of ownership +	 * when the cycle bit is set to 1. +	 */ +	ring->cycle_state = 1; + +	/* +	 * Reset the hardware dequeue pointer. +	 * Yes, this will need to be re-written after resume, but we're paranoid +	 * and want to make sure the hardware doesn't access bogus memory +	 * because, say, the BIOS or an SMI started the host without changing +	 * the command ring pointers. +	 */ +	xhci_set_cmd_ring_deq(xhci); +} +  /*   * Stop HC (not bus-specific)   * @@ -604,6 +663,7 @@ int xhci_suspend(struct xhci_hcd *xhci)  		spin_unlock_irq(&xhci->lock);  		return -ETIMEDOUT;  	} +	xhci_clear_command_ring(xhci);  	/* step 3: save registers */  	xhci_save_registers(xhci); @@ -635,7 +695,6 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)  	u32			command, temp = 0;  	struct usb_hcd		*hcd = xhci_to_hcd(xhci);  	struct pci_dev		*pdev = to_pci_dev(hcd->self.controller); -	u64	val_64;  	int	old_state, retval;  	old_state = hcd->state; @@ -648,15 +707,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)  		/* step 1: restore register */  		xhci_restore_registers(xhci);  		/* step 2: initialize command ring buffer */ -		val_64 = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); -		val_64 = (val_64 & (u64) CMD_RING_RSVD_BITS) | -			 (xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, -					       xhci->cmd_ring->dequeue) & -			 (u64) ~CMD_RING_RSVD_BITS) | -			 xhci->cmd_ring->cycle_state; -		xhci_dbg(xhci, "// Setting command ring address to 0x%llx\n", -				(long unsigned long) val_64); -		xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring); +		xhci_set_cmd_ring_deq(xhci);  		/* step 3: restore state and start state*/  		/* step 3: set CRS flag */  		command = xhci_readl(xhci, &xhci->op_regs->command); @@ -714,6 +765,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)  		return retval;  	} +	spin_unlock_irq(&xhci->lock);  	/* Re-setup MSI-X */  	if (hcd->irq)  		free_irq(hcd->irq, hcd); @@ -736,6 +788,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)  		hcd->irq = pdev->irq;  	} +	spin_lock_irq(&xhci->lock);  	/* step 4: set Run/Stop bit */  	command = xhci_readl(xhci, &xhci->op_regs->command);  	command |= CMD_RUN;  | 
