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

Commit ecb5fa75 authored by Jack Pham's avatar Jack Pham Committed by Gerrit - the friendly Code Review server
Browse files

usb: dwc3-msm: Handle erratic event with full POR sequence



The current handling of erratic errors that occur in peripheral
mode does so by scheduling a block reset. Since this occurs
asynchronously, additional changes needed to be made to ensure
the device controller would not generate spurious events or
try to access any registers while the reset was in progress
and the clocks were off. Even after that, the controller would
still not be functional until a cable reconnect since the block
reset routine did not do anything to re-establish the peripheral
session.

Instead, improve the erratic error handling to simulate a
cable disconnection-reconnection sequence. The forced disconnect
should solve issues related to post-error events as well as
prevent any function drivers from continuing to issue endpoint
commands. This is followed with the controller entering low
power mode. If the cable is still physically connected, then
invoke a reconnection which does the full reset and power-on
initialization sequence that is more complete than the current
block reset. The USB session would then get re-enumerated.

Change-Id: I45ccb30ff12064e5410d45c423a404d97b299022
Signed-off-by: default avatarJack Pham <jackp@codeaurora.org>
parent c950223d
Loading
Loading
Loading
Loading
+34 −86
Original line number Original line Diff line number Diff line
@@ -167,7 +167,7 @@ struct dwc3_msm {
	int			hs_phy_irq;
	int			hs_phy_irq;
	struct delayed_work	resume_work;
	struct delayed_work	resume_work;
	struct work_struct	restart_usb_work;
	struct work_struct	restart_usb_work;
	struct work_struct	usb_block_reset_work;
	bool			in_restart;
	struct dwc3_charger	charger;
	struct dwc3_charger	charger;
	struct usb_phy		*otg_xceiv;
	struct usb_phy		*otg_xceiv;
	struct delayed_work	chg_work;
	struct delayed_work	chg_work;
@@ -203,7 +203,6 @@ struct dwc3_msm {
#define MDWC3_PHY_REF_AND_CORECLK_OFF	\
#define MDWC3_PHY_REF_AND_CORECLK_OFF	\
			(MDWC3_PHY_REF_CLK_OFF | MDWC3_CORECLK_OFF)
			(MDWC3_PHY_REF_CLK_OFF | MDWC3_CORECLK_OFF)


	u32 gctl_val;
	u32 qscratch_ctl_val;
	u32 qscratch_ctl_val;
	dev_t ext_chg_dev;
	dev_t ext_chg_dev;
	struct cdev ext_chg_cdev;
	struct cdev ext_chg_cdev;
@@ -879,11 +878,15 @@ void dwc3_tx_fifo_resize_request(struct usb_ep *ep, bool qdss_enabled)
}
}
EXPORT_SYMBOL(dwc3_tx_fifo_resize_request);
EXPORT_SYMBOL(dwc3_tx_fifo_resize_request);


static void dwc3_resume_work(struct work_struct *w);

static void dwc3_restart_usb_work(struct work_struct *w)
static void dwc3_restart_usb_work(struct work_struct *w)
{
{
	struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm,
	struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm,
						restart_usb_work);
						restart_usb_work);
	struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);
	struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);
	enum dwc3_chg_type chg_type;
	unsigned timeout = 50;


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


