// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2025, The Linux Foundation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define RST_ASSERT_DELAY_MIN_US 100 #define RST_ASSERT_DELAY_MAX_US 150 #define PIPE_CLK_DELAY_MIN_US 5000 #define PIPE_CLK_DELAY_MAX_US 5100 #define CLK_EN_DELAY_MIN_US 30 #define CLK_EN_DELAY_MAX_US 50 #define CDR_CTRL_REG_1 0x80 #define CDR_CTRL_REG_2 0x84 #define CDR_CTRL_REG_3 0x88 #define CDR_CTRL_REG_4 0x8c #define CDR_CTRL_REG_5 0x90 #define CDR_CTRL_REG_6 0x94 #define CDR_CTRL_REG_7 0x98 #define SSCG_CTRL_REG_1 0x9c #define SSCG_CTRL_REG_2 0xa0 #define SSCG_CTRL_REG_3 0xa4 #define SSCG_CTRL_REG_4 0xa8 #define SSCG_CTRL_REG_5 0xac #define SSCG_CTRL_REG_6 0xb0 #define PCS_INTERNAL_CONTROL_2 0x2d8 #define PHY_CFG_PLLCFG 0x220 #define PHY_CFG_EIOS_DTCT_REG 0x3e4 #define PHY_CFG_GEN3_ALIGN_HOLDOFF_TIME 0x3e8 enum qcom_uniphy_pcie_type { PHY_TYPE_PCIE = 1, PHY_TYPE_PCIE_GEN2, PHY_TYPE_PCIE_GEN3, }; struct qcom_uniphy_pcie_regs { u32 offset; u32 val; }; struct qcom_uniphy_pcie_data { int lane_offset; /* offset between the lane register bases */ u32 phy_type; const struct qcom_uniphy_pcie_regs *init_seq; u32 init_seq_num; u32 pipe_clk_rate; }; struct qcom_uniphy_pcie { struct phy phy; struct device *dev; const struct qcom_uniphy_pcie_data *data; struct clk_bulk_data *clks; int num_clks; struct reset_control *resets; void __iomem *base; int lanes; }; #define phy_to_dw_phy(x) container_of((x), struct qca_uni_pcie_phy, phy) static const struct qcom_uniphy_pcie_regs ipq5018_regs[] = { { .offset = SSCG_CTRL_REG_4, .val = 0x1cb9, }, { .offset = SSCG_CTRL_REG_5, .val = 0x023a, }, { .offset = SSCG_CTRL_REG_3, .val = 0xd360, }, { .offset = SSCG_CTRL_REG_1, .val = 0x1, }, { .offset = SSCG_CTRL_REG_2, .val = 0xeb, }, { .offset = CDR_CTRL_REG_4, .val = 0x3f9, }, { .offset = CDR_CTRL_REG_5, .val = 0x1c9, }, { .offset = CDR_CTRL_REG_2, .val = 0x419, }, { .offset = CDR_CTRL_REG_1, .val = 0x200, }, { .offset = PCS_INTERNAL_CONTROL_2, .val = 0xf101, }, }; static const struct qcom_uniphy_pcie_regs ipq5332_regs[] = { { .offset = PHY_CFG_PLLCFG, .val = 0x30, }, { .offset = PHY_CFG_EIOS_DTCT_REG, .val = 0x53ef, }, { .offset = PHY_CFG_GEN3_ALIGN_HOLDOFF_TIME, .val = 0xcf, }, }; static const struct qcom_uniphy_pcie_data ipq5018_data = { .lane_offset = 0x800, .phy_type = PHY_TYPE_PCIE_GEN2, .init_seq = ipq5018_regs, .init_seq_num = ARRAY_SIZE(ipq5018_regs), .pipe_clk_rate = 125 * MEGA, }; static const struct qcom_uniphy_pcie_data ipq5332_data = { .lane_offset = 0x800, .phy_type = PHY_TYPE_PCIE_GEN3, .init_seq = ipq5332_regs, .init_seq_num = ARRAY_SIZE(ipq5332_regs), .pipe_clk_rate = 250 * MEGA, }; static void qcom_uniphy_pcie_init(struct qcom_uniphy_pcie *phy) { const struct qcom_uniphy_pcie_data *data = phy->data; const struct qcom_uniphy_pcie_regs *init_seq; void __iomem *base = phy->base; int lane, i; for (lane = 0; lane < phy->lanes; lane++) { init_seq = data->init_seq; for (i = 0; i < data->init_seq_num; i++) writel(init_seq[i].val, base + init_seq[i].offset); base += data->lane_offset; } } static int qcom_uniphy_pcie_power_off(struct phy *x) { struct qcom_uniphy_pcie *phy = phy_get_drvdata(x); clk_bulk_disable_unprepare(phy->num_clks, phy->clks); return reset_control_assert(phy->resets); } static int qcom_uniphy_pcie_power_on(struct phy *x) { struct qcom_uniphy_pcie *phy = phy_get_drvdata(x); int ret; ret = reset_control_assert(phy->resets); if (ret) { dev_err(phy->dev, "reset assert failed (%d)\n", ret); return ret; } usleep_range(RST_ASSERT_DELAY_MIN_US, RST_ASSERT_DELAY_MAX_US); ret = reset_control_deassert(phy->resets); if (ret) { dev_err(phy->dev, "reset deassert failed (%d)\n", ret); return ret; } usleep_range(PIPE_CLK_DELAY_MIN_US, PIPE_CLK_DELAY_MAX_US); ret = clk_bulk_prepare_enable(phy->num_clks, phy->clks); if (ret) { dev_err(phy->dev, "clk prepare and enable failed %d\n", ret); return ret; } usleep_range(CLK_EN_DELAY_MIN_US, CLK_EN_DELAY_MAX_US); qcom_uniphy_pcie_init(phy); return 0; } static inline int qcom_uniphy_pcie_get_resources(struct platform_device *pdev, struct qcom_uniphy_pcie *phy) { struct resource *res; phy->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(phy->base)) return PTR_ERR(phy->base); phy->num_clks = devm_clk_bulk_get_all(phy->dev, &phy->clks); if (phy->num_clks < 0) return phy->num_clks; phy->resets = devm_reset_control_array_get_exclusive(phy->dev); if (IS_ERR(phy->resets)) return PTR_ERR(phy->resets); return 0; } /* * Register a fixed rate pipe clock. * * The _pipe_clksrc generated by PHY goes to the GCC that gate * controls it. The _pipe_clk coming out of the GCC is requested * by the PHY driver for its operations. * We register the _pipe_clksrc here. The gcc driver takes care * of assigning this _pipe_clksrc as parent to _pipe_clk. * Below picture shows this relationship. * * +---------------+ * | PHY block |<<---------------------------------------+ * | | | * | +-------+ | +-----+ | * I/P---^-->| PLL |---^--->pipe_clksrc--->| GCC |--->pipe_clk---+ * clk | +-------+ | +-----+ * +---------------+ */ static inline int phy_pipe_clk_register(struct qcom_uniphy_pcie *phy, int id) { const struct qcom_uniphy_pcie_data *data = phy->data; struct clk_hw *hw; char name[64]; snprintf(name, sizeof(name), "phy%d_pipe_clk_src", id); hw = devm_clk_hw_register_fixed_rate(phy->dev, name, NULL, 0, data->pipe_clk_rate); if (IS_ERR(hw)) return dev_err_probe(phy->dev, PTR_ERR(hw), "Unable to register %s\n", name); return devm_of_clk_add_hw_provider(phy->dev, of_clk_hw_simple_get, hw); } static const struct of_device_id qcom_uniphy_pcie_id_table[] = { { .compatible = "qcom,ipq5018-uniphy-pcie-phy", .data = &ipq5018_data, }, { .compatible = "qcom,ipq5332-uniphy-pcie-phy", .data = &ipq5332_data, }, { /* Sentinel */ }, }; MODULE_DEVICE_TABLE(of, qcom_uniphy_pcie_id_table); static const struct phy_ops pcie_ops = { .power_on = qcom_uniphy_pcie_power_on, .power_off = qcom_uniphy_pcie_power_off, .owner = THIS_MODULE, }; static int qcom_uniphy_pcie_probe(struct platform_device *pdev) { struct phy_provider *phy_provider; struct device *dev = &pdev->dev; struct qcom_uniphy_pcie *phy; struct phy *generic_phy; int ret; phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); if (!phy) return -ENOMEM; platform_set_drvdata(pdev, phy); phy->dev = &pdev->dev; phy->data = of_device_get_match_data(dev); if (!phy->data) return -EINVAL; ret = of_property_read_u32(dev_of_node(dev), "num-lanes", &phy->lanes); if (ret) return dev_err_probe(dev, ret, "Couldn't read num-lanes\n"); ret = qcom_uniphy_pcie_get_resources(pdev, phy); if (ret < 0) return dev_err_probe(&pdev->dev, ret, "failed to get resources: %d\n", ret); generic_phy = devm_phy_create(phy->dev, NULL, &pcie_ops); if (IS_ERR(generic_phy)) return PTR_ERR(generic_phy); phy_set_drvdata(generic_phy, phy); ret = phy_pipe_clk_register(phy, generic_phy->id); if (ret) dev_err(&pdev->dev, "failed to register phy pipe clk\n"); phy_provider = devm_of_phy_provider_register(phy->dev, of_phy_simple_xlate); if (IS_ERR(phy_provider)) return PTR_ERR(phy_provider); return 0; } static struct platform_driver qcom_uniphy_pcie_driver = { .probe = qcom_uniphy_pcie_probe, .driver = { .name = "qcom-uniphy-pcie", .of_match_table = qcom_uniphy_pcie_id_table, }, }; module_platform_driver(qcom_uniphy_pcie_driver); MODULE_DESCRIPTION("PCIE QCOM UNIPHY driver"); MODULE_LICENSE("GPL");