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

Commit 7ef8c71c authored by qctecmdr Service's avatar qctecmdr Service Committed by Gerrit - the friendly Code Review server
Browse files

Merge "usb: dwc3: Add support for PM suspend and hibernation in host mode"

parents 0770db49 0949fa1b
Loading
Loading
Loading
Loading
+12 −1
Original line number Diff line number Diff line
@@ -1853,8 +1853,18 @@ static int dwc3_resume(struct device *dev)
	int		ret;

	/* Check if platform glue driver handling PM, if not then handle here */
	if (!dwc3_notify_event(dwc, DWC3_CORE_PM_RESUME_EVENT, 0))
	if (!dwc3_notify_event(dwc, DWC3_CORE_PM_RESUME_EVENT, 0)) {
		/*
		 * If the core was in host mode during suspend, then set the
		 * runtime PM state as active to reflect actual state of device
		 * which is now out of LPM. This allows runtime_suspend later.
		 */
		if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST &&
		    dwc->host_poweroff_in_pm_suspend)
			goto runtime_set_active;

		return 0;
	}

	pinctrl_pm_select_default_state(dev);

@@ -1862,6 +1872,7 @@ static int dwc3_resume(struct device *dev)
	if (ret)
		return ret;

runtime_set_active:
	pm_runtime_disable(dev);
	pm_runtime_set_active(dev);
	pm_runtime_enable(dev);
+6 −0
Original line number Diff line number Diff line
@@ -1334,6 +1334,12 @@ struct dwc3 {
	unsigned int		vbus_active:1;
	/* Indicate if software connect was issued by the usb_gadget_driver */
	unsigned int		softconnect:1;
	/*
	 * If true, PM suspend allowed irrespective of host runtimePM state
	 * and core will power collapse. This also leads to reset-resume of
	 * connected devices on PM resume.
	 */
	bool			host_poweroff_in_pm_suspend;
};

#define INCRX_BURST_MODE 0
+4 −0
Original line number Diff line number Diff line
@@ -35,6 +35,10 @@

#define dbg_setup(ep_num, req) \
	dwc3_dbg_setup(dwc, ep_num, req)

