Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bf131dca authored by Mayank Rana's avatar Mayank Rana
Browse files

dwc3: gadget: Improve remote wakeup functionality



Current available remote wakeup functionality is not robust
enough as most of time it failed to wait for host to complete
resuming signle and move USB device to U0 state. Hence enhance
this functionality by using link state change interrupt and
waiting for max 16 sec for host to resume.

Change-Id: I375d7812336ce0ecc49bfeea859bc76d90a4e0b9
Signed-off-by: default avatarMayank Rana <mrana@codeaurora.org>
parent d73672e1
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1622,6 +1622,7 @@ static int dwc3_probe(struct platform_device *pdev)

	platform_set_drvdata(pdev, dwc);

	init_waitqueue_head(&dwc->wait_linkstate);
	spin_lock_init(&dwc->lock);

	pm_runtime_no_callbacks(dev);
+4 −0
Original line number Diff line number Diff line
@@ -1151,6 +1151,8 @@ struct dwc3_scratchpad_array {
 * @bh_dbg_index: index for capturing bh_completion_time and bh_handled_evt_cnt
 * @last_run_stop: timestamp denoting the last run_stop update
 * @is_remote_wakeup_enabled: remote wakeup status from host perspective
 * @wait_linkstate: waitqueue for waiting LINK to move into required state
 * @remote_wakeup_work: use to perform remote wakeup from this context
 */
struct dwc3 {
	struct work_struct	drd_work;
@@ -1397,6 +1399,8 @@ struct dwc3 {
	u32			gen2_tx_de_emph3;
	ktime_t			last_run_stop;
	bool			is_remote_wakeup_enabled;
	wait_queue_head_t	wait_linkstate;
	struct work_struct	remote_wakeup_work;
};

#define INCRX_BURST_MODE 0
+143 −28
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@

static int __dwc3_gadget_start(struct dwc3 *dwc);
static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc);
static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, bool remote_wakeup);

/**
 * dwc3_gadget_set_test_mode - enables usb2 test modes
@@ -1924,15 +1925,17 @@ static int dwc3_gadget_get_frame(struct usb_gadget *g)
	return __dwc3_gadget_get_frame(dwc);
}

static int __dwc3_gadget_wakeup(struct dwc3 *dwc)
static int dwc3_gadget_remote_wakeup(struct dwc3 *dwc)
{
	int			retries;

	int			ret;
	int			ret = 0;
	u32			reg;

	u8			link_state;
	unsigned long		flags;
	bool			link_recover_only = false;

	dev_dbg(dwc->dev, "%s(): Entry\n", __func__);
	disable_irq(dwc->irq);
	spin_lock_irqsave(&dwc->lock, flags);
	/*
	 * According to the Databook Remote wakeup request should
	 * be issued only when the device is in early suspend state.
@@ -1942,21 +1945,45 @@ static int __dwc3_gadget_wakeup(struct dwc3 *dwc)
	reg = dwc3_readl(dwc->regs, DWC3_DSTS);

	link_state = DWC3_DSTS_USBLNKST(reg);

	switch (link_state) {
	case DWC3_LINK_STATE_RESET:
	case DWC3_LINK_STATE_RX_DET:	/* in HS, means Early Suspend */
	case DWC3_LINK_STATE_U3:	/* in HS, means SUSPEND */
	case DWC3_LINK_STATE_RESUME:
		break;
	case DWC3_LINK_STATE_U1:
		if (dwc->gadget.speed != USB_SPEED_SUPER) {
			link_recover_only = true;
			break;
		}
		fallthrough;
	default:
		return -EINVAL;
		dev_dbg(dwc->dev, "can't wakeup from link state %d\n",
				link_state);
		ret = -EINVAL;
		goto out;
	}

	/* Enable LINK STATUS change event */
	reg = dwc3_readl(dwc->regs, DWC3_DEVTEN);
	reg |= DWC3_DEVTEN_ULSTCNGEN;
	dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
	/*
	 * memory barrier is required to make sure that required events
	 * with core is enabled before performing RECOVERY mechnism.
	 */
	mb();

	ret = dwc3_gadget_set_link_state(dwc, DWC3_LINK_STATE_RECOV);
	if (ret < 0) {
		dev_err(dwc->dev, "failed to put link in Recovery\n");
		return ret;
		/* Disable LINK STATUS change */
		reg = dwc3_readl(dwc->regs, DWC3_DEVTEN);
		reg &= ~DWC3_DEVTEN_ULSTCNGEN;
		dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
		/* Required to complete this operation before returning */
		mb();
		goto out;
	}

	/* Recent versions do this automatically */
