[PATCH v2 15/17] phy: qcom-qusb2: Add support for runtime PM

From: Manu Gautam
Date: Wed Sep 27 2017 - 05:01:42 EST


Disable clocks and dp/dm asynchronous interrupts from
PHY as part of runtime suspend.

Signed-off-by: Manu Gautam <mgautam@xxxxxxxxxxxxxx>
---
drivers/phy/qualcomm/phy-qcom-qusb2.c | 159 ++++++++++++++++++++++++++++++++++
1 file changed, 159 insertions(+)

diff --git a/drivers/phy/qualcomm/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c
index 0e9d88b..7414272 100644
--- a/drivers/phy/qualcomm/phy-qcom-qusb2.c
+++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c
@@ -56,6 +56,18 @@

#define PHY_CLK_SCHEME_SEL BIT(0)

+/* QUSB2PHY_INTR_CTRL register bits */
+#define DMSE_INTR_HIGH_SEL BIT(4)
+#define DPSE_INTR_HIGH_SEL BIT(3)
+#define CHG_DET_INTR_EN BIT(2)
+#define DMSE_INTR_EN BIT(1)
+#define DPSE_INTR_EN BIT(0)
+
+/* QUSB2PHY_PLL_CORE_INPUT_OVERRIDE register bits */
+#define CORE_PLL_EN_FROM_RESET BIT(4)
+#define CORE_RESET BIT(5)
+#define CORE_RESET_MUX BIT(6)
+
#define QUSB2PHY_PLL_ANALOG_CONTROLS_TWO 0x04
#define QUSB2PHY_PLL_CLOCK_INVERTERS 0x18c
#define QUSB2PHY_PLL_CMODE 0x2c
@@ -73,6 +85,10 @@
#define UTMI_OTG_VBUS_VALID BIT(20)
#define SW_SESSVLD_SEL BIT(28)

