Merge "usb-phy-qusb: powerdown PHY during disconnect to avoid leakage"
diff --git a/Documentation/devicetree/bindings/usb/msm-phy.txt b/Documentation/devicetree/bindings/usb/msm-phy.txt
index 6405371..f8c8a69 100644
--- a/Documentation/devicetree/bindings/usb/msm-phy.txt
+++ b/Documentation/devicetree/bindings/usb/msm-phy.txt
@@ -160,6 +160,9 @@
"efuse_addr": EFUSE address to read and update analog tune parameter.
"emu_phy_base" : phy base address used for programming emulation target phy.
"ref_clk_addr" : ref_clk bcr address used for on/off ref_clk before reset.
+ "tcsr_clamp_dig_n" : To enable/disable digital clamp to the phy. When
+ de-asserted, it will prevent random leakage from qusb2 phy resulting from
+ out of sequence turn on/off of 1p8, 3p3 and DVDD regulators.
"refgen_north_bg_reg" : address used to read REFGEN status for overriding QUSB PHY register.
- clocks: a list of phandles to the PHY clocks. Use as per
Documentation/devicetree/bindings/clock/clock-bindings.txt
@@ -179,6 +182,8 @@
- qcom,major-rev: provide major revision number to differentiate power up sequence. default is 2.0
- pinctrl-names/pinctrl-0/1: The GPIOs configured as output function. Names represents "active"
state when attached in host mode and "suspend" state when detached.
+ - qcom,tune2-efuse-correction: The value to be adjusted from fused value for
+ improved rise/fall times.
Example:
qusb_phy: qusb@f9b39000 {
diff --git a/drivers/usb/phy/phy-msm-qusb.c b/drivers/usb/phy/phy-msm-qusb.c
index f7ff9e8f..9c33c6e 100644
--- a/drivers/usb/phy/phy-msm-qusb.c
+++ b/drivers/usb/phy/phy-msm-qusb.c
@@ -45,6 +45,8 @@
#define FREEZIO_N BIT(1)
#define POWER_DOWN BIT(0)
+#define QUSB2PHY_PORT_TEST_CTRL 0xB8
+
#define QUSB2PHY_PWR_CTRL1 0x210
#define PWR_CTRL1_CLAMP_N_EN BIT(1)
#define PWR_CTRL1_POWR_DOWN BIT(0)
@@ -68,10 +70,7 @@
#define QUSB2PHY_PORT_TUNE2 0x84
#define QUSB2PHY_PORT_TUNE3 0x88
#define QUSB2PHY_PORT_TUNE4 0x8C
-
-/* In case Efuse register shows zero, use this value */
-#define TUNE2_DEFAULT_HIGH_NIBBLE 0xB
-#define TUNE2_DEFAULT_LOW_NIBBLE 0x3
+#define QUSB2PHY_PORT_TUNE5 0x90
/* Get TUNE2's high nibble value read from efuse */
#define TUNE2_HIGH_NIBBLE_VAL(val, pos, mask) ((val >> pos) & mask)
@@ -98,21 +97,42 @@
#define QUSB2PHY_REFCLK_ENABLE BIT(0)
-unsigned int tune2;
-module_param(tune2, uint, S_IRUGO | S_IWUSR);
+static unsigned int tune1;
+module_param(tune1, uint, 0644);
+MODULE_PARM_DESC(tune1, "QUSB PHY TUNE1");
+
+static unsigned int tune2;
+module_param(tune2, uint, 0644);
MODULE_PARM_DESC(tune2, "QUSB PHY TUNE2");
+static unsigned int tune3;
+module_param(tune3, uint, 0644);
+MODULE_PARM_DESC(tune3, "QUSB PHY TUNE3");
+
+static unsigned int tune4;
+module_param(tune4, uint, 0644);
+MODULE_PARM_DESC(tune4, "QUSB PHY TUNE4");
+
+static unsigned int tune5;
+module_param(tune5, uint, 0644);
+MODULE_PARM_DESC(tune5, "QUSB PHY TUNE5");
+
+
struct qusb_phy {
struct usb_phy phy;
void __iomem *base;
void __iomem *tune2_efuse_reg;
void __iomem *ref_clk_base;
+ void __iomem *tcsr_clamp_dig_n;
struct clk *ref_clk_src;
struct clk *ref_clk;
struct clk *cfg_ahb_clk;
struct reset_control *phy_reset;
+ struct clk *iface_clk;
+ struct clk *core_clk;
+ struct regulator *gdsc;
struct regulator *vdd;
struct regulator *vdda33;
struct regulator *vdda18;
@@ -124,6 +144,7 @@ struct qusb_phy {
u32 tune2_val;
int tune2_efuse_bit_pos;
int tune2_efuse_num_of_bits;
+ int tune2_efuse_correction;
bool power_enabled;
bool clocks_enabled;
@@ -145,6 +166,8 @@ struct qusb_phy {
int phy_pll_reset_seq_len;
int *emu_dcm_reset_seq;
int emu_dcm_reset_seq_len;
+ bool put_into_high_z_state;
+ struct mutex phy_lock;
};
static void qusb_phy_enable_clocks(struct qusb_phy *qphy, bool on)
@@ -155,14 +178,22 @@ static void qusb_phy_enable_clocks(struct qusb_phy *qphy, bool on)
if (!qphy->clocks_enabled && on) {
clk_prepare_enable(qphy->ref_clk_src);
clk_prepare_enable(qphy->ref_clk);
+ clk_prepare_enable(qphy->iface_clk);
+ clk_prepare_enable(qphy->core_clk);
clk_prepare_enable(qphy->cfg_ahb_clk);
qphy->clocks_enabled = true;
}
if (qphy->clocks_enabled && !on) {
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ /*
+ * FSM depedency beween iface_clk and core_clk.
+ * Hence turned off core_clk before iface_clk.
+ */
+ clk_disable_unprepare(qphy->core_clk);
+ clk_disable_unprepare(qphy->iface_clk);
clk_disable_unprepare(qphy->ref_clk);
clk_disable_unprepare(qphy->ref_clk_src);
- clk_disable_unprepare(qphy->cfg_ahb_clk);
qphy->clocks_enabled = false;
}
@@ -170,6 +201,32 @@ static void qusb_phy_enable_clocks(struct qusb_phy *qphy, bool on)
qphy->clocks_enabled);
}
+static int qusb_phy_gdsc(struct qusb_phy *qphy, bool on)
+{
+ int ret;
+
+ if (IS_ERR_OR_NULL(qphy->gdsc))
+ return -EPERM;
+
+ if (on) {
+ dev_dbg(qphy->phy.dev, "TURNING ON GDSC\n");
+ ret = regulator_enable(qphy->gdsc);
+ if (ret) {
+ dev_err(qphy->phy.dev, "unable to enable gdsc\n");
+ return ret;
+ }
+ } else {
+ dev_dbg(qphy->phy.dev, "TURNING OFF GDSC\n");
+ ret = regulator_disable(qphy->gdsc);
+ if (ret) {
+ dev_err(qphy->phy.dev, "unable to disable gdsc\n");
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
static int qusb_phy_config_vdd(struct qusb_phy *qphy, int high)
{
int min, ret;
@@ -313,6 +370,7 @@ static void qusb_phy_get_tune2_param(struct qusb_phy *qphy)
{
u8 num_of_bits;
u32 bit_mask = 1;
+ u8 reg_val;
pr_debug("%s(): num_of_bits:%d bit_pos:%d\n", __func__,
qphy->tune2_efuse_num_of_bits,
@@ -326,9 +384,8 @@ static void qusb_phy_get_tune2_param(struct qusb_phy *qphy)
/*
* Read EFUSE register having TUNE2 parameter's high nibble.
- * If efuse register shows value as 0x0, then use default value
- * as 0xB as high nibble. Otherwise use efuse register based
- * value for this purpose.
+ * If efuse register shows value as 0x0, then use previous value
+ * as it is. Otherwise use efuse register based value for this purpose.
*/
qphy->tune2_val = readl_relaxed(qphy->tune2_efuse_reg);
pr_debug("%s(): bit_mask:%d efuse based tune2 value:%d\n",
@@ -337,12 +394,24 @@ static void qusb_phy_get_tune2_param(struct qusb_phy *qphy)
qphy->tune2_val = TUNE2_HIGH_NIBBLE_VAL(qphy->tune2_val,
qphy->tune2_efuse_bit_pos, bit_mask);
- if (!qphy->tune2_val)
- qphy->tune2_val = TUNE2_DEFAULT_HIGH_NIBBLE;
+ /* Update higher nibble of TUNE2 value for better rise/fall times */
+ if (qphy->tune2_efuse_correction && qphy->tune2_val) {
+ if (qphy->tune2_efuse_correction > 5 ||
+ qphy->tune2_efuse_correction < -10)
+ pr_warn("Correction value is out of range : %d\n",
+ qphy->tune2_efuse_correction);
+ else
+ qphy->tune2_val = qphy->tune2_val +
+ qphy->tune2_efuse_correction;
+ }
- /* Get TUNE2 byte value using high and low nibble value */
- qphy->tune2_val = ((qphy->tune2_val << 0x4) |
- TUNE2_DEFAULT_LOW_NIBBLE);
+ reg_val = readb_relaxed(qphy->base + QUSB2PHY_PORT_TUNE2);
+ if (qphy->tune2_val) {
+ reg_val &= 0x0f;
+ reg_val |= (qphy->tune2_val << 4);
+ }
+
+ qphy->tune2_val = reg_val;
}
static void qusb_phy_write_seq(void __iomem *base, u32 *seq, int cnt,
@@ -450,7 +519,7 @@ static int qusb_phy_init(struct usb_phy *phy)
* and try to read EFUSE value only once i.e. not every USB
* cable connect case.
*/
- if (qphy->tune2_efuse_reg) {
+ if (qphy->tune2_efuse_reg && !tune2) {
if (!qphy->tune2_val)
qusb_phy_get_tune2_param(qphy);
@@ -460,13 +529,29 @@ static int qusb_phy_init(struct usb_phy *phy)
qphy->base + QUSB2PHY_PORT_TUNE2);
}
- /* If tune2 modparam set, override tune2 value */
- if (tune2) {
- pr_debug("%s(): (modparam) TUNE2 val:0x%02x\n",
- __func__, tune2);
+ /* If tune modparam set, override tune value */
+
+ pr_debug("%s():userspecified modparams TUNEX val:0x%x %x %x %x %x\n",
+ __func__, tune1, tune2, tune3, tune4, tune5);
+ if (tune1)
+ writel_relaxed(tune1,
+ qphy->base + QUSB2PHY_PORT_TUNE1);
+
+ if (tune2)
writel_relaxed(tune2,
qphy->base + QUSB2PHY_PORT_TUNE2);
- }
+
+ if (tune3)
+ writel_relaxed(tune3,
+ qphy->base + QUSB2PHY_PORT_TUNE3);
+
+ if (tune4)
+ writel_relaxed(tune4,
+ qphy->base + QUSB2PHY_PORT_TUNE4);
+
+ if (tune5)
+ writel_relaxed(tune5,
+ qphy->base + QUSB2PHY_PORT_TUNE5);
/* ensure above writes are completed before re-enabling PHY */
wmb();
@@ -596,27 +681,55 @@ static int qusb_phy_set_suspend(struct usb_phy *phy, int suspend)
writel_relaxed(intr_mask,
qphy->base + QUSB2PHY_PORT_INTR_CTRL);
+ if (linestate & (LINESTATE_DP | LINESTATE_DM)) {
+ /* enable phy auto-resume */
+ writel_relaxed(0x0C,
+ qphy->base + QUSB2PHY_PORT_TEST_CTRL);
+ /* flush the previous write before next write */
+ wmb();
+ writel_relaxed(0x04,
+ qphy->base + QUSB2PHY_PORT_TEST_CTRL);
+ }
+
+
+ dev_dbg(phy->dev, "%s: intr_mask = %x\n",
+ __func__, intr_mask);
+
+ /* Makes sure that above write goes through */
+ wmb();
+
qusb_phy_enable_clocks(qphy, false);
} else { /* Disconnect case */
+ mutex_lock(&qphy->phy_lock);
/* Disable all interrupts */
writel_relaxed(0x00,
qphy->base + QUSB2PHY_PORT_INTR_CTRL);
- /*
- * Phy in non-driving mode leaves Dp and Dm lines in
- * high-Z state. Controller power collapse is not
- * switching phy to non-driving mode causing charger
- * detection failure. Bring phy to non-driving mode by
- * overriding controller output via UTMI interface.
- */
- writel_relaxed(TERM_SELECT | XCVR_SELECT_FS |
- OP_MODE_NON_DRIVE,
- qphy->base + QUSB2PHY_PORT_UTMI_CTRL1);
- writel_relaxed(UTMI_ULPI_SEL | UTMI_TEST_MUX_SEL,
- qphy->base + QUSB2PHY_PORT_UTMI_CTRL2);
+ /* Disable PHY */
+ writel_relaxed(POWER_DOWN,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN);
+ /* Make sure that above write is completed */
+ wmb();
qusb_phy_enable_clocks(qphy, false);
- qusb_phy_enable_power(qphy, false);
+ if (qphy->tcsr_clamp_dig_n)
+ writel_relaxed(0x0,
+ qphy->tcsr_clamp_dig_n);
+ /* Do not disable power rails if there is vote for it */
+ if (!qphy->dpdm_enable)
+ qusb_phy_enable_power(qphy, false);
+ else
+ dev_dbg(phy->dev, "race with rm_pulldown. Keep ldo ON\n");
+ mutex_unlock(&qphy->phy_lock);
+
+ /*
+ * Set put_into_high_z_state to true so next USB
+ * cable connect, DPF_DMF request performs PHY
+ * reset and put it into high-z state. For bootup
+ * with or without USB cable, it doesn't require
+ * to put QUSB PHY into high-z state.
+ */
+ qphy->put_into_high_z_state = true;
}
qphy->suspended = true;
} else {
@@ -629,6 +742,9 @@ static int qusb_phy_set_suspend(struct usb_phy *phy, int suspend)
qphy->base + QUSB2PHY_PORT_INTR_CTRL);
} else {
qusb_phy_enable_power(qphy, true);
+ if (qphy->tcsr_clamp_dig_n)
+ writel_relaxed(0x1,
+ qphy->tcsr_clamp_dig_n);
qusb_phy_enable_clocks(qphy, true);
}
qphy->suspended = false;
@@ -669,15 +785,61 @@ static int qusb_phy_dpdm_regulator_enable(struct regulator_dev *rdev)
dev_dbg(qphy->phy.dev, "%s dpdm_enable:%d\n",
__func__, qphy->dpdm_enable);
+ mutex_lock(&qphy->phy_lock);
if (!qphy->dpdm_enable) {
ret = qusb_phy_enable_power(qphy, true);
if (ret < 0) {
dev_dbg(qphy->phy.dev,
"dpdm regulator enable failed:%d\n", ret);
+ mutex_unlock(&qphy->phy_lock);
return ret;
}
qphy->dpdm_enable = true;
+ if (qphy->put_into_high_z_state) {
+ if (qphy->tcsr_clamp_dig_n)
+ writel_relaxed(0x1,
+ qphy->tcsr_clamp_dig_n);
+
+ qusb_phy_gdsc(qphy, true);
+ qusb_phy_enable_clocks(qphy, true);
+
+ dev_dbg(qphy->phy.dev, "RESET QUSB PHY\n");
+ ret = reset_control_assert(qphy->phy_reset);
+ if (ret)
+ dev_err(qphy->phy.dev, "phyassert failed\n");
+ usleep_range(100, 150);
+ ret = reset_control_deassert(qphy->phy_reset);
+ if (ret)
+ dev_err(qphy->phy.dev, "deassert failed\n");
+
+ /*
+ * Phy in non-driving mode leaves Dp and Dm
+ * lines in high-Z state. Controller power
+ * collapse is not switching phy to non-driving
+ * mode causing charger detection failure. Bring
+ * phy to non-driving mode by overriding
+ * controller output via UTMI interface.
+ */
+ writel_relaxed(TERM_SELECT | XCVR_SELECT_FS |
+ OP_MODE_NON_DRIVE,
+ qphy->base + QUSB2PHY_PORT_UTMI_CTRL1);
+ writel_relaxed(UTMI_ULPI_SEL |
+ UTMI_TEST_MUX_SEL,
+ qphy->base + QUSB2PHY_PORT_UTMI_CTRL2);
+
+
+ /* Disable PHY */
+ writel_relaxed(CLAMP_N_EN | FREEZIO_N |
+ POWER_DOWN,
+ qphy->base + QUSB2PHY_PORT_POWERDOWN);
+ /* Make sure that above write is completed */
+ wmb();
+
+ qusb_phy_enable_clocks(qphy, false);
+ qusb_phy_gdsc(qphy, false);
+ }
}
+ mutex_unlock(&qphy->phy_lock);
return ret;
}
@@ -690,19 +852,25 @@ static int qusb_phy_dpdm_regulator_disable(struct regulator_dev *rdev)
dev_dbg(qphy->phy.dev, "%s dpdm_enable:%d\n",
__func__, qphy->dpdm_enable);
+ mutex_lock(&qphy->phy_lock);
if (qphy->dpdm_enable) {
if (!qphy->cable_connected) {
+ if (qphy->tcsr_clamp_dig_n)
+ writel_relaxed(0x0,
+ qphy->tcsr_clamp_dig_n);
dev_dbg(qphy->phy.dev, "turn off for HVDCP case\n");
ret = qusb_phy_enable_power(qphy, false);
if (ret < 0) {
dev_dbg(qphy->phy.dev,
"dpdm regulator disable failed:%d\n",
ret);
+ mutex_unlock(&qphy->phy_lock);
return ret;
}
}
qphy->dpdm_enable = false;
}
+ mutex_unlock(&qphy->phy_lock);
return ret;
}
@@ -794,6 +962,9 @@ static int qusb_phy_probe(struct platform_device *pdev)
"qcom,tune2-efuse-num-bits",
&qphy->tune2_efuse_num_of_bits);
}
+ of_property_read_u32(dev->of_node,
+ "qcom,tune2-efuse-correction",
+ &qphy->tune2_efuse_correction);
if (ret) {
dev_err(dev, "DT Value for tune2 efuse is invalid.\n");
@@ -829,6 +1000,17 @@ static int qusb_phy_probe(struct platform_device *pdev)
}
}
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "tcsr_clamp_dig_n_1p8");
+ if (res) {
+ qphy->tcsr_clamp_dig_n = devm_ioremap_nocache(dev,
+ res->start, resource_size(res));
+ if (IS_ERR(qphy->tcsr_clamp_dig_n)) {
+ dev_err(dev, "err reading tcsr_clamp_dig_n\n");
+ qphy->tcsr_clamp_dig_n = NULL;
+ }
+ }
+
qphy->ref_clk_src = devm_clk_get(dev, "ref_clk_src");
if (IS_ERR(qphy->ref_clk_src))
dev_dbg(dev, "clk get failed for ref_clk_src\n");
@@ -847,6 +1029,34 @@ static int qusb_phy_probe(struct platform_device *pdev)
if (IS_ERR(qphy->phy_reset))
return PTR_ERR(qphy->phy_reset);
+ if (of_property_match_string(dev->of_node,
+ "clock-names", "iface_clk") >= 0) {
+ qphy->iface_clk = devm_clk_get(dev, "iface_clk");
+ if (IS_ERR(qphy->iface_clk)) {
+ ret = PTR_ERR(qphy->iface_clk);
+ qphy->iface_clk = NULL;
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ dev_err(dev, "couldn't get iface_clk(%d)\n", ret);
+ }
+ }
+
+ if (of_property_match_string(dev->of_node,
+ "clock-names", "core_clk") >= 0) {
+ qphy->core_clk = devm_clk_get(dev, "core_clk");
+ if (IS_ERR(qphy->core_clk)) {
+ ret = PTR_ERR(qphy->core_clk);
+ qphy->core_clk = NULL;
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ dev_err(dev, "couldn't get core_clk(%d)\n", ret);
+ }
+ }
+
+ qphy->gdsc = devm_regulator_get(dev, "USB3_GDSC");
+ if (IS_ERR(qphy->gdsc))
+ qphy->gdsc = NULL;
+
qphy->emulation = of_property_read_bool(dev->of_node,
"qcom,emulation");
@@ -981,6 +1191,7 @@ static int qusb_phy_probe(struct platform_device *pdev)
return PTR_ERR(qphy->vdda18);
}
+ mutex_init(&qphy->phy_lock);
platform_set_drvdata(pdev, qphy);
qphy->phy.label = "msm-qusb-phy";
@@ -1010,6 +1221,10 @@ static int qusb_phy_probe(struct platform_device *pdev)
if (ret)
usb_remove_phy(&qphy->phy);
+ /* de-assert clamp dig n to reduce leakage on 1p8 upon boot up */
+ if (qphy->tcsr_clamp_dig_n)
+ writel_relaxed(0x0, qphy->tcsr_clamp_dig_n);
+
return ret;
}