]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
phy: qcom: qmp-pcie: Add PHY register retention support
authorQiang Yu <quic_qianyu@quicinc.com>
Fri, 11 Apr 2025 11:31:20 +0000 (19:31 +0800)
committerVinod Koul <vkoul@kernel.org>
Fri, 11 Apr 2025 11:39:04 +0000 (17:09 +0530)
Some QCOM PCIe PHYs support no_csr reset. Unlike BCR reset which resets the
whole PHY (hardware and register), no_csr reset only resets PHY hardware
but retains register values, which means PHY setting can be skipped during
PHY init if PCIe link is enabled in bootloader and only no_csr is toggled
after that.

Hence, determine whether the PHY has been enabled in bootloader by
verifying QPHY_START_CTRL register. If it's programmed and no_csr reset is
available, skip BCR reset and PHY register setting to establish the PCIe
link with bootloader - programmed PHY settings.

Signed-off-by: Qiang Yu <quic_qianyu@quicinc.com>
Signed-off-by: Wenbin Yao <quic_wenbyao@quicinc.com>
Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Tested-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com>
Link: https://lore.kernel.org/r/20250411113120.651363-3-quic_wenbyao@quicinc.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
drivers/phy/qualcomm/phy-qcom-qmp-pcie.c

index 3fd911506f08d92cc9cfee03d4cb8efbe4280fdb..ab90aafb313e6e759c0f88589013632bb6277807 100644 (file)
@@ -3033,6 +3033,7 @@ struct qmp_pcie {
 
        const struct qmp_phy_cfg *cfg;
        bool tcsr_4ln_config;
+       bool skip_init;
 
        void __iomem *serdes;
        void __iomem *pcs;
@@ -4330,18 +4331,38 @@ static int qmp_pcie_init(struct phy *phy)
 {
        struct qmp_pcie *qmp = phy_get_drvdata(phy);
        const struct qmp_phy_cfg *cfg = qmp->cfg;
+       void __iomem *pcs = qmp->pcs;
+       bool phy_initialized = !!(readl(pcs + cfg->regs[QPHY_START_CTRL]));
        int ret;
 
+       qmp->skip_init = qmp->nocsr_reset && phy_initialized;
+       /*
+        * We need to check the existence of init sequences in two cases:
+        * 1. The PHY doesn't support no_csr reset.
+        * 2. The PHY supports no_csr reset but isn't initialized by bootloader.
+        * As we can't skip init in these two cases.
+        */
+       if (!qmp->skip_init && !cfg->tbls.serdes_num) {
+               dev_err(qmp->dev, "Init sequence not available\n");
+               return -ENODATA;
+       }
+
        ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs);
        if (ret) {
                dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret);
                return ret;
        }
 
