phy: qcom: qmp-pcie: Add PHY register retention support
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>pull/1257/head
parent
ea57d7fe4f
commit
0cc22f5a86
|
|
@ -3033,6 +3033,7 @@ struct qmp_pcie {
|
||||||
|
|
||||||
const struct qmp_phy_cfg *cfg;
|
const struct qmp_phy_cfg *cfg;
|
||||||
bool tcsr_4ln_config;
|
bool tcsr_4ln_config;
|
||||||
|
bool skip_init;
|
||||||
|
|
||||||
void __iomem *serdes;
|
void __iomem *serdes;
|
||||||
void __iomem *pcs;
|
void __iomem *pcs;
|
||||||
|
|
@ -4330,19 +4331,39 @@ static int qmp_pcie_init(struct phy *phy)
|
||||||
{
|
{
|
||||||
struct qmp_pcie *qmp = phy_get_drvdata(phy);
|
struct qmp_pcie *qmp = phy_get_drvdata(phy);
|
||||||
const struct qmp_phy_cfg *cfg = qmp->cfg;
|
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;
|
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);
|
ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret);
|
dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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);
|
ret = reset_control_bulk_assert(cfg->num_resets, qmp->resets);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(qmp->dev, "reset assert failed\n");
|
dev_err(qmp->dev, "reset assert failed\n");
|
||||||
goto err_disable_regulators;
|
goto err_disable_regulators;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ret = reset_control_assert(qmp->nocsr_reset);
|
ret = reset_control_assert(qmp->nocsr_reset);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
|
@ -4352,11 +4373,13 @@ static int qmp_pcie_init(struct phy *phy)
|
||||||
|
|
||||||
usleep_range(200, 300);
|
usleep_range(200, 300);
|
||||||
|
|
||||||
|
if (!qmp->skip_init) {
|
||||||
ret = reset_control_bulk_deassert(cfg->num_resets, qmp->resets);
|
ret = reset_control_bulk_deassert(cfg->num_resets, qmp->resets);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(qmp->dev, "reset deassert failed\n");
|
dev_err(qmp->dev, "reset deassert failed\n");
|
||||||
goto err_assert_reset;
|
goto err_assert_reset;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ret = clk_bulk_prepare_enable(ARRAY_SIZE(qmp_pciephy_clk_l), qmp->clks);
|
ret = clk_bulk_prepare_enable(ARRAY_SIZE(qmp_pciephy_clk_l), qmp->clks);
|
||||||
if (ret)
|
if (ret)
|
||||||
|
|
@ -4365,6 +4388,7 @@ static int qmp_pcie_init(struct phy *phy)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_assert_reset:
|
err_assert_reset:
|
||||||
|
if (!qmp->skip_init)
|
||||||
reset_control_bulk_assert(cfg->num_resets, qmp->resets);
|
reset_control_bulk_assert(cfg->num_resets, qmp->resets);
|
||||||
err_disable_regulators:
|
err_disable_regulators:
|
||||||
regulator_bulk_disable(cfg->num_vregs, qmp->vregs);
|
regulator_bulk_disable(cfg->num_vregs, qmp->vregs);
|
||||||
|
|
@ -4377,6 +4401,9 @@ static int qmp_pcie_exit(struct phy *phy)
|
||||||
struct qmp_pcie *qmp = phy_get_drvdata(phy);
|
struct qmp_pcie *qmp = phy_get_drvdata(phy);
|
||||||
const struct qmp_phy_cfg *cfg = qmp->cfg;
|
const struct qmp_phy_cfg *cfg = qmp->cfg;
|
||||||
|
|
||||||
|
if (qmp->nocsr_reset)
|
||||||
|
reset_control_assert(qmp->nocsr_reset);
|
||||||
|
else
|
||||||
reset_control_bulk_assert(cfg->num_resets, qmp->resets);
|
reset_control_bulk_assert(cfg->num_resets, qmp->resets);
|
||||||
|
|
||||||
clk_bulk_disable_unprepare(ARRAY_SIZE(qmp_pciephy_clk_l), qmp->clks);
|
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;
|
unsigned int mask, val;
|
||||||
int ret;
|
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],
|
qphy_setbits(pcs, cfg->regs[QPHY_PCS_POWER_DOWN_CONTROL],
|
||||||
cfg->pwrdn_ctrl);
|
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, &cfg->tbls);
|
||||||
qmp_pcie_init_registers(qmp, mode_tbls);
|
qmp_pcie_init_registers(qmp, mode_tbls);
|
||||||
|
|
||||||
|
skip_tbls_init:
|
||||||
ret = clk_bulk_prepare_enable(qmp->num_pipe_clks, qmp->pipe_clks);
|
ret = clk_bulk_prepare_enable(qmp->num_pipe_clks, qmp->pipe_clks);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -4417,6 +4452,9 @@ static int qmp_pcie_power_on(struct phy *phy)
|
||||||
goto err_disable_pipe_clk;
|
goto err_disable_pipe_clk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (qmp->skip_init)
|
||||||
|
goto skip_serdes_start;
|
||||||
|
|
||||||
/* Pull PHY out of reset state */
|
/* Pull PHY out of reset state */
|
||||||
qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
|
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)
|
if (!cfg->skip_start_delay)
|
||||||
usleep_range(1000, 1200);
|
usleep_range(1000, 1200);
|
||||||
|
|
||||||
|
skip_serdes_start:
|
||||||
status = pcs + cfg->regs[QPHY_PCS_STATUS];
|
status = pcs + cfg->regs[QPHY_PCS_STATUS];
|
||||||
mask = cfg->phy_status;
|
mask = cfg->phy_status;
|
||||||
ret = readl_poll_timeout(status, val, !(val & mask), 200,
|
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);
|
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 */
|
/* PHY reset */
|
||||||
qphy_setbits(qmp->pcs, cfg->regs[QPHY_SW_RESET], SW_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],
|
qphy_clrbits(qmp->pcs, cfg->regs[QPHY_PCS_POWER_DOWN_CONTROL],
|
||||||
cfg->pwrdn_ctrl);
|
cfg->pwrdn_ctrl);
|
||||||
|
|
||||||
|
skip_phy_deinit:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue