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

Commit 70588795 authored by Danny Segal's avatar Danny Segal
Browse files

usb: gadget: Delay low-power mode entry after USB bus suspend



When the USB bus is suspended, the suspend event handler immediately puts
the USB core into low power mode state. When this occurs concurrently with
other connectivity event, like disconnect, connect or reset, this may cause
race condition issues. This patch moves the low-power mode after suspend to
be handled by the OTG state machine which is capable of maintaining all of
the bus events in one location. Additionaly, a delay has been added between
the suspend event and the actual low-power mode entry.

CRs-fixed: 690236
Change-Id: I2239c27c0ed1d76b85e3ea7b02e6bbbd66a5b414
Signed-off-by: default avatarDanny Segal <dsegal@codeaurora.org>
parent 810f6916
Loading
Loading
Loading
Loading
+34 −0
Original line number Diff line number Diff line
@@ -388,6 +388,36 @@ static int dwc3_otg_set_peripheral(struct usb_otg *otg,
	return 0;
}

/**
 * dwc3_otg_set_suspend -  Set or clear OTG suspend bit and schedule OTG state machine
 * work.
 *
 * @phy: Pointer to the phy structure.
 * @suspend: 1 - Ask OTG state machine to issue low power mode entry.
 *                 0 - Cancel low-power mode entry request.
 * Returns 0 on success otherwise negative errno.
 */
static int dwc3_otg_set_suspend(struct usb_phy *phy, int suspend)
{
	const unsigned int lpm_after_suspend_delay = 500;

	struct dwc3_otg *dotg = container_of(phy->otg, struct dwc3_otg, otg);

	if (!dotg->dwc->enable_bus_suspend)
		return 0;

	if (suspend) {
		set_bit(DWC3_OTG_SUSPEND, &dotg->inputs);
		queue_delayed_work(system_nrt_wq,
			&dotg->sm_work,
			msecs_to_jiffies(lpm_after_suspend_delay));
	} else {
		clear_bit(DWC3_OTG_SUSPEND, &dotg->inputs);
	}

	return 0;
}

/**
 * dwc3_ext_chg_det_done - callback to handle charger detection completion
 * @otg: Pointer to the otg transceiver structure
@@ -852,6 +882,9 @@ static void dwc3_otg_sm_work(struct work_struct *w)
			if (charger)
				charger->chg_type = DWC3_INVALID_CHARGER;
			work = 1;
		} else if (test_bit(DWC3_OTG_SUSPEND, &dotg->inputs) &&
			test_bit(B_SESS_VLD, &dotg->inputs)) {
			pm_runtime_put_sync(phy->dev);
		}
		break;

@@ -999,6 +1032,7 @@ int dwc3_otg_init(struct dwc3 *dwc)
	dotg->otg.phy->set_power = dwc3_otg_set_power;
	dotg->otg.set_peripheral = dwc3_otg_set_peripheral;
	dotg->otg.set_host = dwc3_otg_set_host;
	dotg->otg.phy->set_suspend = dwc3_otg_set_suspend;
	dotg->otg.phy->state = OTG_STATE_UNDEFINED;

	/*
+4 −3
Original line number Diff line number Diff line
/**
 * dwc3_otg.h - DesignWare USB3 DRD Controller OTG
 *
 * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
 * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -46,6 +46,7 @@ struct dwc3_otg {
	struct dwc3_ext_xceiv	*ext_xceiv;
#define ID		 0
#define B_SESS_VLD	 1
#define DWC3_OTG_SUSPEND 2
	unsigned long inputs;
	struct power_supply	*psy;
	struct completion	dwc3_xcvr_vbus_init;
+25 −3
Original line number Diff line number Diff line
@@ -1705,7 +1705,6 @@ static void dwc3_gadget_wakeup_work(struct work_struct *w)
	spin_unlock_irqrestore(&dwc->lock, flags);
}


static void _dwc3_gadget_wakeup(struct dwc3 *dwc)
{
	u32			timeout = 0;
@@ -2770,6 +2769,10 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)

	dev_vdbg(dwc->dev, "%s\n", __func__);

	/* Clear OTG suspend state */
	if (dwc->enable_bus_suspend)
		usb_phy_set_suspend(dwc->dotg->otg.phy, 0);

	reg = dwc3_readl(dwc->regs, DWC3_DCTL);
	reg &= ~DWC3_DCTL_INITU1ENA;
	dwc3_writel(dwc->regs, DWC3_DCTL, reg);
@@ -2859,6 +2862,10 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
			dwc3_gadget_disconnect_interrupt(dwc);
	}

	/* Clear OTG suspend state */
	if (dwc->enable_bus_suspend)
		usb_phy_set_suspend(dwc->dotg->otg.phy, 0);

	dbg_event(0xFF, "BUS RST", 0);
	/* after reset -> Default State */
	usb_gadget_set_state(&dwc->gadget, USB_STATE_DEFAULT);
@@ -3043,6 +3050,11 @@ static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc)
	 */

	dbg_event(0xFF, "WAKEUP", 0);

	/* Clear OTG suspend state */
	if (dwc->enable_bus_suspend)
		usb_phy_set_suspend(dwc->dotg->otg.phy, 0);

	/*
	 * gadget_driver resume function might require some dwc3-gadget
	 * operations, such as ep_enable. Hence, dwc->lock must be released.
@@ -3141,6 +3153,8 @@ static void dwc3_gadget_suspend_interrupt(struct dwc3 *dwc,
{
	enum dwc3_link_state    next = evtinfo & DWC3_LINK_STATE_MASK;

	dev_dbg(dwc->dev, "%s Entry\n", __func__);

	if (next == DWC3_LINK_STATE_U3) {
		dbg_event(0xFF, "SUSPEND", 0);
		/*
@@ -3160,8 +3174,16 @@ static void dwc3_gadget_suspend_interrupt(struct dwc3 *dwc,
		dwc->gadget_driver->suspend(&dwc->gadget);
		spin_lock(&dwc->lock);

		if (dwc->enable_bus_suspend)
			pm_runtime_put(dwc->dev);
		if (dwc->enable_bus_suspend) {
			/*
			 * Set OTG suspend state and schedule OTG state machine
			 * work
			 */
			dev_dbg(dwc->dev, "%s: Triggering OTG suspend\n",
				__func__);

			usb_phy_set_suspend(dwc->dotg->otg.phy, 1);
		}
	}

	dwc->link_state = next;