mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-04 04:06:26 +00:00
f89263b697
When enable initcall_debug together with higher debug level below.
CONFIG_CONSOLE_LOGLEVEL_DEFAULT=9
CONFIG_CONSOLE_LOGLEVEL_QUIET=9
CONFIG_MESSAGE_LOGLEVEL_DEFAULT=7
The initialization of i.MX8MP PCIe PHY might be timeout failed randomly.
To fix this issue, adjust the sequence of the resets refer to the power
up sequence listed below.
i.MX8MP PCIe PHY power up sequence:
/---------------------------------------------
1.8v supply ---------/
/---------------------------------------------------
0.8v supply ---/
---\ /--------------------------------------------------
X REFCLK Valid
Reference Clock ---/ \--------------------------------------------------
-------------------------------------------
|
i_init_restn --------------
------------------------------------
|
i_cmn_rstn ---------------------
-------------------------------
|
o_pll_lock_done --------------------------
Logs:
imx6q-pcie 33800000.pcie: host bridge /soc@0/pcie@33800000 ranges:
imx6q-pcie 33800000.pcie: IO 0x001ff80000..0x001ff8ffff -> 0x0000000000
imx6q-pcie 33800000.pcie: MEM 0x0018000000..0x001fefffff -> 0x0018000000
probe of clk_imx8mp_audiomix.reset.0 returned 0 after 1052 usecs
probe of 30e20000.clock-controller returned 0 after 32971 usecs
phy phy-32f00000.pcie-phy.4: phy poweron failed --> -110
probe of 30e10000.dma-controller returned 0 after 10235 usecs
imx6q-pcie 33800000.pcie: waiting for PHY ready timeout!
dwhdmi-imx 32fd8000.hdmi: Detected HDMI TX controller v2.13a with HDCP (samsung_dw_hdmi_phy2)
imx6q-pcie 33800000.pcie: probe with driver imx6q-pcie failed with error -110
Fixes: dce9edff16
("phy: freescale: imx8m-pcie: Add i.MX8MP PCIe PHY support")
Cc: stable@vger.kernel.org
Signed-off-by: Richard Zhu <hongxing.zhu@nxp.com>
Signed-off-by: Frank Li <Frank.Li@nxp.com>
v2 changes:
- Rebase to latest fixes branch of linux-phy git repo.
- Richard's environment have problem and can't sent out patch. So I help
post this fix patch.
Link: https://lore.kernel.org/r/20241021155241.943665-1-Frank.Li@nxp.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
289 lines
8.3 KiB
C
289 lines
8.3 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright 2021 NXP
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/io.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/mfd/syscon/imx7-iomuxc-gpr.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/reset.h>
|
|
|
|
#include <dt-bindings/phy/phy-imx8-pcie.h>
|
|
|
|
#define IMX8MM_PCIE_PHY_CMN_REG061 0x184
|
|
#define ANA_PLL_CLK_OUT_TO_EXT_IO_EN BIT(0)
|
|
#define IMX8MM_PCIE_PHY_CMN_REG062 0x188
|
|
#define ANA_PLL_CLK_OUT_TO_EXT_IO_SEL BIT(3)
|
|
#define IMX8MM_PCIE_PHY_CMN_REG063 0x18C
|
|
#define AUX_PLL_REFCLK_SEL_SYS_PLL GENMASK(7, 6)
|
|
#define IMX8MM_PCIE_PHY_CMN_REG064 0x190
|
|
#define ANA_AUX_RX_TX_SEL_TX BIT(7)
|
|
#define ANA_AUX_RX_TERM_GND_EN BIT(3)
|
|
#define ANA_AUX_TX_TERM BIT(2)
|
|
#define IMX8MM_PCIE_PHY_CMN_REG065 0x194
|
|
#define ANA_AUX_RX_TERM (BIT(7) | BIT(4))
|
|
#define ANA_AUX_TX_LVL GENMASK(3, 0)
|
|
#define IMX8MM_PCIE_PHY_CMN_REG075 0x1D4
|
|
#define ANA_PLL_DONE 0x3
|
|
#define PCIE_PHY_TRSV_REG5 0x414
|
|
#define PCIE_PHY_TRSV_REG6 0x418
|
|
|
|
#define IMX8MM_GPR_PCIE_REF_CLK_SEL GENMASK(25, 24)
|
|
#define IMX8MM_GPR_PCIE_REF_CLK_PLL FIELD_PREP(IMX8MM_GPR_PCIE_REF_CLK_SEL, 0x3)
|
|
#define IMX8MM_GPR_PCIE_REF_CLK_EXT FIELD_PREP(IMX8MM_GPR_PCIE_REF_CLK_SEL, 0x2)
|
|
#define IMX8MM_GPR_PCIE_AUX_EN BIT(19)
|
|
#define IMX8MM_GPR_PCIE_CMN_RST BIT(18)
|
|
#define IMX8MM_GPR_PCIE_POWER_OFF BIT(17)
|
|
#define IMX8MM_GPR_PCIE_SSC_EN BIT(16)
|
|
#define IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE BIT(9)
|
|
|
|
enum imx8_pcie_phy_type {
|
|
IMX8MM,
|
|
IMX8MP,
|
|
};
|
|
|
|
struct imx8_pcie_phy_drvdata {
|
|
const char *gpr;
|
|
enum imx8_pcie_phy_type variant;
|
|
};
|
|
|
|
struct imx8_pcie_phy {
|
|
void __iomem *base;
|
|
struct clk *clk;
|
|
struct phy *phy;
|
|
struct regmap *iomuxc_gpr;
|
|
struct reset_control *perst;
|
|
struct reset_control *reset;
|
|
u32 refclk_pad_mode;
|
|
u32 tx_deemph_gen1;
|
|
u32 tx_deemph_gen2;
|
|
bool clkreq_unused;
|
|
const struct imx8_pcie_phy_drvdata *drvdata;
|
|
};
|
|
|
|
static int imx8_pcie_phy_power_on(struct phy *phy)
|
|
{
|
|
int ret;
|
|
u32 val, pad_mode;
|
|
struct imx8_pcie_phy *imx8_phy = phy_get_drvdata(phy);
|
|
|
|
pad_mode = imx8_phy->refclk_pad_mode;
|
|
switch (imx8_phy->drvdata->variant) {
|
|
case IMX8MM:
|
|
reset_control_assert(imx8_phy->reset);
|
|
|
|
/* Tune PHY de-emphasis setting to pass PCIe compliance. */
|
|
if (imx8_phy->tx_deemph_gen1)
|
|
writel(imx8_phy->tx_deemph_gen1,
|
|
imx8_phy->base + PCIE_PHY_TRSV_REG5);
|
|
if (imx8_phy->tx_deemph_gen2)
|
|
writel(imx8_phy->tx_deemph_gen2,
|
|
imx8_phy->base + PCIE_PHY_TRSV_REG6);
|
|
break;
|
|
case IMX8MP: /* Do nothing. */
|
|
break;
|
|
}
|
|
|
|
if (pad_mode == IMX8_PCIE_REFCLK_PAD_INPUT ||
|
|
pad_mode == IMX8_PCIE_REFCLK_PAD_UNUSED) {
|
|
/* Configure the pad as input */
|
|
val = readl(imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG061);
|
|
writel(val & ~ANA_PLL_CLK_OUT_TO_EXT_IO_EN,
|
|
imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG061);
|
|
} else {
|
|
/* Configure the PHY to output the refclock via pad */
|
|
writel(ANA_PLL_CLK_OUT_TO_EXT_IO_EN,
|
|
imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG061);
|
|
}
|
|
|
|
if (pad_mode == IMX8_PCIE_REFCLK_PAD_OUTPUT ||
|
|
pad_mode == IMX8_PCIE_REFCLK_PAD_UNUSED) {
|
|
/* Source clock from SoC internal PLL */
|
|
writel(ANA_PLL_CLK_OUT_TO_EXT_IO_SEL,
|
|
imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG062);
|
|
if (imx8_phy->drvdata->variant != IMX8MM) {
|
|
writel(AUX_PLL_REFCLK_SEL_SYS_PLL,
|
|
imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG063);
|
|
}
|
|
val = ANA_AUX_RX_TX_SEL_TX | ANA_AUX_TX_TERM;
|
|
writel(val | ANA_AUX_RX_TERM_GND_EN,
|
|
imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG064);
|
|
writel(ANA_AUX_RX_TERM | ANA_AUX_TX_LVL,
|
|
imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG065);
|
|
}
|
|
|
|
/* Set AUX_EN_OVERRIDE 1'b0, when the CLKREQ# isn't hooked */
|
|
regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14,
|
|
IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE,
|
|
imx8_phy->clkreq_unused ?
|
|
0 : IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE);
|
|
regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14,
|
|
IMX8MM_GPR_PCIE_AUX_EN,
|
|
IMX8MM_GPR_PCIE_AUX_EN);
|
|
regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14,
|
|
IMX8MM_GPR_PCIE_POWER_OFF, 0);
|
|
regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14,
|
|
IMX8MM_GPR_PCIE_SSC_EN, 0);
|
|
|
|
regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14,
|
|
IMX8MM_GPR_PCIE_REF_CLK_SEL,
|
|
pad_mode == IMX8_PCIE_REFCLK_PAD_INPUT ?
|
|
IMX8MM_GPR_PCIE_REF_CLK_EXT :
|
|
IMX8MM_GPR_PCIE_REF_CLK_PLL);
|
|
usleep_range(100, 200);
|
|
|
|
switch (imx8_phy->drvdata->variant) {
|
|
case IMX8MP:
|
|
reset_control_deassert(imx8_phy->perst);
|
|
fallthrough;
|
|
case IMX8MM:
|
|
reset_control_deassert(imx8_phy->reset);
|
|
usleep_range(200, 500);
|
|
break;
|
|
}
|
|
|
|
/* Do the PHY common block reset */
|
|
regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14,
|
|
IMX8MM_GPR_PCIE_CMN_RST,
|
|
IMX8MM_GPR_PCIE_CMN_RST);
|
|
|
|
/* Polling to check the phy is ready or not. */
|
|
ret = readl_poll_timeout(imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG075,
|
|
val, val == ANA_PLL_DONE, 10, 20000);
|
|
return ret;
|
|
}
|
|
|
|
static int imx8_pcie_phy_init(struct phy *phy)
|
|
{
|
|
struct imx8_pcie_phy *imx8_phy = phy_get_drvdata(phy);
|
|
|
|
return clk_prepare_enable(imx8_phy->clk);
|
|
}
|
|
|
|
static int imx8_pcie_phy_exit(struct phy *phy)
|
|
{
|
|
struct imx8_pcie_phy *imx8_phy = phy_get_drvdata(phy);
|
|
|
|
clk_disable_unprepare(imx8_phy->clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct phy_ops imx8_pcie_phy_ops = {
|
|
.init = imx8_pcie_phy_init,
|
|
.exit = imx8_pcie_phy_exit,
|
|
.power_on = imx8_pcie_phy_power_on,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static const struct imx8_pcie_phy_drvdata imx8mm_drvdata = {
|
|
.gpr = "fsl,imx8mm-iomuxc-gpr",
|
|
.variant = IMX8MM,
|
|
};
|
|
|
|
static const struct imx8_pcie_phy_drvdata imx8mp_drvdata = {
|
|
.gpr = "fsl,imx8mp-iomuxc-gpr",
|
|
.variant = IMX8MP,
|
|
};
|
|
|
|
static const struct of_device_id imx8_pcie_phy_of_match[] = {
|
|
{.compatible = "fsl,imx8mm-pcie-phy", .data = &imx8mm_drvdata, },
|
|
{.compatible = "fsl,imx8mp-pcie-phy", .data = &imx8mp_drvdata, },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, imx8_pcie_phy_of_match);
|
|
|
|
static int imx8_pcie_phy_probe(struct platform_device *pdev)
|
|
{
|
|
struct phy_provider *phy_provider;
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct imx8_pcie_phy *imx8_phy;
|
|
|
|
imx8_phy = devm_kzalloc(dev, sizeof(*imx8_phy), GFP_KERNEL);
|
|
if (!imx8_phy)
|
|
return -ENOMEM;
|
|
|
|
imx8_phy->drvdata = of_device_get_match_data(dev);
|
|
|
|
/* get PHY refclk pad mode */
|
|
of_property_read_u32(np, "fsl,refclk-pad-mode",
|
|
&imx8_phy->refclk_pad_mode);
|
|
|
|
if (of_property_read_u32(np, "fsl,tx-deemph-gen1",
|
|
&imx8_phy->tx_deemph_gen1))
|
|
imx8_phy->tx_deemph_gen1 = 0;
|
|
|
|
if (of_property_read_u32(np, "fsl,tx-deemph-gen2",
|
|
&imx8_phy->tx_deemph_gen2))
|
|
imx8_phy->tx_deemph_gen2 = 0;
|
|
|
|
if (of_property_read_bool(np, "fsl,clkreq-unsupported"))
|
|
imx8_phy->clkreq_unused = true;
|
|
else
|
|
imx8_phy->clkreq_unused = false;
|
|
|
|
imx8_phy->clk = devm_clk_get(dev, "ref");
|
|
if (IS_ERR(imx8_phy->clk)) {
|
|
dev_err(dev, "failed to get imx pcie phy clock\n");
|
|
return PTR_ERR(imx8_phy->clk);
|
|
}
|
|
|
|
/* Grab GPR config register range */
|
|
imx8_phy->iomuxc_gpr =
|
|
syscon_regmap_lookup_by_compatible(imx8_phy->drvdata->gpr);
|
|
if (IS_ERR(imx8_phy->iomuxc_gpr)) {
|
|
dev_err(dev, "unable to find iomuxc registers\n");
|
|
return PTR_ERR(imx8_phy->iomuxc_gpr);
|
|
}
|
|
|
|
imx8_phy->reset = devm_reset_control_get_exclusive(dev, "pciephy");
|
|
if (IS_ERR(imx8_phy->reset)) {
|
|
dev_err(dev, "Failed to get PCIEPHY reset control\n");
|
|
return PTR_ERR(imx8_phy->reset);
|
|
}
|
|
|
|
if (imx8_phy->drvdata->variant == IMX8MP) {
|
|
imx8_phy->perst =
|
|
devm_reset_control_get_exclusive(dev, "perst");
|
|
if (IS_ERR(imx8_phy->perst))
|
|
return dev_err_probe(dev, PTR_ERR(imx8_phy->perst),
|
|
"Failed to get PCIE PHY PERST control\n");
|
|
}
|
|
|
|
imx8_phy->base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(imx8_phy->base))
|
|
return PTR_ERR(imx8_phy->base);
|
|
|
|
imx8_phy->phy = devm_phy_create(dev, NULL, &imx8_pcie_phy_ops);
|
|
if (IS_ERR(imx8_phy->phy))
|
|
return PTR_ERR(imx8_phy->phy);
|
|
|
|
phy_set_drvdata(imx8_phy->phy, imx8_phy);
|
|
|
|
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
|
|
|
return PTR_ERR_OR_ZERO(phy_provider);
|
|
}
|
|
|
|
static struct platform_driver imx8_pcie_phy_driver = {
|
|
.probe = imx8_pcie_phy_probe,
|
|
.driver = {
|
|
.name = "imx8-pcie-phy",
|
|
.of_match_table = imx8_pcie_phy_of_match,
|
|
}
|
|
};
|
|
module_platform_driver(imx8_pcie_phy_driver);
|
|
|
|
MODULE_DESCRIPTION("FSL IMX8 PCIE PHY driver");
|
|
MODULE_LICENSE("GPL v2");
|