diff options
Diffstat (limited to 'drivers/usb/dwc3/gadget.c')
| -rw-r--r-- | drivers/usb/dwc3/gadget.c | 183 | 
1 files changed, 152 insertions, 31 deletions
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 2da0a5a2803a..a740eac74d56 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -68,6 +68,22 @@ int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode)  }  /** + * dwc3_gadget_get_link_state - Gets current state of USB Link + * @dwc: pointer to our context structure + * + * Caller should take care of locking. This function will + * return the link state on success (>= 0) or -ETIMEDOUT. + */ +int dwc3_gadget_get_link_state(struct dwc3 *dwc) +{ +	u32		reg; + +	reg = dwc3_readl(dwc->regs, DWC3_DSTS); + +	return DWC3_DSTS_USBLNKST(reg); +} + +/**   * dwc3_gadget_set_link_state - Sets USB Link to a particular State   * @dwc: pointer to our context structure   * @state: the state to put link into @@ -417,7 +433,7 @@ static int dwc3_gadget_start_config(struct dwc3 *dwc, struct dwc3_ep *dep)  static int dwc3_gadget_set_ep_config(struct dwc3 *dwc, struct dwc3_ep *dep,  		const struct usb_endpoint_descriptor *desc,  		const struct usb_ss_ep_comp_descriptor *comp_desc, -		bool ignore) +		bool ignore, bool restore)  {  	struct dwc3_gadget_ep_cmd_params params; @@ -436,6 +452,11 @@ static int dwc3_gadget_set_ep_config(struct dwc3 *dwc, struct dwc3_ep *dep,  	if (ignore)  		params.param0 |= DWC3_DEPCFG_IGN_SEQ_NUM; +	if (restore) { +		params.param0 |= DWC3_DEPCFG_ACTION_RESTORE; +		params.param2 |= dep->saved_state; +	} +  	params.param1 = DWC3_DEPCFG_XFER_COMPLETE_EN  		| DWC3_DEPCFG_XFER_NOT_READY_EN; @@ -494,7 +515,7 @@ static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep)  static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep,  		const struct usb_endpoint_descriptor *desc,  		const struct usb_ss_ep_comp_descriptor *comp_desc, -		bool ignore) +		bool ignore, bool restore)  {  	struct dwc3		*dwc = dep->dwc;  	u32			reg; @@ -508,7 +529,8 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep,  			return ret;  	} -	ret = dwc3_gadget_set_ep_config(dwc, dep, desc, comp_desc, ignore); +	ret = dwc3_gadget_set_ep_config(dwc, dep, desc, comp_desc, ignore, +			restore);  	if (ret)  		return ret; @@ -548,13 +570,13 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep,  	return 0;  } -static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum); +static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force);  static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep)  {  	struct dwc3_request		*req;  	if (!list_empty(&dep->req_queued)) { -		dwc3_stop_active_transfer(dwc, dep->number); +		dwc3_stop_active_transfer(dwc, dep->number, true);  		/* - giveback all requests to gadget driver */  		while (!list_empty(&dep->req_queued)) { @@ -659,7 +681,7 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep,  	}  	spin_lock_irqsave(&dwc->lock, flags); -	ret = __dwc3_gadget_ep_enable(dep, desc, ep->comp_desc, false); +	ret = __dwc3_gadget_ep_enable(dep, desc, ep->comp_desc, false, false);  	spin_unlock_irqrestore(&dwc->lock, flags);  	return ret; @@ -771,9 +793,6 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep,  			trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS_FIRST;  		else  			trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS; - -		if (!req->request.no_interrupt && !chain) -			trb->ctrl |= DWC3_TRB_CTRL_IOC;  		break;  	case USB_ENDPOINT_XFER_BULK: @@ -788,6 +807,9 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep,  		BUG();  	} +	if (!req->request.no_interrupt && !chain) +		trb->ctrl |= DWC3_TRB_CTRL_IOC; +  	if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) {  		trb->ctrl |= DWC3_TRB_CTRL_ISP_IMI;  		trb->ctrl |= DWC3_TRB_CTRL_CSP; @@ -1077,7 +1099,7 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)  		 */  		if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) {  			if (list_empty(&dep->req_queued)) { -				dwc3_stop_active_transfer(dwc, dep->number); +				dwc3_stop_active_transfer(dwc, dep->number, true);  				dep->flags = DWC3_EP_ENABLED;  			}  			return 0; @@ -1107,6 +1129,23 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)  		return ret;  	} +	/* +	 * 4. Stream Capable Bulk Endpoints. We need to start the transfer +	 * right away, otherwise host will not know we have streams to be +	 * handled. +	 */ +	if (dep->stream_capable) { +		int	ret; + +		ret = __dwc3_gadget_kick_transfer(dep, 0, true); +		if (ret && ret != -EBUSY) { +			struct dwc3	*dwc = dep->dwc; + +			dev_dbg(dwc->dev, "%s: failed to kick transfers\n", +					dep->name); +		} +	} +  	return 0;  } @@ -1163,7 +1202,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,  		}  		if (r == req) {  			/* wait until it is processed */ -			dwc3_stop_active_transfer(dwc, dep->number); +			dwc3_stop_active_transfer(dwc, dep->number, true);  			goto out1;  		}  		dev_err(dwc->dev, "request %p was not queued to %s\n", @@ -1194,8 +1233,7 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value)  		ret = dwc3_send_gadget_ep_cmd(dwc, dep->number,  			DWC3_DEPCMD_SETSTALL, ¶ms);  		if (ret) -			dev_err(dwc->dev, "failed to %s STALL on %s\n", -					value ? "set" : "clear", +			dev_err(dwc->dev, "failed to set STALL on %s\n",  					dep->name);  		else  			dep->flags |= DWC3_EP_STALL; @@ -1203,8 +1241,7 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value)  		ret = dwc3_send_gadget_ep_cmd(dwc, dep->number,  			DWC3_DEPCMD_CLEARSTALL, ¶ms);  		if (ret) -			dev_err(dwc->dev, "failed to %s STALL on %s\n", -					value ? "set" : "clear", +			dev_err(dwc->dev, "failed to clear STALL on %s\n",  					dep->name);  		else  			dep->flags &= ~(DWC3_EP_STALL | DWC3_EP_WEDGE); @@ -1387,7 +1424,7 @@ static int dwc3_gadget_set_selfpowered(struct usb_gadget *g,  	return 0;  } -static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on) +static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend)  {  	u32			reg;  	u32			timeout = 500; @@ -1402,9 +1439,17 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on)  		if (dwc->revision >= DWC3_REVISION_194A)  			reg &= ~DWC3_DCTL_KEEP_CONNECT;  		reg |= DWC3_DCTL_RUN_STOP; + +		if (dwc->has_hibernation) +			reg |= DWC3_DCTL_KEEP_CONNECT; +  		dwc->pullups_connected = true;  	} else {  		reg &= ~DWC3_DCTL_RUN_STOP; + +		if (dwc->has_hibernation && !suspend) +			reg &= ~DWC3_DCTL_KEEP_CONNECT; +  		dwc->pullups_connected = false;  	} @@ -1442,7 +1487,7 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on)  	is_on = !!is_on;  	spin_lock_irqsave(&dwc->lock, flags); -	ret = dwc3_gadget_run_stop(dwc, is_on); +	ret = dwc3_gadget_run_stop(dwc, is_on, false);  	spin_unlock_irqrestore(&dwc->lock, flags);  	return ret; @@ -1549,14 +1594,16 @@ static int dwc3_gadget_start(struct usb_gadget *g,  	dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);  	dep = dwc->eps[0]; -	ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); +	ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, +			false);  	if (ret) {  		dev_err(dwc->dev, "failed to enable %s\n", dep->name);  		goto err2;  	}  	dep = dwc->eps[1]; -	ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); +	ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, +			false);  	if (ret) {  		dev_err(dwc->dev, "failed to enable %s\n", dep->name);  		goto err3; @@ -1849,15 +1896,12 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep,  			 */  			dep->flags = DWC3_EP_PENDING_REQUEST;  		} else { -			dwc3_stop_active_transfer(dwc, dep->number); +			dwc3_stop_active_transfer(dwc, dep->number, true);  			dep->flags = DWC3_EP_ENABLED;  		}  		return 1;  	} -	if ((event->status & DEPEVT_STATUS_IOC) && -			(trb->ctrl & DWC3_TRB_CTRL_IOC)) -		return 0;  	return 1;  } @@ -1999,7 +2043,25 @@ static void dwc3_disconnect_gadget(struct dwc3 *dwc)  	}  } -static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum) +static void dwc3_suspend_gadget(struct dwc3 *dwc) +{ +	if (dwc->gadget_driver && dwc->gadget_driver->suspend) { +		spin_unlock(&dwc->lock); +		dwc->gadget_driver->suspend(&dwc->gadget); +		spin_lock(&dwc->lock); +	} +} + +static void dwc3_resume_gadget(struct dwc3 *dwc) +{ +	if (dwc->gadget_driver && dwc->gadget_driver->resume) { +		spin_unlock(&dwc->lock); +		dwc->gadget_driver->resume(&dwc->gadget); +		spin_lock(&dwc->lock); +	} +} + +static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force)  {  	struct dwc3_ep *dep;  	struct dwc3_gadget_ep_cmd_params params; @@ -2031,7 +2093,8 @@ static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum)  	 */  	cmd = DWC3_DEPCMD_ENDTRANSFER; -	cmd |= DWC3_DEPCMD_HIPRI_FORCERM | DWC3_DEPCMD_CMDIOC; +	cmd |= force ? DWC3_DEPCMD_HIPRI_FORCERM : 0; +	cmd |= DWC3_DEPCMD_CMDIOC;  	cmd |= DWC3_DEPCMD_PARAM(dep->resource_index);  	memset(¶ms, 0, sizeof(params));  	ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); @@ -2260,17 +2323,23 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)  		reg |= DWC3_DCTL_HIRD_THRES(12);  		dwc3_writel(dwc->regs, DWC3_DCTL, reg); +	} else { +		reg = dwc3_readl(dwc->regs, DWC3_DCTL); +		reg &= ~DWC3_DCTL_HIRD_THRES_MASK; +		dwc3_writel(dwc->regs, DWC3_DCTL, reg);  	}  	dep = dwc->eps[0]; -	ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true); +	ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true, +			false);  	if (ret) {  		dev_err(dwc->dev, "failed to enable %s\n", dep->name);  		return;  	}  	dep = dwc->eps[1]; -	ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true); +	ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true, +			false);  	if (ret) {  		dev_err(dwc->dev, "failed to enable %s\n", dep->name);  		return; @@ -2378,9 +2447,50 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,  	dwc->link_state = next; +	switch (next) { +	case DWC3_LINK_STATE_U1: +		if (dwc->speed == USB_SPEED_SUPER) +			dwc3_suspend_gadget(dwc); +		break; +	case DWC3_LINK_STATE_U2: +	case DWC3_LINK_STATE_U3: +		dwc3_suspend_gadget(dwc); +		break; +	case DWC3_LINK_STATE_RESUME: +		dwc3_resume_gadget(dwc); +		break; +	default: +		/* do nothing */ +		break; +	} +  	dev_vdbg(dwc->dev, "%s link %d\n", __func__, dwc->link_state);  } +static void dwc3_gadget_hibernation_interrupt(struct dwc3 *dwc, +		unsigned int evtinfo) +{ +	unsigned int is_ss = evtinfo & BIT(4); + +	/** +	 * WORKAROUND: DWC3 revison 2.20a with hibernation support +	 * have a known issue which can cause USB CV TD.9.23 to fail +	 * randomly. +	 * +	 * Because of this issue, core could generate bogus hibernation +	 * events which SW needs to ignore. +	 * +	 * Refers to: +	 * +	 * STAR#9000546576: Device Mode Hibernation: Issue in USB 2.0 +	 * Device Fallback from SuperSpeed +	 */ +	if (is_ss ^ (dwc->speed == USB_SPEED_SUPER)) +		return; + +	/* enter hibernation here */ +} +  static void dwc3_gadget_interrupt(struct dwc3 *dwc,  		const struct dwc3_event_devt *event)  { @@ -2397,6 +2507,13 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc,  	case DWC3_DEVICE_EVENT_WAKEUP:  		dwc3_gadget_wakeup_interrupt(dwc);  		break; +	case DWC3_DEVICE_EVENT_HIBER_REQ: +		if (dev_WARN_ONCE(dwc->dev, !dwc->has_hibernation, +					"unexpected hibernation event\n")) +			break; + +		dwc3_gadget_hibernation_interrupt(dwc, event->event_info); +		break;  	case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE:  		dwc3_gadget_linksts_change_interrupt(dwc, event->event_info);  		break; @@ -2661,8 +2778,10 @@ void dwc3_gadget_exit(struct dwc3 *dwc)  int dwc3_gadget_prepare(struct dwc3 *dwc)  { -	if (dwc->pullups_connected) +	if (dwc->pullups_connected) {  		dwc3_gadget_disable_irq(dwc); +		dwc3_gadget_run_stop(dwc, true, true); +	}  	return 0;  } @@ -2671,7 +2790,7 @@ void dwc3_gadget_complete(struct dwc3 *dwc)  {  	if (dwc->pullups_connected) {  		dwc3_gadget_enable_irq(dwc); -		dwc3_gadget_run_stop(dwc, true); +		dwc3_gadget_run_stop(dwc, true, false);  	}  } @@ -2694,12 +2813,14 @@ int dwc3_gadget_resume(struct dwc3 *dwc)  	dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);  	dep = dwc->eps[0]; -	ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); +	ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, +			false);  	if (ret)  		goto err0;  	dep = dwc->eps[1]; -	ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); +	ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, +			false);  	if (ret)  		goto err1;  | 