@@ -892,21 +895,41 @@ static void dwc3_restart_usb_work(struct work_struct *w)
		return;
		return;
	}
	}


	/* guard against concurrent VBUS handling */
	mdwc->in_restart = true;

	if (!mdwc->ext_xceiv.bsv) {
	if (!mdwc->ext_xceiv.bsv) {
		dev_dbg(mdwc->dev, "%s bailing out in disconnect\n", __func__);
		dev_dbg(mdwc->dev, "%s bailing out in disconnect\n", __func__);
		dwc->err_evt_seen = false;
		mdwc->in_restart = false;
		return;
		return;
	}
	}


	dbg_event(0xFF, "RestartUSB", 0);
	dbg_event(0xFF, "RestartUSB", 0);
	chg_type = mdwc->charger.chg_type;


	/* Reset active USB connection */
	/* Reset active USB connection */
	mdwc->ext_xceiv.bsv = false;
	mdwc->ext_xceiv.bsv = false;
	queue_delayed_work(system_nrt_wq, &mdwc->resume_work, 0);
	dwc3_resume_work(&mdwc->resume_work.work);

	/* Make sure disconnect is processed before sending connect */
	/* Make sure disconnect is processed before sending connect */
	flush_delayed_work(&mdwc->resume_work);
	while (--timeout && !pm_runtime_suspended(mdwc->dev))
		msleep(20);


	if (!timeout) {
		dev_warn(mdwc->dev, "Not in LPM after disconnect, forcing suspend...\n");
		pm_runtime_suspend(mdwc->dev);
	}

	/* Force reconnect only if cable is still connected */
	if (mdwc->vbus_active) {
		mdwc->ext_xceiv.bsv = true;
		mdwc->ext_xceiv.bsv = true;
	queue_delayed_work(system_nrt_wq, &mdwc->resume_work, 0);
		mdwc->charger.chg_type = chg_type;
		dwc3_resume_work(&mdwc->resume_work.work);
	}

	dwc->err_evt_seen = false;
	mdwc->in_restart = false;
}
}


/**
/**
@@ -1140,11 +1163,8 @@ static void dwc3_msm_notify_event(struct dwc3 *dwc, unsigned event)
		reg |= DWC3_GCTL_CORESOFTRESET;
		reg |= DWC3_GCTL_CORESOFTRESET;
		dwc3_msm_write_reg(mdwc->base, DWC3_GCTL, reg);
		dwc3_msm_write_reg(mdwc->base, DWC3_GCTL, reg);


		/*
		/* restart USB which performs full reset and reconnect */
		 * schedule work for doing block reset for recovery from erratic
		queue_work(system_nrt_wq, &mdwc->restart_usb_work);
		 * error event.
		 */
		queue_work(system_nrt_wq, &mdwc->usb_block_reset_work);
		break;
		break;
	case DWC3_CONTROLLER_RESET_EVENT:
	case DWC3_CONTROLLER_RESET_EVENT:
		dev_dbg(mdwc->dev, "DWC3_CONTROLLER_RESET_EVENT received\n");
		dev_dbg(mdwc->dev, "DWC3_CONTROLLER_RESET_EVENT received\n");
@@ -1220,24 +1240,6 @@ static void dwc3_msm_block_reset(struct dwc3_ext_xceiv *xceiv, bool core_reset)


}
}


static void dwc3_block_reset_usb_work(struct work_struct *w)
{
	struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm,
						usb_block_reset_work);
	struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);
	unsigned long flags;

	dev_dbg(mdwc->dev, "%s\n", __func__);
	dwc3_msm_block_reset(&mdwc->ext_xceiv, true);
	if (mdwc->ext_xceiv.bsv) {
		dbg_event(0xFF, "BR EnEVT", 0);
		dwc3_gadget_enable_irq(dwc);
	}
	spin_lock_irqsave(&dwc->lock, flags);
	dwc->err_evt_seen = 0;
	spin_unlock_irqrestore(&dwc->lock, flags);
}

static void dwc3_chg_enable_secondary_det(struct dwc3_msm *mdwc)
static void dwc3_chg_enable_secondary_det(struct dwc3_msm *mdwc)
{
{
	u32 chg_ctrl;
	u32 chg_ctrl;
@@ -1472,6 +1474,7 @@ static void dwc3_msm_power_collapse_por(struct dwc3_msm *mdwc)
	dwc3_core_init(dwc);
	dwc3_core_init(dwc);
	/* Re-configure event buffers */
	/* Re-configure event buffers */
	dwc3_event_buffers_setup(dwc);
	dwc3_event_buffers_setup(dwc);
	dwc3_msm_notify_event(dwc, DWC3_CONTROLLER_POST_INITIALIZATION_EVENT);
}
}


