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

Commit e8ec1217 authored by Manu Gautam's avatar Manu Gautam
Browse files

USB: PHY: msm: Improve power management handling for OTG



Driver currently performs runtime-pm usage_count update only once
as part of pm_runtime_resume. This allows driver to invoke
pm_runtime_resume as many times without pm usage_count going
beyond '1'. As these calls are not ref counted, for suspend
driver uses pm_runtime_put_noidle API when it figures out that all
LPM conditions are met. The _put_noidle API also ensures that
pm usage count would never be '-1'. This works fine as long as
only PHY driver is controlling runtime PM states of the device,
but this design starts giving problems when PM core also performs
_get/_put around system wide PM suspend/resume transitions of device.
And it also makes it impossible to define children of PHY devices
which can use runtime PM APIs to manage their devices' power states.
To simplify this, change runtime PM design to fix these issues
and also define UDC and EHCI devices as children of PHY driver.
This also allows cleaning up various hacks which are now redundant
due to this design change.

Change-Id: Ifee2f411d7192000395fea6fad15c640b9684e3f
Signed-off-by: default avatarManu Gautam <mgautam@codeaurora.org>
parent 66c068c9
Loading
Loading
Loading
Loading
+0 −5
Original line number Diff line number Diff line
@@ -135,10 +135,6 @@ Optional properties :
	to supply voltage to the D+ line during VDD minimization and peripheral
	bus suspend. If not exists, then VDD minimization will not be allowed
	during peripheral bus suspend.
- qcom,hsusb-otg-rw-during-lpm-workaround: If present, indicates that remote-
	wakeup during USB low-power mode SW workaround will be applied. When
	this workaround is applied, the PHCD and the FPR bits are written
	to the PORTSC register in the same register write operation.
- qcom,ahb-async-bridge-bypass: If present, indicates that enable AHB2AHB By Pass
	mode with device controller for better throughput. With this mode, USB Core
	runs using PNOC clock and synchronous to it. Hence it is must to have proper
@@ -210,7 +206,6 @@ Example HSUSB OTG controller device node :
		pinctrl-0 = <&vddmin_act>;
		pinctrl-0 = <&vddmin_sus>;
		qcom,hsusb-otg-vddmin-gpio = <&pm8019_mpps 6 0>;
		qcom,hsusb-otg-rw-during-lpm-workaround = <1>;
		qcom,disable-retention-with-vdd-min;
		qcom,usbin-vadc = <&pm8226_vadc>;
		qcom,usbid-gpio = <&msm_gpio 110 0>;
+1 −31
Original line number Diff line number Diff line
@@ -230,21 +230,6 @@ static void ci13xxx_msm_notify_event(struct ci13xxx *udc, unsigned event)
	}
}

static bool ci13xxx_cancel_pending_suspend(struct ci13xxx *udc)
{
	struct msm_otg *otg;

	if (udc == NULL)
		return false;

	if (udc->transceiver == NULL)
		return false;

	otg = container_of(udc->transceiver, struct msm_otg, phy);

	return cancel_delayed_work_sync(&otg->suspend_work);
}

static bool ci13xxx_msm_in_lpm(struct ci13xxx *udc)
{
	struct msm_otg *otg;
@@ -260,20 +245,6 @@ static bool ci13xxx_msm_in_lpm(struct ci13xxx *udc)
	return (atomic_read(&otg->in_lpm) != 0);
}

static void ci13xxx_msm_set_fpr_flag(struct ci13xxx *udc)
{
	struct msm_otg *otg;

	if (udc == NULL)
		return;

	if (udc->transceiver == NULL)
		return;

	otg = container_of(udc->transceiver, struct msm_otg, phy);

	atomic_set(&otg->set_fpr_with_lpm_exit, 1);
}

