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

Commit fc017bf3 authored by Jack Pham's avatar Jack Pham
Browse files

usb: phy: msm: Handle multiport suspend/resume



On some platforms the SSPHY and HSPHY are actually shared
multi-port PHYs that are paired with separate DWC3 controllers.
In this case suspending into low power mode must not be allowed
unless all instances are suspended. Add reference counting to
keep track of when all ports have suspended before allowing PHY
to be placed in low power mode.

The approaches used for the SS versus the HS PHY are slightly
different, so the reference counting semantics differ as follows:
- The HS PHY instances can be operated more or less indepedently,
  as each port has its own set of QSCRATCH registers. The only
  exception is the PHY reset, which is managed by the primary
  instance. So the reference counter is added as a static global
  to keep track across multiple instances of when to allow reset.
- The SS PHY instances can share a single device driver instance,
  as only the primary controller QSCRATCH registers are useful.
  Both controllers can then share the same PHY device, so reference
  counting is added within the instance structure and is used to
  prevent powering off the PHY until all instances have called
  set_suspend(0).

Change-Id: Ia5a03ff711d60da84bec8bd9086b70c386de0b93
Signed-off-by: default avatarJack Pham <jackp@codeaurora.org>
parent 3fd4f55f
Loading
Loading
Loading
Loading
+22 −1
Original line number Diff line number Diff line
@@ -137,6 +137,9 @@ struct msm_hsphy {
	bool			cable_connected;
};

/* global reference counter between all HSPHY instances */
static atomic_t hsphy_active_count;

static int msm_hsusb_config_vdd(struct msm_hsphy *phy, int high)
{
	int min, ret;
@@ -263,6 +266,13 @@ static int msm_hsphy_reset(struct usb_phy *uphy)
	u32 val;
	int ret;

	/* skip reset if there are other active PHY instances */
	ret = atomic_read(&hsphy_active_count);
	if (ret > 1) {
		dev_dbg(uphy->dev, "skipping reset, inuse count=%d\n", ret);
		return 0;
	}

	if (phy->tcsr) {
		val = readl_relaxed(phy->tcsr);

@@ -354,7 +364,7 @@ static int msm_hsphy_set_suspend(struct usb_phy *uphy, int suspend)
	struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy);
	bool host = uphy->flags & PHY_HOST_MODE;
	bool chg_connected = uphy->flags & PHY_CHARGER_CONNECTED;
	int i;
	int i, count;

	if (!!suspend == phy->suspended) {
		dev_dbg(uphy->dev, "%s\n", suspend ? "already suspended"
@@ -438,7 +448,15 @@ static int msm_hsphy_set_suspend(struct usb_phy *uphy, int suspend)
			}
			msm_hsusb_config_vdd(phy, 0);
		}

		count = atomic_dec_return(&hsphy_active_count);
		if (count < 0) {
			dev_WARN(uphy->dev, "hsphy_active_count=%d, something wrong?\n",
					count);
			atomic_set(&hsphy_active_count, 0);
		}
	} else {
		atomic_inc(&hsphy_active_count);
		if (phy->lpm_flags & PHY_RETENTIONED && !phy->cable_connected) {
			msm_hsusb_config_vdd(phy, 1);
			if (phy->ext_vbus_id) {
@@ -736,6 +754,7 @@ static int msm_hsphy_probe(struct platform_device *pdev)
	if (ret)
		goto disable_clk;

	atomic_inc(&hsphy_active_count);
	return 0;

disable_clk:
@@ -762,6 +781,8 @@ static int msm_hsphy_remove(struct platform_device *pdev)
	msm_hsusb_ldo_enable(phy, 0);
	regulator_disable(phy->vdd);
	msm_hsusb_config_vdd(phy, 0);
	if (!phy->suspended)
		atomic_dec(&hsphy_active_count);
	kfree(phy);

	return 0;
+25 −9
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@ struct msm_ssphy {
	struct clk		*reset_clk;	/* SS PHY reset */
	struct regulator	*vdd;
	struct regulator	*vdda18;
	atomic_t		active_count;	/* num of active instances */
	bool			suspended;
	int			vdd_levels[3]; /* none, low, high */
	int			deemphasis_val;
@@ -331,18 +332,25 @@ static int msm_ssphy_set_suspend(struct usb_phy *uphy, int suspend)
{
	struct msm_ssphy *phy = container_of(uphy, struct msm_ssphy, phy);
	void __iomem *base = phy->base;
	int ret = 0;

	if (!!suspend == phy->suspended) {
		dev_dbg(uphy->dev, "%s\n", suspend ? "already suspended"
						   : "already resumed");
		return 0;
	}
	int count;

	/* Ensure clock is on before accessing QSCRATCH registers */
	clk_prepare_enable(phy->core_clk);

	if (suspend) {
		count = atomic_dec_return(&phy->active_count);
		if (count > 0 || phy->suspended) {
			dev_dbg(uphy->dev, "Skipping suspend, active_count=%d phy->suspended=%d\n",
					count, phy->suspended);
			goto done;
		}

		if (count < 0) {
			dev_WARN(uphy->dev, "Suspended too many times!  active_count=%d\n",
					count);
			atomic_set(&phy->active_count, 0);
		}

		/* Clear REF_SS_PHY_EN */
		msm_usb_write_readback(base, SS_PHY_CTRL_REG, REF_SS_PHY_EN, 0);
		/* Clear REF_USE_PAD */
@@ -359,7 +367,16 @@ static int msm_ssphy_set_suspend(struct usb_phy *uphy, int suspend)

		msm_ssusb_ldo_enable(phy, 0);
		msm_ssusb_config_vdd(phy, 0);
		phy->suspended = true;
	} else {
		count = atomic_inc_return(&phy->active_count);
		if (count > 1 || !phy->suspended) {
			dev_dbg(uphy->dev, "Skipping resume, active_count=%d phy->suspended=%d\n",
					count, phy->suspended);
			goto done;
		}

		phy->suspended = false;
		msm_ssusb_config_vdd(phy, 1);
		msm_ssusb_ldo_enable(phy, 1);

@@ -401,8 +418,7 @@ static int msm_ssphy_set_suspend(struct usb_phy *uphy, int suspend)

done:
	clk_disable_unprepare(phy->core_clk);
	phy->suspended = !!suspend; /* double-NOT coerces to bool value */
	return ret;
	return 0;
}

static int msm_ssphy_notify_connect(struct usb_phy *uphy,