static int dwc3_msm_prepare_suspend(struct dwc3_msm *mdwc)
static int dwc3_msm_prepare_suspend(struct dwc3_msm *mdwc)
@@ -1690,9 +1693,6 @@ static int dwc3_msm_suspend(struct dwc3_msm *mdwc)
	/* make sure above writes are completed before turning off clocks */
	/* make sure above writes are completed before turning off clocks */
	wmb();
	wmb();


	/* Save value of GCTL to rewrite upon resume */
	mdwc->gctl_val = dwc3_msm_read_reg(mdwc->base, DWC3_GCTL);

	/* Perform controller power collapse */
	/* Perform controller power collapse */
	if (!host_bus_suspend && !device_bus_suspend && mdwc->power_collapse) {
	if (!host_bus_suspend && !device_bus_suspend && mdwc->power_collapse) {
		mdwc->lpm_flags |= MDWC3_POWER_COLLAPSE;
		mdwc->lpm_flags |= MDWC3_POWER_COLLAPSE;
@@ -1865,8 +1865,6 @@ static int dwc3_msm_resume(struct dwc3_msm *mdwc)
		mdwc->lpm_flags &= ~MDWC3_POWER_COLLAPSE;
		mdwc->lpm_flags &= ~MDWC3_POWER_COLLAPSE;
	}
	}


	dwc3_msm_write_reg(mdwc->base, DWC3_GCTL, mdwc->gctl_val);

	atomic_set(&dwc->in_lpm, 0);
	atomic_set(&dwc->in_lpm, 0);


	msm_bam_notify_lpm_resume(DWC3_CTRL);
	msm_bam_notify_lpm_resume(DWC3_CTRL);
@@ -1928,43 +1926,6 @@ static void dwc3_wait_for_ext_chg_done(struct dwc3_msm *mdwc)


}
}


static void dwc3_flush_event_buffers(struct dwc3_msm *mdwc)
{
	struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);
	int i;
	unsigned long flags;

	/* Only disable/flush all events when cable is disconnected */
	if (mdwc->ext_xceiv.bsv)
		return;

	/* Disable all events on cable disconnect */
	dbg_event(0xFF, "Dis EVT", 0);
	dwc3_gadget_disable_irq(dwc);

	/* Skip remaining events on disconnect */
	spin_lock_irqsave(&dwc->lock, flags);
	for (i = 0; i < dwc->num_event_buffers; i++) {
		struct dwc3_event_buffer *evt;
		evt = dwc->ev_buffs[i];
		evt->lpos = (evt->lpos + evt->count) %
			DWC3_EVENT_BUFFERS_SIZE;
		evt->count = 0;
		evt->flags &= ~DWC3_EVENT_PENDING;
	}
	spin_unlock_irqrestore(&dwc->lock, flags);

	/*
	 * If there is a pending block reset due to erratic event,
	 * wait for it to complete
	 */
	if (dwc->err_evt_seen) {
		dbg_event(0xFF, "Flush BR", 0);
		flush_work(&mdwc->usb_block_reset_work);
		dwc->err_evt_seen = 0;
	}
}