#define dbg_log_string(fmt, ...) \
	ipc_log_string(dwc->dwc_ipc_log_ctxt,\
			"%s: " fmt, __func__, ##__VA_ARGS__)
/**
 * dwc3_gadget_ep_cmd_string - returns endpoint command string
 * @cmd: command code
+130 −44
Original line number Diff line number Diff line
@@ -173,6 +173,13 @@ static const struct usb_irq usb_irq_info[USB_MAX_IRQ] = {
	{"ss_phy_irq", 0},
};

static const char * const gsi_op_strings[] = {
	"EP_CONFIG", "START_XFER", "STORE_DBL_INFO",
	"ENABLE_GSI", "UPDATE_XFER", "RING_DB",
	"END_XFER", "GET_CH_INFO", "PREPARE_TRBS",
	"FREE_TRBS", "SET_CLR_BLOCK_DBL", "CHECK_FOR_SUSP",
	"EP_DISABLE" };

struct dwc3_msm;

struct extcon_nb {
@@ -230,6 +237,7 @@ struct dwc3_msm {
	struct work_struct	restart_usb_work;
	bool			in_restart;
	struct workqueue_struct *dwc3_wq;
	struct workqueue_struct *sm_usb_wq;
	struct delayed_work	sm_work;
	unsigned long		inputs;
	unsigned int		max_power;
@@ -1368,6 +1376,13 @@ static bool gsi_check_ready_to_suspend(struct usb_ep *ep, bool f_suspend)
	return true;
}

static inline const char *gsi_op_to_string(unsigned int op)
{
	if (op < ARRAY_SIZE(gsi_op_strings))
		return gsi_op_strings[op];

	return "Invalid";
}

/**
 * Performs GSI operations or GSI EP related operations.
@@ -1391,41 +1406,36 @@ static int dwc3_msm_gsi_ep_op(struct usb_ep *ep,
	bool block_db, f_suspend;
	unsigned long flags;

	dbg_log_string("%s(%d):%s", ep->name, ep->ep_num, gsi_op_to_string(op));

	switch (op) {
	case GSI_EP_OP_PREPARE_TRBS:
		request = (struct usb_gsi_request *)op_data;
		dev_dbg(mdwc->dev, "EP_OP_PREPARE_TRBS for %s\n", ep->name);
		ret = gsi_prepare_trbs(ep, request);
		break;
	case GSI_EP_OP_FREE_TRBS:
		dev_dbg(mdwc->dev, "EP_OP_FREE_TRBS for %s\n", ep->name);
		request = (struct usb_gsi_request *)op_data;
		gsi_free_trbs(ep, request);
		break;
	case GSI_EP_OP_CONFIG:
		request = (struct usb_gsi_request *)op_data;
		dev_dbg(mdwc->dev, "EP_OP_CONFIG for %s\n", ep->name);
		spin_lock_irqsave(&dwc->lock, flags);
		gsi_configure_ep(ep, request);
		spin_unlock_irqrestore(&dwc->lock, flags);
		break;
	case GSI_EP_OP_STARTXFER:
		dev_dbg(mdwc->dev, "EP_OP_STARTXFER for %s\n", ep->name);
		spin_lock_irqsave(&dwc->lock, flags);
		ret = gsi_startxfer_for_ep(ep);
		spin_unlock_irqrestore(&dwc->lock, flags);
		break;
	case GSI_EP_OP_GET_XFER_IDX:
		dev_dbg(mdwc->dev, "EP_OP_GET_XFER_IDX for %s\n", ep->name);
		ret = gsi_get_xfer_index(ep);
		break;
	case GSI_EP_OP_STORE_DBL_INFO:
		dev_dbg(mdwc->dev, "EP_OP_STORE_DBL_INFO\n");
		request = (struct usb_gsi_request *)op_data;
		gsi_store_ringbase_dbl_info(ep, request);
		break;
	case GSI_EP_OP_ENABLE_GSI:
		dev_dbg(mdwc->dev, "EP_OP_ENABLE_GSI\n");
		gsi_enable(ep);
		break;
	case GSI_EP_OP_GET_CH_INFO:
@@ -1434,36 +1444,29 @@ static int dwc3_msm_gsi_ep_op(struct usb_ep *ep,
		break;
	case GSI_EP_OP_RING_DB:
		request = (struct usb_gsi_request *)op_data;
		dbg_print(0xFF, "RING_DB", 0, ep->name);
		gsi_ring_db(ep, request);
		break;
	case GSI_EP_OP_UPDATEXFER:
		request = (struct usb_gsi_request *)op_data;
		dev_dbg(mdwc->dev, "EP_OP_UPDATEXFER\n");
		spin_lock_irqsave(&dwc->lock, flags);
		ret = gsi_updatexfer_for_ep(ep, request);
		spin_unlock_irqrestore(&dwc->lock, flags);
		break;
	case GSI_EP_OP_ENDXFER:
		request = (struct usb_gsi_request *)op_data;
		dev_dbg(mdwc->dev, "EP_OP_ENDXFER for %s\n", ep->name);
		spin_lock_irqsave(&dwc->lock, flags);
		gsi_endxfer_for_ep(ep);
		spin_unlock_irqrestore(&dwc->lock, flags);
		break;
	case GSI_EP_OP_SET_CLR_BLOCK_DBL:
		block_db = *((bool *)op_data);
		dev_dbg(mdwc->dev, "EP_OP_SET_CLR_BLOCK_DBL %d\n",
						block_db);
		gsi_set_clear_dbell(ep, block_db);
		break;
	case GSI_EP_OP_CHECK_FOR_SUSPEND:
		dev_dbg(mdwc->dev, "EP_OP_CHECK_FOR_SUSPEND\n");
		f_suspend = *((bool *)op_data);
		ret = gsi_check_ready_to_suspend(ep, f_suspend);
		break;
	case GSI_EP_OP_DISABLE:
		dev_dbg(mdwc->dev, "EP_OP_DISABLE\n");
		ret = ep->ops->disable(ep);
		break;
	default:
@@ -1695,7 +1698,7 @@ static int msm_dwc3_usbdev_notify(struct notifier_block *self,
	}

	mdwc->hc_died = true;
	schedule_delayed_work(&mdwc->sm_work, 0);
	queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, 0);
	return 0;
}

@@ -2056,15 +2059,34 @@ static void dwc3_msm_power_collapse_por(struct dwc3_msm *mdwc)
	if (ret)
		dev_err(mdwc->dev, "%s: dwc3_core init failed (%d)\n",
							__func__, ret);

	/* Get initial P3 status and enable IN_P3 event */
	if (dwc3_is_usb31(dwc))
		val = dwc3_msm_read_reg_field(mdwc->base,
			DWC31_LINK_GDBGLTSSM,
			DWC3_GDBGLTSSM_LINKSTATE_MASK);
	else
		val = dwc3_msm_read_reg_field(mdwc->base,
			DWC3_GDBGLTSSM, DWC3_GDBGLTSSM_LINKSTATE_MASK);
	atomic_set(&mdwc->in_p3, val == DWC3_LINK_STATE_U3);
	dwc3_msm_write_reg_field(mdwc->base, PWR_EVNT_IRQ_MASK_REG,
				PWR_EVNT_POWERDOWN_IN_P3_MASK, 1);

	/* Set the core in host mode if it was in host mode during pm_suspend */
	if (mdwc->in_host_mode) {
		dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST);
		dwc3_en_sleep_mode(dwc);
	}

}