+#define QSCRATCH_CHARGING_DET_OUTPUT 0x1C
+#define LINESTATE_DP BIT(8)
+#define LINESTATE_DM BIT(9)
+
struct qusb2_phy_init_tbl {
unsigned int offset;
unsigned int val;
@@ -98,6 +114,7 @@ struct qusb2_phy_init_tbl {

/* set of registers with offsets different per-PHY */
enum qusb2phy_reg_layout {
+ QUSB2PHY_PLL_CORE_INPUT_OVERRIDE,
QUSB2PHY_PLL_STATUS,
QUSB2PHY_PORT_TUNE1,
QUSB2PHY_PORT_TUNE2,
@@ -117,8 +134,10 @@ enum qusb2phy_reg_layout {
[QUSB2PHY_PORT_TUNE3] = 0x88,
[QUSB2PHY_PORT_TUNE4] = 0x8c,
[QUSB2PHY_PORT_TUNE5] = 0x90,
+ [QUSB2PHY_PORT_TEST1] = 0xb8,
[QUSB2PHY_PORT_TEST2] = 0x9c,
[QUSB2PHY_PORT_POWERDOWN] = 0xb4,
+ [QUSB2PHY_INTR_CTRL] = 0xbc,
};

static const struct qusb2_phy_init_tbl msm8996_init_tbl[] = {
@@ -138,14 +157,17 @@ enum qusb2phy_reg_layout {
};

static const unsigned int qusb2_v2_regs_layout[] = {
+ [QUSB2PHY_PLL_CORE_INPUT_OVERRIDE] = 0xa8,
[QUSB2PHY_PLL_STATUS] = 0x1a0,
[QUSB2PHY_PORT_TUNE1] = 0x240,
[QUSB2PHY_PORT_TUNE2] = 0x244,
[QUSB2PHY_PORT_TUNE3] = 0x248,
[QUSB2PHY_PORT_TUNE4] = 0x24c,
[QUSB2PHY_PORT_TUNE5] = 0x250,
+ [QUSB2PHY_PORT_TEST1] = 0x254,
[QUSB2PHY_PORT_TEST2] = 0x258,
[QUSB2PHY_PORT_POWERDOWN] = 0x210,
+ [QUSB2PHY_INTR_CTRL] = 0x230,
};

static const struct qusb2_phy_init_tbl qusb2_v2_init_tbl[] = {
@@ -180,9 +202,13 @@ struct qusb2_phy_cfg {
const unsigned int *regs;
unsigned int mask_core_ready;
unsigned int disable_ctrl;
+ unsigned int autoresume_en;

/* true if PHY has PLL_TEST register to select clk_scheme */
bool has_pll_test;
+
+ /* true if PHY has PLL_CORE_INPUT_OVERRIDE register to reset PLL */
+ bool has_pll_override;
};

static const struct qusb2_phy_cfg msm8996_phy_cfg = {
@@ -193,6 +219,7 @@ struct qusb2_phy_cfg {
.has_pll_test = true,
.disable_ctrl = (CLAMP_N_EN | FREEZIO_N | POWER_DOWN),
.mask_core_ready = PLL_LOCKED,
+ .autoresume_en = BIT(3),
};

static const struct qusb2_phy_cfg qusb2_v2_phy_cfg = {
@@ -203,6 +230,8 @@ struct qusb2_phy_cfg {
.disable_ctrl = (PWR_CTRL1_VREF_SUPPLY_TRIM | PWR_CTRL1_CLAMP_N_EN |
POWER_DOWN),
.mask_core_ready = CORE_READY_STATUS,
+ .has_pll_override = true,
+ .autoresume_en = BIT(0),
};

static const char * const qusb2_phy_vreg_names[] = {
@@ -229,6 +258,7 @@ struct qusb2_phy_cfg {
*
* @cfg: phy config data
* @has_se_clk_scheme: indicate if PHY has single-ended ref clock scheme
+ * @phy_initialized: indicate if PHY has been initialized
* @mode: indicate current PHY mode of operation e.g. HOST or DEVICE
*/
struct qusb2_phy {
@@ -247,6 +277,7 @@ struct qusb2_phy {

const struct qusb2_phy_cfg *cfg;
bool has_se_clk_scheme;
+ bool phy_initialized;
enum phy_mode mode;
};

@@ -335,6 +366,115 @@ static int qusb2_phy_set_mode(struct phy *phy, enum phy_mode mode)
return 0;
}

+static int __maybe_unused qusb2_phy_runtime_suspend(struct device *dev)
+{
+ struct qusb2_phy *qphy = dev_get_drvdata(dev);
+ const struct qusb2_phy_cfg *cfg = qphy->cfg;
+ u32 linestate = 0, intr_mask;
+
+ dev_vdbg(dev, "Suspending QUSB2 Phy, mode:%d\n", qphy->mode);
+
+ if (!qphy->phy_initialized) {
+ dev_vdbg(dev, "PHY not initialized, bailing out\n");
+ return 0;
+ }
+
+ /*
+ * Enable DP/DM interrupts to detect line state changes based on current
+ * state. In other words, enable the triggers _opposite_ of what the
+ * current D+/D- levels are e.g. if currently D+ high, D- low
+ * (HS 'J'/Suspend), configure the mask to trigger on D+ low OR D- high
+ */
+ if (qphy->qscratch_base) {
+ linestate = readl(qphy->qscratch_base +
+ QSCRATCH_CHARGING_DET_OUTPUT);
+ intr_mask = DPSE_INTR_EN | DMSE_INTR_EN;
+ if (!(linestate & LINESTATE_DP)) /* D+ low */
+ intr_mask |= DPSE_INTR_HIGH_SEL;
+ if (!(linestate & LINESTATE_DM)) /* D- low */
+ intr_mask |= DMSE_INTR_HIGH_SEL;
+
+ writel(intr_mask, qphy->base + cfg->regs[QUSB2PHY_INTR_CTRL]);
+ }
+
+ /* hold core PLL into reset */
+ if (cfg->has_pll_override) {
+ qusb2_setbits(qphy->base,
+ cfg->regs[QUSB2PHY_PLL_CORE_INPUT_OVERRIDE],
+ CORE_PLL_EN_FROM_RESET | CORE_RESET |
+ CORE_RESET_MUX);
+ }
+
+ /* enable phy auto-resume */
+ if (linestate & (LINESTATE_DP | LINESTATE_DM)) {
+ qusb2_setbits(qphy->base, cfg->regs[QUSB2PHY_PORT_TEST1],
+ cfg->autoresume_en);
+ /* Autoresume bit has to be toggled in order to enable it */
+ qusb2_clrbits(qphy->base, cfg->regs[QUSB2PHY_PORT_TEST1],
+ cfg->autoresume_en);
+ }
+
+ if (!qphy->has_se_clk_scheme)
+ clk_disable_unprepare(qphy->ref_clk);
+
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+ clk_disable_unprepare(qphy->iface_clk);
+
+ return 0;
+}
+
+static int __maybe_unused qusb2_phy_runtime_resume(struct device *dev)
+{
+ struct qusb2_phy *qphy = dev_get_drvdata(dev);
+ const struct qusb2_phy_cfg *cfg = qphy->cfg;
+ int ret;
+
+ dev_vdbg(dev, "Resuming QUSB2 phy, mode:%d\n", qphy->mode);
+
+ if (!qphy->phy_initialized) {
+ dev_vdbg(dev, "PHY not initialized, bailing out\n");
+ return 0;
+ }
+
+ ret = clk_prepare_enable(qphy->iface_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable iface_clk, %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable cfg ahb clock, %d\n", ret);
+ goto disable_iface_clk;
+ }
+
+ if (!qphy->has_se_clk_scheme) {
+ clk_prepare_enable(qphy->ref_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable ref clk, %d\n", ret);
+ goto disable_ahb_clk;
+ }
+ }
+
+ writel(0x0, qphy->base + cfg->regs[QUSB2PHY_INTR_CTRL]);
+
+ /* bring core PLL out of reset */
+ if (cfg->has_pll_override) {
+ qusb2_clrbits(qphy->base,
+ cfg->regs[QUSB2PHY_PLL_CORE_INPUT_OVERRIDE],
+ CORE_RESET | CORE_RESET_MUX);
+ }
+
+ return 0;
+
+disable_ahb_clk:
+ clk_disable_unprepare(qphy->cfg_ahb_clk);
+disable_iface_clk:
+ clk_disable_unprepare(qphy->iface_clk);
+
+ return ret;
+}
+
static int qusb2_phy_init(struct phy *phy)
{
struct qusb2_phy *qphy = phy_get_drvdata(phy);
@@ -459,6 +599,7 @@ static int qusb2_phy_init(struct phy *phy)
ret = -EBUSY;
goto disable_ref_clk;
}
+ qphy->phy_initialized = true;

return 0;

@@ -495,6 +636,8 @@ static int qusb2_phy_exit(struct phy *phy)

regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs);

+ qphy->phy_initialized = false;
+
return 0;
}

@@ -517,6 +660,11 @@ static int qusb2_phy_exit(struct phy *phy)
};
MODULE_DEVICE_TABLE(of, qusb2_phy_of_match_table);

+static const struct dev_pm_ops qusb2_phy_pm_ops = {
+ SET_RUNTIME_PM_OPS(qusb2_phy_runtime_suspend,
+ qusb2_phy_runtime_resume, NULL)
+};
+
static int qusb2_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -604,11 +752,19 @@ static int qusb2_phy_probe(struct platform_device *pdev)
qphy->cell = NULL;
dev_dbg(dev, "failed to lookup tune2 hstx trim value\n");
}
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ /*
+ * Prevent runtime pm from being ON by default. Users can enable
+ * it using power/control in sysfs.
+ */
+ pm_runtime_forbid(dev);

generic_phy = devm_phy_create(dev, NULL, &qusb2_phy_gen_ops);
if (IS_ERR(generic_phy)) {
ret = PTR_ERR(generic_phy);
dev_err(dev, "failed to create phy, %d\n", ret);
+ pm_runtime_disable(dev);
return ret;
}
qphy->phy = generic_phy;
@@ -619,6 +775,8 @@ static int qusb2_phy_probe(struct platform_device *pdev)
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
if (!IS_ERR(phy_provider))
dev_info(dev, "Registered Qcom-QUSB2 phy\n");
+ else
+ pm_runtime_disable(dev);

return PTR_ERR_OR_ZERO(phy_provider);
}
@@ -627,6 +785,7 @@ static int qusb2_phy_probe(struct platform_device *pdev)
.probe = qusb2_phy_probe,
.driver = {
.name = "qcom-qusb2-phy",
+ .pm = &qusb2_phy_pm_ops,
.of_match_table = qusb2_phy_of_match_table,
},
};
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project