@@ -1967,30 +1994,115 @@ static int __dwc3_gadget_wakeup(struct dwc3 *dwc)
		dwc3_writel(dwc->regs, DWC3_DCTL, reg);
	}

	/* poll until Link State changes to ON */
	retries = 20000;
	spin_unlock_irqrestore(&dwc->lock, flags);
	enable_irq(dwc->irq);

	while (retries--) {
		reg = dwc3_readl(dwc->regs, DWC3_DSTS);
	/*
	 * Have bigger value (16 sec) for timeout since some host PCs driving
	 * resume for very long time (e.g. 8 sec)
	 */
	ret = wait_event_interruptible_timeout(dwc->wait_linkstate,
			(dwc->link_state < DWC3_LINK_STATE_U3) ||
			(dwc->link_state == DWC3_LINK_STATE_SS_DIS),
			msecs_to_jiffies(16000));

		/* in HS, means ON */
		if (DWC3_DSTS_USBLNKST(reg) == DWC3_LINK_STATE_U0)
			break;
	spin_lock_irqsave(&dwc->lock, flags);
	/* Disable link status change event */
	reg = dwc3_readl(dwc->regs, DWC3_DEVTEN);
	reg &= ~DWC3_DEVTEN_ULSTCNGEN;
	dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
	/*
	 * Complete this write before we go ahead and perform resume
	 * as we don't need link status change notificaiton anymore.
	 */
	mb();

	if (!ret) {
		dev_dbg(dwc->dev, "Timeout moving into state(%d)\n",
							dwc->link_state);
		ret = -EINVAL;
		spin_unlock_irqrestore(&dwc->lock, flags);
		goto out1;
	} else {
		ret = 0;
		/*
		 * If USB is disconnected OR received RESET from host,
		 * don't perform resume
		 */
		if (dwc->link_state == DWC3_LINK_STATE_SS_DIS ||
				dwc->gadget.state == USB_STATE_DEFAULT)
			link_recover_only = true;
	}

	if (DWC3_DSTS_USBLNKST(reg) != DWC3_LINK_STATE_U0) {
		dev_err(dwc->dev, "failed to send remote wakeup\n");
		return -EINVAL;
	/*
	 * According to DWC3 databook, the controller does not
	 * trigger a wakeup event when remote-wakeup is used.
	 * Hence, after remote-wakeup sequence is complete, and
	 * the device is back at U0 state, it is required that
	 * the resume sequence is initiated by SW.
	 */
	if (!link_recover_only)
		dwc3_gadget_wakeup_interrupt(dwc, true);

	spin_unlock_irqrestore(&dwc->lock, flags);
	dev_dbg(dwc->dev, "%s: Exit\n", __func__);
	return ret;

out:
	spin_unlock_irqrestore(&dwc->lock, flags);
	enable_irq(dwc->irq);

out1:
	return ret;
}

	return 0;
#define DWC3_PM_RESUME_RETRIES		20    /* Max Number of retries */
#define DWC3_PM_RESUME_DELAY		100   /* 100 msec */
static void dwc3_gadget_remote_wakeup_work(struct work_struct *w)
{
	struct dwc3		*dwc;
	int			ret;
	static int		retry_count;

	dwc = container_of(w, struct dwc3, remote_wakeup_work);

	ret = pm_runtime_get_sync(dwc->dev);
	if (ret) {
		/* pm_runtime_get_sync returns -EACCES error between
		 * late_suspend and early_resume, wait for system resume to
		 * finish and queue work again
		 */
		dev_dbg(dwc->dev, "PM runtime get sync failed, ret %d\n", ret);
		if (ret == -EACCES) {
			pm_runtime_put_noidle(dwc->dev);
			if (retry_count == DWC3_PM_RESUME_RETRIES) {
				retry_count = 0;
				dev_err(dwc->dev, "pm_runtime_get_sync timed out\n");
				return;
			}
			msleep(DWC3_PM_RESUME_DELAY);
			retry_count++;
			schedule_work(&dwc->remote_wakeup_work);
			return;
		}
	}
	retry_count = 0;
	dbg_event(0xFF, "Gdgwake gsyn",
		atomic_read(&dwc->dev->power.usage_count));

	ret = dwc3_gadget_remote_wakeup(dwc);
	if (ret)
		dev_err(dwc->dev, "Remote wakeup failed. ret = %d\n", ret);

	pm_runtime_put_noidle(dwc->dev);
	dbg_event(0xFF, "Gdgwake put",
		atomic_read(&dwc->dev->power.usage_count));
}

static int dwc3_gadget_wakeup(struct usb_gadget *g)
{
	struct dwc3		*dwc = gadget_to_dwc(g);
	unsigned long	flags;
	int			ret;

	spin_lock_irqsave(&dwc->lock, flags);
	if (!dwc->is_remote_wakeup_enabled) {
@@ -1999,10 +2111,9 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g)
		return -EINVAL;
	}

	ret = __dwc3_gadget_wakeup(dwc);
	spin_unlock_irqrestore(&dwc->lock, flags);

	return ret;
	schedule_work(&dwc->remote_wakeup_work);
	return 0;
}

static int dwc3_gadget_set_selfpowered(struct usb_gadget *g,
@@ -3286,6 +3397,7 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)
	usb_gadget_set_state(&dwc->gadget, USB_STATE_NOTATTACHED);

	dwc->connected = false;
	wake_up_interruptible(&dwc->wait_linkstate);
}

static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
@@ -3371,6 +3483,7 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
	dwc->gadget.speed = USB_SPEED_UNKNOWN;
	dwc->link_state = DWC3_LINK_STATE_U0;
	dwc->is_remote_wakeup_enabled = false;
	wake_up_interruptible(&dwc->wait_linkstate);
}

static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
@@ -3503,7 +3616,7 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
	 */
}

static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc)
static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, bool remote_wakeup)
{
	enum dwc3_link_state link_state = dwc->link_state;

@@ -3511,7 +3624,7 @@ static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc)
	dwc->link_state = DWC3_LINK_STATE_U0;

	/* For L1 resume case, don't perform resume */
	if (link_state != DWC3_LINK_STATE_U3)
	if (!remote_wakeup && link_state != DWC3_LINK_STATE_U3)
		return;

	/* Handle bus resume case */
@@ -3617,7 +3730,9 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
	}

	dev_dbg(dwc->dev, "Going from (%d)--->(%d)\n", dwc->link_state, next);
	dbg_log_string("link state from %d to %d", dwc->link_state, next);
	dwc->link_state = next;
	wake_up_interruptible(&dwc->wait_linkstate);
}

static void dwc3_gadget_suspend_interrupt(struct dwc3 *dwc,
@@ -3694,7 +3809,7 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc,
		dwc->dbg_gadget_events.connect++;
		break;
	case DWC3_DEVICE_EVENT_WAKEUP:
		dwc3_gadget_wakeup_interrupt(dwc);
		dwc3_gadget_wakeup_interrupt(dwc, false);
		dwc->dbg_gadget_events.wakeup++;
		break;
	case DWC3_DEVICE_EVENT_HIBER_REQ:
@@ -3982,7 +4097,7 @@ int dwc3_gadget_init(struct dwc3 *dwc)
	}

	dwc->irq_gadget = irq;

	INIT_WORK(&dwc->remote_wakeup_work, dwc3_gadget_remote_wakeup_work);
	dwc->ep0_trb = dma_alloc_coherent(dwc->sysdev,
					  sizeof(*dwc->ep0_trb) * 2,
					  &dwc->ep0_trb_addr, GFP_KERNEL);