static int dwc3_msm_prepare_suspend(struct dwc3_msm *mdwc)
static int dwc3_msm_prepare_suspend(struct dwc3_msm *mdwc, bool ignore_p3_state)
{
	unsigned long timeout;
	u32 reg = 0;

	if ((mdwc->in_host_mode || mdwc->in_device_mode)
			&& dwc3_msm_is_superspeed(mdwc) && !mdwc->in_restart) {
	if (!ignore_p3_state && ((mdwc->in_host_mode || mdwc->in_device_mode)
			&& dwc3_msm_is_superspeed(mdwc) && !mdwc->in_restart)) {
		if (!atomic_read(&mdwc->in_p3)) {
			dev_err(mdwc->dev, "Not in P3,aborting LPM sequence\n");
			return -EBUSY;
@@ -2263,7 +2285,7 @@ static int dwc3_msm_update_bus_bw(struct dwc3_msm *mdwc, enum bus_vote bv)
	return ret;
}

static int dwc3_msm_suspend(struct dwc3_msm *mdwc)
static int dwc3_msm_suspend(struct dwc3_msm *mdwc, bool force_power_collapse)
{
	int ret;
	struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);
@@ -2325,7 +2347,7 @@ static int dwc3_msm_suspend(struct dwc3_msm *mdwc)
		return -EBUSY;
	}

	ret = dwc3_msm_prepare_suspend(mdwc);
	ret = dwc3_msm_prepare_suspend(mdwc, force_power_collapse);
	if (ret) {
		mutex_unlock(&mdwc->suspend_resume_mutex);
		return ret;
@@ -2403,8 +2425,8 @@ static int dwc3_msm_suspend(struct dwc3_msm *mdwc)
	clk_disable_unprepare(mdwc->xo_clk);

	/* Perform controller power collapse */
	if (!mdwc->in_host_mode && (!mdwc->in_device_mode ||
					mdwc->in_restart)) {
	if (!(mdwc->in_host_mode || mdwc->in_device_mode) ||
	      mdwc->in_restart || force_power_collapse) {
		mdwc->lpm_flags |= MDWC3_POWER_COLLAPSE;
		dev_dbg(mdwc->dev, "%s: power collapse\n", __func__);
		dwc3_msm_config_gdsc(mdwc, 0);
@@ -2564,8 +2586,6 @@ static int dwc3_msm_resume(struct dwc3_msm *mdwc)

	/* Recover from controller power collapse */
	if (mdwc->lpm_flags & MDWC3_POWER_COLLAPSE) {
		u32 tmp;

		if (mdwc->iommu_map) {
			ret = __depr_arm_iommu_attach_device(mdwc->dev,
					mdwc->iommu_map);
@@ -2580,18 +2600,6 @@ static int dwc3_msm_resume(struct dwc3_msm *mdwc)

		dwc3_msm_power_collapse_por(mdwc);

		/* Get initial P3 status and enable IN_P3 event */
		if (dwc3_is_usb31(dwc))
			tmp = dwc3_msm_read_reg_field(mdwc->base,
				DWC31_LINK_GDBGLTSSM,
				DWC3_GDBGLTSSM_LINKSTATE_MASK);
		else
			tmp = dwc3_msm_read_reg_field(mdwc->base,
				DWC3_GDBGLTSSM, DWC3_GDBGLTSSM_LINKSTATE_MASK);
		atomic_set(&mdwc->in_p3, tmp == DWC3_LINK_STATE_U3);
		dwc3_msm_write_reg_field(mdwc->base, PWR_EVNT_IRQ_MASK_REG,
					PWR_EVNT_POWERDOWN_IN_P3_MASK, 1);

		mdwc->lpm_flags &= ~MDWC3_POWER_COLLAPSE;
	}

@@ -2674,7 +2682,7 @@ static void dwc3_ext_event_notify(struct dwc3_msm *mdwc)
		clear_bit(B_SUSPEND, &mdwc->inputs);
	}

	schedule_delayed_work(&mdwc->sm_work, 0);
	queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, 0);
}

static void dwc3_resume_work(struct work_struct *w)
@@ -2727,6 +2735,13 @@ static void dwc3_resume_work(struct work_struct *w)
					ORIENTATION_CC2 : ORIENTATION_CC1;

		dbg_event(0xFF, "cc_state", mdwc->typec_orientation);

		ret = extcon_get_property(edev, extcon_id,
				EXTCON_PROP_USB_TYPEC_MED_HIGH_CURRENT, &val);
		if (!ret)
			dwc->gadget.is_selfpowered = val.intval;
		else
			dwc->gadget.is_selfpowered = 0;
	}

	/*
@@ -3372,6 +3387,18 @@ static int dwc3_msm_probe(struct platform_device *pdev)
		return -ENOMEM;
	}

	/*
	 * Create freezable workqueue for sm_work so that it gets scheduled only
	 * after pm_resume has happened completely. This helps in avoiding race
	 * conditions between xhci_plat_resume and xhci_runtime_resume; and also
	 * between hcd disconnect and xhci_resume.
	 */
	mdwc->sm_usb_wq = create_freezable_workqueue("k_sm_usb");
	if (!mdwc->sm_usb_wq) {
		destroy_workqueue(mdwc->dwc3_wq);
		return -ENOMEM;
	}

	/* Get all clks and gdsc reference */
	ret = dwc3_msm_get_clk_gdsc(mdwc);
	if (ret) {
@@ -3623,6 +3650,12 @@ static int dwc3_msm_probe(struct platform_device *pdev)
		mdwc->pm_qos_latency = 0;
	}

	if (of_property_read_bool(node, "qcom,host-poweroff-in-pm-suspend")) {
		dwc->host_poweroff_in_pm_suspend = true;
		dev_dbg(mdwc->dev, "%s: Core power collapse on host PM suspend\n",
								__func__);
	}

	mutex_init(&mdwc->suspend_resume_mutex);

	if (of_property_read_bool(node, "extcon")) {
@@ -3661,6 +3694,7 @@ static int dwc3_msm_probe(struct platform_device *pdev)
	}
	of_platform_depopulate(&pdev->dev);
err:
	destroy_workqueue(mdwc->sm_usb_wq);
	destroy_workqueue(mdwc->dwc3_wq);
	return ret;
}
@@ -3741,6 +3775,9 @@ static int dwc3_msm_remove(struct platform_device *pdev)
		__depr_arm_iommu_release_mapping(mdwc->iommu_map);
	}

	destroy_workqueue(mdwc->sm_usb_wq);
	destroy_workqueue(mdwc->dwc3_wq);

	return 0;
}