static irqreturn_t ci13xxx_msm_resume_irq(int irq, void *data)
{
@@ -296,9 +267,7 @@ static struct ci13xxx_udc_driver ci13xxx_msm_udc_driver = {
				  CI13XXX_DISABLE_STREAMING,
	.nz_itc			= 0,
	.notify_event		= ci13xxx_msm_notify_event,
	.cancel_pending_suspend = ci13xxx_cancel_pending_suspend,
	.in_lpm                 = ci13xxx_msm_in_lpm,
	.set_fpr_flag           = ci13xxx_msm_set_fpr_flag,
};

static int ci13xxx_msm_install_wake_gpio(struct platform_device *pdev,
@@ -466,6 +435,7 @@ static int ci13xxx_msm_probe(struct platform_device *pdev)
							(unsigned long)NULL);

	pm_runtime_no_callbacks(&pdev->dev);
	pm_runtime_set_active(&pdev->dev);
	pm_runtime_enable(&pdev->dev);

	return 0;
+12 −98
Original line number Diff line number Diff line
@@ -1653,7 +1653,6 @@ static int ci13xxx_wakeup(struct usb_gadget *_gadget)
{
	struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget);
	unsigned long flags;
	bool skip_fpr = false;
	int ret = 0;

	trace();
@@ -1666,21 +1665,7 @@ static int ci13xxx_wakeup(struct usb_gadget *_gadget)
	}
	spin_unlock_irqrestore(udc->lock, flags);

	/* Make sure phy driver is done with its bus suspend handling */
	if (udc->udc_driver->cancel_pending_suspend)
		udc->udc_driver->cancel_pending_suspend(udc);

	if ((udc->udc_driver->in_lpm != NULL) &&
	    (udc->udc_driver->in_lpm(udc))) {
		if (udc->udc_driver->set_fpr_flag) {
			/* When USB HW is in low-power mode we set a flag
			 * for the OTG layer to set the FPR bit during the
			 * low-power mode mode exit sequence.
			 */
			udc->udc_driver->set_fpr_flag(udc);
			skip_fpr = true;
		}
	}
	pm_runtime_get_sync(&_gadget->dev);

	udc->udc_driver->notify_event(udc,
		CI13XXX_CONTROLLER_REMOTE_WAKEUP_EVENT);
@@ -1689,15 +1674,15 @@ static int ci13xxx_wakeup(struct usb_gadget *_gadget)
		usb_phy_set_suspend(udc->transceiver, 0);

	spin_lock_irqsave(udc->lock, flags);
	if (!skip_fpr) {
	if (!hw_cread(CAP_PORTSC, PORTSC_SUSP)) {
		ret = -EINVAL;
		dbg_trace("port is not suspended\n");
		pm_runtime_put_sync(&_gadget->dev);
		goto out;
	}
	hw_cwrite(CAP_PORTSC, PORTSC_FPR, PORTSC_FPR);
	}

	pm_runtime_put_sync(&_gadget->dev);
out:
	spin_unlock_irqrestore(udc->lock, flags);
	return ret;
@@ -2909,70 +2894,6 @@ delegate:
	}
}

/**
 * ci13xxx_exit_lpm: Exit controller from low power mode
 * @udc: UDC descriptor
 * @allow_sleep: Are we in preemptible context or not.
 *
 * This function check if controller is in low power mode and if so, exit from
 * the low power mode.
 *
 * In case the controller is in low power mode, registers are not accessible,
 * therefore this function can be used as utility function to ensure exit from
 * low power mode before do registers read/write operations.
 *
 * Return 0 if not in low power mode and read/write operations are safe.
 * Return -EAGAIN in case exit from low power mode was initiated, but it is not
 * safe yet to use read/write operations against the controller registers.
 */
static int ci13xxx_exit_lpm(struct ci13xxx *udc, bool allow_sleep)
{
	if (!udc)
		return -ENODEV;

	/* Make sure phy driver is done with its bus suspend handling */
	if (udc->udc_driver->cancel_pending_suspend && allow_sleep)
		udc->udc_driver->cancel_pending_suspend(udc);

	/* Check if the controller is in low power mode state */
	if (udc->udc_driver->in_lpm &&
	    udc->udc_driver->in_lpm(udc) &&
	    udc->transceiver) {

		dev_dbg(udc->transceiver->dev,
			"%s: Exit from low power mode\n",
			__func__);

		/*
		 * Resume of the controller may be done
		 * asynchronically in deffered context.
		 */
		usb_phy_set_suspend(udc->transceiver, 0);

		/*
		 * Wait for controller resume to finish in case of non atomic
		 * context or return EAGAIN otherwise.
		 */
		if (allow_sleep) {
			while (udc->udc_driver->in_lpm(udc))
				usleep_range(1, 10);
		} else {
			/*
			 * Return EAGAIN only in case controller resume
			 * was done asynchronically.
			 */
			if (udc->udc_driver->in_lpm(udc)) {
				dev_err(udc->transceiver->dev,
					"%s: Unable to exit lpm\n",
					__func__);
				return -EAGAIN;
			}
		}
	}

	return 0;
}

/******************************************************************************
 * ENDPT block
 *****************************************************************************/
@@ -3507,7 +3428,6 @@ static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active)

	if (gadget_ready) {
		if (is_active) {
			pm_runtime_get_sync(&_gadget->dev);
			hw_device_reset(udc);
			if (udc->udc_driver->notify_event)
				udc->udc_driver->notify_event(udc,
@@ -3520,7 +3440,6 @@ static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active)
			if (udc->udc_driver->notify_event)
				udc->udc_driver->notify_event(udc,
					CI13XXX_CONTROLLER_DISCONNECT_EVENT);
			pm_runtime_put_sync(&_gadget->dev);
		}
	}

