From ac0d239936bd87d044d34037a9b581ca260269e3 Mon Sep 17 00:00:00 2001 From: Manu Gautam Date: Tue, 16 Jan 2018 16:27:11 +0530 Subject: [PATCH] phy: qcom-qmp: Add support for runtime PM Disable clocks and enable PHY autonomous mode to detect wakeup events when PHY is suspended. Core driver should notify speed to PHY driver to enable LFPS and/or RX_DET interrupts. Signed-off-by: Manu Gautam Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/qualcomm/phy-qcom-qmp.c | 177 +++++++++++++++++++++++++++- drivers/phy/qualcomm/phy-qcom-qmp.h | 3 + 2 files changed, 179 insertions(+), 1 deletion(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index 55b83974b531..ba6c5b2e4898 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -61,6 +61,19 @@ #define USB3_MODE BIT(0) /* enables USB3 mode */ #define DP_MODE BIT(1) /* enables DP mode */ +/* QPHY_PCS_AUTONOMOUS_MODE_CTRL register bits */ +#define ARCVR_DTCT_EN BIT(0) +#define ALFPS_DTCT_EN BIT(1) +#define ARCVR_DTCT_EVENT_SEL BIT(4) + +/* QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR register bits */ +#define IRQ_CLEAR BIT(0) + +/* QPHY_PCS_LFPS_RXTERM_IRQ_STATUS register bits */ +#define RCVR_DETECT BIT(0) + +/* QPHY_V3_PCS_MISC_CLAMP_ENABLE register bits */ +#define CLAMP_EN BIT(0) /* enables i/o clamp_n */ #define PHY_INIT_COMPLETE_TIMEOUT 1000 #define POWER_DOWN_DELAY_US_MIN 10 @@ -108,6 +121,9 @@ enum qphy_reg_layout { QPHY_SW_RESET, QPHY_START_CTRL, QPHY_PCS_READY_STATUS, + QPHY_PCS_AUTONOMOUS_MODE_CTRL, + QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR, + QPHY_PCS_LFPS_RXTERM_IRQ_STATUS, }; static const unsigned int pciephy_regs_layout[] = { @@ -135,12 +151,18 @@ static const unsigned int usb3phy_regs_layout[] = { [QPHY_SW_RESET] = 0x00, [QPHY_START_CTRL] = 0x08, [QPHY_PCS_READY_STATUS] = 0x17c, + [QPHY_PCS_AUTONOMOUS_MODE_CTRL] = 0x0d4, + [QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR] = 0x0d8, + [QPHY_PCS_LFPS_RXTERM_IRQ_STATUS] = 0x178, }; static const unsigned int qmp_v3_usb3phy_regs_layout[] = { [QPHY_SW_RESET] = 0x00, [QPHY_START_CTRL] = 0x08, [QPHY_PCS_READY_STATUS] = 0x174, + [QPHY_PCS_AUTONOMOUS_MODE_CTRL] = 0x0d8, + [QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR] = 0x0dc, + [QPHY_PCS_LFPS_RXTERM_IRQ_STATUS] = 0x170, }; static const struct qmp_phy_init_tbl msm8996_pcie_serdes_tbl[] = { @@ -536,6 +558,7 @@ struct qmp_phy_cfg { * @tx: iomapped memory space for lane's tx * @rx: iomapped memory space for lane's rx * @pcs: iomapped memory space for lane's pcs + * @pcs_misc: iomapped memory space for lane's pcs_misc * @pipe_clk: pipe lock * @index: lane index * @qmp: QMP phy to which this lane belongs @@ -546,6 +569,7 @@ struct qmp_phy { void __iomem *tx; void __iomem *rx; void __iomem *pcs; + void __iomem *pcs_misc; struct clk *pipe_clk; unsigned int index; struct qcom_qmp *qmp; @@ -567,6 +591,8 @@ struct qmp_phy { * @phys: array of per-lane phy descriptors * @phy_mutex: mutex lock for PHY common block initialization * @init_count: phy common block initialization count + * @phy_initialized: indicate if PHY has been initialized + * @mode: current PHY mode */ struct qcom_qmp { struct device *dev; @@ -582,6 +608,8 @@ struct qcom_qmp { struct mutex phy_mutex; int init_count; + bool phy_initialized; + enum phy_mode mode; }; static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) @@ -995,6 +1023,7 @@ static int qcom_qmp_phy_init(struct phy *phy) dev_err(qmp->dev, "phy initialization timed-out\n"); goto err_pcs_ready; } + qmp->phy_initialized = true; return ret; @@ -1029,6 +1058,128 @@ static int qcom_qmp_phy_exit(struct phy *phy) qcom_qmp_phy_com_exit(qmp); + qmp->phy_initialized = false; + + return 0; +} + +static int qcom_qmp_phy_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct qmp_phy *qphy = phy_get_drvdata(phy); + struct qcom_qmp *qmp = qphy->qmp; + + qmp->mode = mode; + + return 0; +} + +static void qcom_qmp_phy_enable_autonomous_mode(struct qmp_phy *qphy) +{ + struct qcom_qmp *qmp = qphy->qmp; + const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *pcs = qphy->pcs; + void __iomem *pcs_misc = qphy->pcs_misc; + u32 intr_mask; + + if (qmp->mode == PHY_MODE_USB_HOST_SS || + qmp->mode == PHY_MODE_USB_DEVICE_SS) + intr_mask = ARCVR_DTCT_EN | ALFPS_DTCT_EN; + else + intr_mask = ARCVR_DTCT_EN | ARCVR_DTCT_EVENT_SEL; + + /* Clear any pending interrupts status */ + qphy_setbits(pcs, cfg->regs[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR], IRQ_CLEAR); + /* Writing 1 followed by 0 clears the interrupt */ + qphy_clrbits(pcs, cfg->regs[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR], IRQ_CLEAR); + + qphy_clrbits(pcs, cfg->regs[QPHY_PCS_AUTONOMOUS_MODE_CTRL], + ARCVR_DTCT_EN | ALFPS_DTCT_EN | ARCVR_DTCT_EVENT_SEL); + + /* Enable required PHY autonomous mode interrupts */ + qphy_setbits(pcs, cfg->regs[QPHY_PCS_AUTONOMOUS_MODE_CTRL], intr_mask); + + /* Enable i/o clamp_n for autonomous mode */ + if (pcs_misc) + qphy_clrbits(pcs_misc, QPHY_V3_PCS_MISC_CLAMP_ENABLE, CLAMP_EN); +} + +static void qcom_qmp_phy_disable_autonomous_mode(struct qmp_phy *qphy) +{ + struct qcom_qmp *qmp = qphy->qmp; + const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *pcs = qphy->pcs; + void __iomem *pcs_misc = qphy->pcs_misc; + + /* Disable i/o clamp_n on resume for normal mode */ + if (pcs_misc) + qphy_setbits(pcs_misc, QPHY_V3_PCS_MISC_CLAMP_ENABLE, CLAMP_EN); + + qphy_clrbits(pcs, cfg->regs[QPHY_PCS_AUTONOMOUS_MODE_CTRL], + ARCVR_DTCT_EN | ARCVR_DTCT_EVENT_SEL | ALFPS_DTCT_EN); + + qphy_setbits(pcs, cfg->regs[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR], IRQ_CLEAR); + /* Writing 1 followed by 0 clears the interrupt */ + qphy_clrbits(pcs, cfg->regs[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR], IRQ_CLEAR); +} + +static int __maybe_unused qcom_qmp_phy_runtime_suspend(struct device *dev) +{ + struct qcom_qmp *qmp = dev_get_drvdata(dev); + struct qmp_phy *qphy = qmp->phys[0]; + const struct qmp_phy_cfg *cfg = qmp->cfg; + + dev_vdbg(dev, "Suspending QMP phy, mode:%d\n", qmp->mode); + + /* Supported only for USB3 PHY */ + if (cfg->type != PHY_TYPE_USB3) + return 0; + + if (!qmp->phy_initialized) { + dev_vdbg(dev, "PHY not initialized, bailing out\n"); + return 0; + } + + qcom_qmp_phy_enable_autonomous_mode(qphy); + + clk_disable_unprepare(qphy->pipe_clk); + clk_bulk_disable_unprepare(cfg->num_clks, qmp->clks); + + return 0; +} + +static int __maybe_unused qcom_qmp_phy_runtime_resume(struct device *dev) +{ + struct qcom_qmp *qmp = dev_get_drvdata(dev); + struct qmp_phy *qphy = qmp->phys[0]; + const struct qmp_phy_cfg *cfg = qmp->cfg; + int ret = 0; + + dev_vdbg(dev, "Resuming QMP phy, mode:%d\n", qmp->mode); + + /* Supported only for USB3 PHY */ + if (cfg->type != PHY_TYPE_USB3) + return 0; + + if (!qmp->phy_initialized) { + dev_vdbg(dev, "PHY not initialized, bailing out\n"); + return 0; + } + + ret = clk_bulk_prepare_enable(cfg->num_clks, qmp->clks); + if (ret) { + dev_err(qmp->dev, "failed to enable clks, err=%d\n", ret); + return ret; + } + + ret = clk_prepare_enable(qphy->pipe_clk); + if (ret) { + dev_err(dev, "pipe_clk enable failed, err=%d\n", ret); + clk_bulk_disable_unprepare(cfg->num_clks, qmp->clks); + return ret; + } + + qcom_qmp_phy_disable_autonomous_mode(qphy); + return 0; } @@ -1142,6 +1293,7 @@ static const struct phy_ops qcom_qmp_phy_gen_ops = { .init = qcom_qmp_phy_init, .exit = qcom_qmp_phy_exit, .power_on = qcom_qmp_phy_poweron, + .set_mode = qcom_qmp_phy_set_mode, .owner = THIS_MODULE, }; @@ -1160,7 +1312,8 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id) /* * Get memory resources for each phy lane: - * Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2. + * Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2; and + * pcs_misc (optional) -> 3. */ qphy->tx = of_iomap(np, 0); if (!qphy->tx) @@ -1174,6 +1327,10 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id) if (!qphy->pcs) return -ENOMEM; + qphy->pcs_misc = of_iomap(np, 3); + if (!qphy->pcs_misc) + dev_vdbg(dev, "PHY pcs_misc-reg not used\n"); + /* * Get PHY's Pipe clock, if any. USB3 and PCIe are PIPE3 * based phys, so they essentially have pipe clock. So, @@ -1240,6 +1397,11 @@ static const struct of_device_id qcom_qmp_phy_of_match_table[] = { }; MODULE_DEVICE_TABLE(of, qcom_qmp_phy_of_match_table); +static const struct dev_pm_ops qcom_qmp_phy_pm_ops = { + SET_RUNTIME_PM_OPS(qcom_qmp_phy_runtime_suspend, + qcom_qmp_phy_runtime_resume, NULL) +}; + static int qcom_qmp_phy_probe(struct platform_device *pdev) { struct qcom_qmp *qmp; @@ -1308,12 +1470,21 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev) return -ENOMEM; id = 0; + 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); + for_each_available_child_of_node(dev->of_node, child) { /* Create per-lane phy */ ret = qcom_qmp_phy_create(dev, child, id); if (ret) { dev_err(dev, "failed to create lane%d phy, %d\n", id, ret); + pm_runtime_disable(dev); return ret; } @@ -1325,6 +1496,7 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev) if (ret) { dev_err(qmp->dev, "failed to register pipe clock source\n"); + pm_runtime_disable(dev); return ret; } id++; @@ -1333,6 +1505,8 @@ static int qcom_qmp_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-QMP phy\n"); + else + pm_runtime_disable(dev); return PTR_ERR_OR_ZERO(phy_provider); } @@ -1341,6 +1515,7 @@ static struct platform_driver qcom_qmp_phy_driver = { .probe = qcom_qmp_phy_probe, .driver = { .name = "qcom-qmp-phy", + .pm = &qcom_qmp_phy_pm_ops, .of_match_table = qcom_qmp_phy_of_match_table, }, }; diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.h b/drivers/phy/qualcomm/phy-qcom-qmp.h index 944269a62761..d1c6905d0439 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.h +++ b/drivers/phy/qualcomm/phy-qcom-qmp.h @@ -274,4 +274,7 @@ #define QPHY_V3_PCS_FLL_MAN_CODE 0x0d4 #define QPHY_V3_PCS_RX_SIGDET_LVL 0x1d8 +/* Only for QMP V3 PHY - PCS_MISC registers */ +#define QPHY_V3_PCS_MISC_CLAMP_ENABLE 0x0c + #endif