@@ -4372,7 +4409,7 @@ static void dwc3_otg_sm_work(struct work_struct *w)
	}

	if (work)
		schedule_delayed_work(&mdwc->sm_work, delay);
		queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, delay);

ret:
	return;
@@ -4381,6 +4418,7 @@ static void dwc3_otg_sm_work(struct work_struct *w)
#ifdef CONFIG_PM_SLEEP
static int dwc3_msm_pm_suspend(struct device *dev)
{
	int ret = 0;
	struct dwc3_msm *mdwc = dev_get_drvdata(dev);
	struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);

@@ -4388,6 +4426,12 @@ static int dwc3_msm_pm_suspend(struct device *dev)
	dbg_event(0xFF, "PM Sus", 0);

	flush_workqueue(mdwc->dwc3_wq);

	/*
	 * Check if pm_suspend can proceed irrespective of runtimePM state of
	 * host.
	 */
	if (!dwc->host_poweroff_in_pm_suspend || !mdwc->in_host_mode) {
		if (!atomic_read(&dwc->in_lpm)) {
			dev_err(mdwc->dev, "Abort PM suspend!! (USB is outside LPM)\n");
			return -EBUSY;
@@ -4398,6 +4442,26 @@ static int dwc3_msm_pm_suspend(struct device *dev)
		return 0;
	}

	/*
	 * PHYs also need to be power collapsed, so call notify_disconnect
	 * before suspend to ensure it.
	 */
	usb_phy_notify_disconnect(mdwc->hs_phy, USB_SPEED_HIGH);
	mdwc->hs_phy->flags &= ~PHY_HOST_MODE;
	usb_phy_notify_disconnect(mdwc->ss_phy, USB_SPEED_SUPER);
	mdwc->ss_phy->flags &= ~PHY_HOST_MODE;

	/*
	 * Power collapse the core. Hence call dwc3_msm_suspend with
	 * 'force_power_collapse' set to 'true'.
	 */
	ret = dwc3_msm_suspend(mdwc, true);
	if (!ret)
		atomic_set(&mdwc->pm_suspended, 1);

	return ret;
}