-       ret = reset_control_bulk_assert(cfg->num_resets, qmp->resets);
-       if (ret) {
-               dev_err(qmp->dev, "reset assert failed\n");
-               goto err_disable_regulators;
+       /*
+        * Toggle BCR reset for PHY that doesn't support no_csr reset or has not
+        * been initialized.
+        */
+       if (!qmp->skip_init) {
+               ret = reset_control_bulk_assert(cfg->num_resets, qmp->resets);
+               if (ret) {
+                       dev_err(qmp->dev, "reset assert failed\n");
+                       goto err_disable_regulators;
+               }
        }
 
        ret = reset_control_assert(qmp->nocsr_reset);
@@ -4352,10 +4373,12 @@ static int qmp_pcie_init(struct phy *phy)
 
        usleep_range(200, 300);
 
-       ret = reset_control_bulk_deassert(cfg->num_resets, qmp->resets);
-       if (ret) {
-               dev_err(qmp->dev, "reset deassert failed\n");
-               goto err_assert_reset;
+       if (!qmp->skip_init) {
+               ret = reset_control_bulk_deassert(cfg->num_resets, qmp->resets);
+               if (ret) {
+                       dev_err(qmp->dev, "reset deassert failed\n");
+                       goto err_assert_reset;
+               }
        }
 
        ret = clk_bulk_prepare_enable(ARRAY_SIZE(qmp_pciephy_clk_l), qmp->clks);
@@ -4365,7 +4388,8 @@ static int qmp_pcie_init(struct phy *phy)
        return 0;
 
 err_assert_reset:
-       reset_control_bulk_assert(cfg->num_resets, qmp->resets);
+       if (!qmp->skip_init)
+               reset_control_bulk_assert(cfg->num_resets, qmp->resets);
 err_disable_regulators:
        regulator_bulk_disable(cfg->num_vregs, qmp->vregs);
 
@@ -4377,7 +4401,10 @@ static int qmp_pcie_exit(struct phy *phy)
        struct qmp_pcie *qmp = phy_get_drvdata(phy);
        const struct qmp_phy_cfg *cfg = qmp->cfg;
 
-       reset_control_bulk_assert(cfg->num_resets, qmp->resets);
+       if (qmp->nocsr_reset)
+               reset_control_assert(qmp->nocsr_reset);
+       else
+               reset_control_bulk_assert(cfg->num_resets, qmp->resets);
 
        clk_bulk_disable_unprepare(ARRAY_SIZE(qmp_pciephy_clk_l), qmp->clks);
 
@@ -4396,6 +4423,13 @@ static int qmp_pcie_power_on(struct phy *phy)
        unsigned int mask, val;
        int ret;
 
+       /*
+        * Write CSR register for PHY that doesn't support no_csr reset or has not
+        * been initialized.
+        */
+       if (qmp->skip_init)
+               goto skip_tbls_init;
+
        qphy_setbits(pcs, cfg->regs[QPHY_PCS_POWER_DOWN_CONTROL],
                        cfg->pwrdn_ctrl);
 
@@ -4407,6 +4441,7 @@ static int qmp_pcie_power_on(struct phy *phy)
        qmp_pcie_init_registers(qmp, &cfg->tbls);
        qmp_pcie_init_registers(qmp, mode_tbls);
 
+skip_tbls_init:
        ret = clk_bulk_prepare_enable(qmp->num_pipe_clks, qmp->pipe_clks);
        if (ret)
                return ret;
@@ -4417,6 +4452,9 @@ static int qmp_pcie_power_on(struct phy *phy)
                goto err_disable_pipe_clk;
        }
 
+       if (qmp->skip_init)
+               goto skip_serdes_start;
+
        /* Pull PHY out of reset state */
        qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
 
@@ -4426,6 +4464,7 @@ static int qmp_pcie_power_on(struct phy *phy)
        if (!cfg->skip_start_delay)
                usleep_range(1000, 1200);
 
+skip_serdes_start:
        status = pcs + cfg->regs[QPHY_PCS_STATUS];
        mask = cfg->phy_status;
        ret = readl_poll_timeout(status, val, !(val & mask), 200,
@@ -4450,6 +4489,15 @@ static int qmp_pcie_power_off(struct phy *phy)
 
        clk_bulk_disable_unprepare(qmp->num_pipe_clks, qmp->pipe_clks);
 
+       /*
+        * While powering off the PHY, only qmp->nocsr_reset needs to be checked. In
+        * this way, no matter whether the PHY settings were initially programmed by
+        * bootloader or PHY driver itself, we can reuse them when PHY is powered on
+        * next time.
+        */
+       if (qmp->nocsr_reset)
+               goto skip_phy_deinit;
+
        /* PHY reset */
        qphy_setbits(qmp->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
 
@@ -4461,6 +4509,7 @@ static int qmp_pcie_power_off(struct phy *phy)
        qphy_clrbits(qmp->pcs, cfg->regs[QPHY_PCS_POWER_DOWN_CONTROL],
                        cfg->pwrdn_ctrl);
 
+skip_phy_deinit:
        return 0;
 }