static void dwc3_resume_work(struct work_struct *w)
static void dwc3_resume_work(struct work_struct *w)
{
{
	struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm,
	struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm,
@@ -1978,8 +1939,6 @@ static void dwc3_resume_work(struct work_struct *w)
		dbg_event(0xFF, "RWrk !lpm", 0);
		dbg_event(0xFF, "RWrk !lpm", 0);
		if (mdwc->otg_xceiv) {
		if (mdwc->otg_xceiv) {
			dwc3_wait_for_ext_chg_done(mdwc);
			dwc3_wait_for_ext_chg_done(mdwc);
			/* Handle erratic events during cable disconnect */
			dwc3_flush_event_buffers(mdwc);
			mdwc->ext_xceiv.notify_ext_events(mdwc->otg_xceiv->otg,
			mdwc->ext_xceiv.notify_ext_events(mdwc->otg_xceiv->otg,
							DWC3_EVENT_XCEIV_STATE);
							DWC3_EVENT_XCEIV_STATE);
		}
		}
@@ -1994,11 +1953,6 @@ static void dwc3_resume_work(struct work_struct *w)
		dbg_event(0xFF, "RWrk !PMSus", mdwc->otg_xceiv ? 1 : 0);
		dbg_event(0xFF, "RWrk !PMSus", mdwc->otg_xceiv ? 1 : 0);
		pm_runtime_get_sync(mdwc->dev);
		pm_runtime_get_sync(mdwc->dev);
		if (mdwc->otg_xceiv) {
		if (mdwc->otg_xceiv) {
			/*
			 * Handle erratic events during bus suspend and cable
			 * disconnect
			 */
			dwc3_flush_event_buffers(mdwc);
			mdwc->ext_xceiv.notify_ext_events(mdwc->otg_xceiv->otg,
			mdwc->ext_xceiv.notify_ext_events(mdwc->otg_xceiv->otg,
							DWC3_EVENT_PHY_RESUME);
							DWC3_EVENT_PHY_RESUME);
		} else if (mdwc->scope == POWER_SUPPLY_SCOPE_SYSTEM) {
		} else if (mdwc->scope == POWER_SUPPLY_SCOPE_SYSTEM) {
@@ -2302,7 +2256,8 @@ static int dwc3_msm_power_set_property_usb(struct power_supply *psy,
	/* Process PMIC notification in PRESENT prop */
	/* Process PMIC notification in PRESENT prop */
	case POWER_SUPPLY_PROP_PRESENT:
	case POWER_SUPPLY_PROP_PRESENT:
		dev_dbg(mdwc->dev, "%s: notify xceiv event\n", __func__);
		dev_dbg(mdwc->dev, "%s: notify xceiv event\n", __func__);
		if (mdwc->otg_xceiv && !mdwc->ext_inuse) {
		mdwc->vbus_active = val->intval;
		if (mdwc->otg_xceiv && !mdwc->ext_inuse && !mdwc->in_restart) {
			if (mdwc->ext_xceiv.bsv == val->intval)
			if (mdwc->ext_xceiv.bsv == val->intval)
				break;
				break;


@@ -2315,7 +2270,6 @@ static int dwc3_msm_power_set_property_usb(struct power_supply *psy,
			queue_delayed_work(system_nrt_wq,
			queue_delayed_work(system_nrt_wq,
							&mdwc->resume_work, 12);
							&mdwc->resume_work, 12);
		}
		}
		mdwc->vbus_active = val->intval;
		break;
		break;
	case POWER_SUPPLY_PROP_ONLINE:
	case POWER_SUPPLY_PROP_ONLINE:
		mdwc->online = val->intval;
		mdwc->online = val->intval;
@@ -2849,7 +2803,6 @@ static int dwc3_msm_probe(struct platform_device *pdev)
	INIT_DELAYED_WORK(&mdwc->chg_work, dwc3_chg_detect_work);
	INIT_DELAYED_WORK(&mdwc->chg_work, dwc3_chg_detect_work);
	INIT_DELAYED_WORK(&mdwc->resume_work, dwc3_resume_work);
	INIT_DELAYED_WORK(&mdwc->resume_work, dwc3_resume_work);
	INIT_WORK(&mdwc->restart_usb_work, dwc3_restart_usb_work);
	INIT_WORK(&mdwc->restart_usb_work, dwc3_restart_usb_work);
	INIT_WORK(&mdwc->usb_block_reset_work, dwc3_block_reset_usb_work);
	INIT_WORK(&mdwc->id_work, dwc3_id_work);
	INIT_WORK(&mdwc->id_work, dwc3_id_work);
	INIT_DELAYED_WORK(&mdwc->init_adc_work, dwc3_init_adc_work);
	INIT_DELAYED_WORK(&mdwc->init_adc_work, dwc3_init_adc_work);
	init_completion(&mdwc->ext_chg_wait);
	init_completion(&mdwc->ext_chg_wait);
@@ -3487,11 +3440,6 @@ static int dwc3_msm_pm_resume(struct device *dev)


		/* Let OTG know about resume event and update pm_count */
		/* Let OTG know about resume event and update pm_count */
		if (mdwc->otg_xceiv) {
		if (mdwc->otg_xceiv) {
			/*
			 * Handle erratic events on bus suspend, PM suspend
			 * and cable disconnect
			 */
			dwc3_flush_event_buffers(mdwc);
			mdwc->ext_xceiv.notify_ext_events(mdwc->otg_xceiv->otg,
			mdwc->ext_xceiv.notify_ext_events(mdwc->otg_xceiv->otg,
							DWC3_EVENT_PHY_RESUME);
							DWC3_EVENT_PHY_RESUME);
			mdwc->ext_xceiv.notify_ext_events(mdwc->otg_xceiv->otg,
			mdwc->ext_xceiv.notify_ext_events(mdwc->otg_xceiv->otg,