static int dwc3_msm_pm_resume(struct device *dev)
{
	struct dwc3_msm *mdwc = dev_get_drvdata(dev);
@@ -4410,6 +4474,28 @@ static int dwc3_msm_pm_resume(struct device *dev)
	flush_workqueue(mdwc->dwc3_wq);
	atomic_set(&mdwc->pm_suspended, 0);

	if (!dwc->host_poweroff_in_pm_suspend || !mdwc->in_host_mode) {
		/* kick in otg state machine */
		queue_work(mdwc->dwc3_wq, &mdwc->resume_work);

		return 0;
	}

	/* Resume dwc to avoid unclocked access by xhci_plat_resume */
	dwc3_msm_resume(mdwc);
	pm_runtime_disable(dev);
	pm_runtime_set_active(dev);
	pm_runtime_enable(dev);

	/* Restore PHY flags if hibernated in host mode */
	mdwc->hs_phy->flags |= PHY_HOST_MODE;
	usb_phy_notify_connect(mdwc->hs_phy, USB_SPEED_HIGH);
	if (dwc->maximum_speed >= USB_SPEED_SUPER) {
		mdwc->ss_phy->flags |= PHY_HOST_MODE;
		usb_phy_notify_connect(mdwc->ss_phy,
					USB_SPEED_SUPER);
	}

	/* kick in otg state machine */
	queue_work(mdwc->dwc3_wq, &mdwc->resume_work);

@@ -4437,7 +4523,7 @@ static int dwc3_msm_runtime_suspend(struct device *dev)
	dev_dbg(dev, "DWC3-msm runtime suspend\n");
	dbg_event(0xFF, "RT Sus", 0);

	return dwc3_msm_suspend(mdwc);
	return dwc3_msm_suspend(mdwc, false);
}

static int dwc3_msm_runtime_resume(struct device *dev)
+6 −0
Original line number Diff line number Diff line
@@ -824,6 +824,7 @@ static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep)
{
	struct dwc3_request		*req;

	dbg_log_string("START for %s(%d)", dep->name, dep->number);
	dwc3_stop_active_transfer(dwc, dep->number, true);

	/* - giveback all requests to gadget driver */
@@ -838,12 +839,14 @@ static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep)

		dwc3_gadget_giveback(dep, req, -ESHUTDOWN);
	}
	dbg_log_string("DONE for %s(%d)", dep->name, dep->number);
}

static void dwc3_stop_active_transfers(struct dwc3 *dwc)
{
	u32 epnum;

	dbg_log_string("START");
	for (epnum = 2; epnum < DWC3_ENDPOINTS_NUM; epnum++) {
		struct dwc3_ep *dep;

@@ -860,6 +863,7 @@ static void dwc3_stop_active_transfers(struct dwc3 *dwc)

		dwc3_remove_requests(dwc, dep);
	}
	dbg_log_string("DONE");
}

/**
@@ -3073,6 +3077,8 @@ void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force)
			dep->flags |= DWC3_EP_END_TRANSFER_PENDING;
		udelay(100);
	}
	dbg_log_string("%s(%d): endxfer ret:%d)",
			dep->name, dep->number, ret);
}

static void dwc3_clear_stall_all_ep(struct dwc3 *dwc)
Loading