@@ -3556,7 +3475,6 @@ static int ci13xxx_pullup(struct usb_gadget *_gadget, int is_active)
{
	struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget);
	unsigned long flags;
	int ret;

	spin_lock_irqsave(udc->lock, flags);
	udc->softconnect = is_active;
@@ -3567,17 +3485,12 @@ static int ci13xxx_pullup(struct usb_gadget *_gadget, int is_active)
	}
	spin_unlock_irqrestore(udc->lock, flags);

	ret = ci13xxx_exit_lpm(udc, true);
	if (ret) {
		dev_err(udc->transceiver->dev,
			"%s: Unable to exit lpm %d, ignore pullup\n",
			__func__, ret);
		return ret;
	}
	pm_runtime_get_sync(&_gadget->dev);

	spin_lock_irqsave(udc->lock, flags);
	if (!udc->vbus_active) {
		spin_unlock_irqrestore(udc->lock, flags);
		pm_runtime_put_sync(&_gadget->dev);
		return 0;
	}
	if (is_active) {
@@ -3590,6 +3503,8 @@ static int ci13xxx_pullup(struct usb_gadget *_gadget, int is_active)
	}
	spin_unlock_irqrestore(udc->lock, flags);

	pm_runtime_put_sync(&_gadget->dev);

	return 0;
}

@@ -3736,7 +3651,6 @@ static int ci13xxx_stop(struct usb_gadget *gadget,
		spin_unlock_irqrestore(udc->lock, flags);
		_gadget_stop_activity(&udc->gadget);
		spin_lock_irqsave(udc->lock, flags);
		pm_runtime_put(&udc->gadget.dev);
	}

	spin_unlock_irqrestore(udc->lock, flags);
+0 −2
Original line number Diff line number Diff line
@@ -148,9 +148,7 @@ struct ci13xxx_udc_driver {
#define CI13XXX_CONTROLLER_ERROR_EVENT			7

	void	(*notify_event)(struct ci13xxx *udc, unsigned event);
	bool (*cancel_pending_suspend)(struct ci13xxx *udc);
	bool    (*in_lpm)(struct ci13xxx *udc);
	void    (*set_fpr_flag)(struct ci13xxx *udc);
	struct clk *system_clk;
};

+4 −40
Original line number Diff line number Diff line
@@ -149,6 +149,7 @@ static int ehci_msm_probe(struct platform_device *pdev)

	hcd->usb_phy = phy;
	device_init_wakeup(&pdev->dev, 1);
	pm_runtime_set_active(&pdev->dev);
	pm_runtime_enable(&pdev->dev);

	msm_bam_set_usb_host_dev(&pdev->dev);
@@ -180,69 +181,32 @@ static int ehci_msm_remove(struct platform_device *pdev)
}

#ifdef CONFIG_PM_RUNTIME
static int ehci_msm_runtime_idle(struct device *dev)
{
	dev_dbg(dev, "ehci runtime idle\n");
	return 0;
}

static int ehci_msm_runtime_suspend(struct device *dev)
{
	struct usb_hcd *hcd = dev_get_drvdata(dev);

	dev_dbg(dev, "ehci runtime suspend\n");
	/*
	 * Notify OTG about suspend.  It takes care of
	 * putting the hardware in LPM.
	 */
	return usb_phy_set_suspend(hcd->usb_phy, 1);

	return 0;
}

static int ehci_msm_runtime_resume(struct device *dev)
{
	struct usb_hcd *hcd = dev_get_drvdata(dev);
	int ret;
	u32 portsc;

	dev_dbg(dev, "ehci runtime resume\n");
	ret = usb_phy_set_suspend(hcd->usb_phy, 0);
	if (ret)
		return ret;

	portsc = readl_relaxed(USB_PORTSC);
	portsc &= ~PORT_RWC_BITS;
	portsc |= PORT_RESUME;
	writel_relaxed(portsc, USB_PORTSC);

	return ret;
}
#endif

#ifdef CONFIG_PM_SLEEP
static int ehci_msm_pm_suspend(struct device *dev)
{
	struct usb_hcd *hcd = dev_get_drvdata(dev);

	dev_dbg(dev, "ehci-msm PM suspend\n");

	if (!hcd->rh_registered)
		return 0;

	return usb_phy_set_suspend(hcd->usb_phy, 1);
}

static int ehci_msm_pm_resume(struct device *dev)
{
	dev_dbg(dev, "ehci-msm PM resume\n");

	return 0;
}
#endif

static const struct dev_pm_ops ehci_msm_dev_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(ehci_msm_pm_suspend, ehci_msm_pm_resume)
	SET_RUNTIME_PM_OPS(ehci_msm_runtime_suspend, ehci_msm_runtime_resume,
				ehci_msm_runtime_idle)
			   NULL)
};

static const struct of_device_id msm_ehci_dt_match[] = {
Loading