[PATCH v2 02/14] phy: qcom: Introduce Qualcomm IPQ5332 Super-Speed USB UNIPHY driver
From: George Moussalem via B4 Relay
Date: Wed Aug 12 2026 - 05:49:06 EST
From: Praveenkumar I <quic_ipkumar@xxxxxxxxxxx>
Adds Qualcomm 22ull Super-Speed USB UNIPHY driver support which is
present in Qualcomm IPQ5018 & IPQ5332 SoCs.
This PHY is interfaced with SNPS DWC3 USB and SNPS DWC PCIe. Either one
of the interface can use the it and selection is done via mux present in
TCSR register. By default, the PHY is configured for PCIe interface and
needs this driver to configure the mux for USB.
Signed-off-by: Praveenkumar I <quic_ipkumar@xxxxxxxxxxx>
Signed-off-by: George Moussalem <george.moussalem@xxxxxxxxxxx>
---
drivers/phy/qualcomm/Kconfig | 10 +
drivers/phy/qualcomm/Makefile | 1 +
.../phy/qualcomm/phy-qcom-uniphy-usb-ss-22ull.c | 304 +++++++++++++++++++++
3 files changed, 315 insertions(+)
diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig
index 60a0ead127fa..1ceb04afe1e3 100644
--- a/drivers/phy/qualcomm/Kconfig
+++ b/drivers/phy/qualcomm/Kconfig
@@ -158,6 +158,16 @@ config PHY_QCOM_UNIPHY_PCIE_28LP
handles PHY initialization, clock management required after
resetting the hardware and power management.
+config PHY_QCOM_UNIPHY_USB_22ULL
+ tristate "Qualcomm IPQ5332 UNIPHY USB Super-Speed 22ull driver"
+ depends on USB && (ARCH_QCOM || COMPILE_TEST)
+ select GENERIC_PHY
+ help
+ Enable this to support the Qualcomm USB Super-Speed UNIPHY transceiver
+ that is used on Qualcomm IPQ5018 and IPQ5332 SoCs with DWC3 USB core.
+ It handles PHY initialization, clock management required after
+ resetting the hardware and power management.
+
config PHY_QCOM_M31_EUSB
tristate "Qualcomm M31 eUSB2 PHY driver support"
depends on USB && (ARCH_QCOM || COMPILE_TEST)
diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile
index b71a6a0bed3f..295e8042e3f4 100644
--- a/drivers/phy/qualcomm/Makefile
+++ b/drivers/phy/qualcomm/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_PHY_QCOM_QMP_USB_LEGACY) += phy-qcom-qmp-usb-legacy.o
obj-$(CONFIG_PHY_QCOM_QUSB2) += phy-qcom-qusb2.o
obj-$(CONFIG_PHY_QCOM_EUSB2_REPEATER) += phy-qcom-eusb2-repeater.o
obj-$(CONFIG_PHY_QCOM_UNIPHY_PCIE_28LP) += phy-qcom-uniphy-pcie-28lp.o
+obj-$(CONFIG_PHY_QCOM_UNIPHY_USB_22ULL) += phy-qcom-uniphy-usb-ss-22ull.o
obj-$(CONFIG_PHY_QCOM_USB_HS) += phy-qcom-usb-hs.o
obj-$(CONFIG_PHY_QCOM_USB_HSIC) += phy-qcom-usb-hsic.o
obj-$(CONFIG_PHY_QCOM_USB_HS_28NM) += phy-qcom-usb-hs-28nm.o
diff --git a/drivers/phy/qualcomm/phy-qcom-uniphy-usb-ss-22ull.c b/drivers/phy/qualcomm/phy-qcom-uniphy-usb-ss-22ull.c
new file mode 100644
index 000000000000..089de7cebef0
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-uniphy-usb-ss-22ull.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2026, Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.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/regulator/consumer.h>
+#include <linux/reset.h>
+
+#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 PCIE_USB_COMBO_PHY_CFG_MISC1 0x214
+#define PCIE_USB_COMBO_PHY_CFG_RX_AFE_2 0x7C4
+#define PCIE_USB_COMBO_PHY_CFG_RX_DLF_DEMUX_2 0x7E8
+
+#define TCSR_USB_MUX_SEL BIT(0)
+
+struct phy_init_tbl {
+ unsigned int offset;
+ unsigned int val;
+};
+
+struct uniphy_cfg {
+ const struct phy_init_tbl *init_seq;
+ int num_init_seq;
+ u32 pipe_clk_rate;
+};
+
+struct uniphy_usb {
+ struct device *dev;
+ const struct uniphy_cfg *cfg;
+ struct phy *phy;
+ void __iomem *base;
+ struct clk_bulk_data *clks;
+ unsigned int num_clks;
+ struct reset_control *reset;
+ struct regulator *vreg;
+ struct regmap *tcsr;
+ unsigned int mux_offset;
+};
+
+#define PHY_INIT_CFG(o, v) \
+ { \
+ .offset = o, \
+ .val = v, \
+ }
+
+static const struct phy_init_tbl ipq5018_usb_uniphy_init_tbl[] = {
+ PHY_INIT_CFG(SSCG_CTRL_REG_4, 0x1cb9),
+ PHY_INIT_CFG(SSCG_CTRL_REG_5, 0x023a),
+ PHY_INIT_CFG(SSCG_CTRL_REG_3, 0xd360),
+ PHY_INIT_CFG(SSCG_CTRL_REG_1, 0x1),
+ PHY_INIT_CFG(SSCG_CTRL_REG_2, 0xeb),
+};
+
+static const struct phy_init_tbl ipq5332_usb_uniphy_init_tbl[] = {
+ PHY_INIT_CFG(PCIE_USB_COMBO_PHY_CFG_RX_AFE_2, 0x1076),
+ PHY_INIT_CFG(PCIE_USB_COMBO_PHY_CFG_RX_DLF_DEMUX_2, 0x3142),
+ PHY_INIT_CFG(PCIE_USB_COMBO_PHY_CFG_MISC1, 0x3),
+};
+
+static const struct uniphy_cfg ipq5018_cfg = {
+ .init_seq = ipq5018_usb_uniphy_init_tbl,
+ .num_init_seq = ARRAY_SIZE(ipq5018_usb_uniphy_init_tbl),
+ .pipe_clk_rate = 250000000,
+};
+
+static const struct uniphy_cfg ipq5332_cfg = {
+ .init_seq = ipq5332_usb_uniphy_init_tbl,
+ .num_init_seq = ARRAY_SIZE(ipq5332_usb_uniphy_init_tbl),
+ .pipe_clk_rate = 250000000,
+};
+
+static int uniphy_usb_init(struct phy *phy)
+{
+ struct uniphy_usb *uniphy = phy_get_drvdata(phy);
+ const struct uniphy_cfg *cfg = uniphy->cfg;
+ const struct phy_init_tbl *tbl = cfg->init_seq;
+ void __iomem *base = uniphy->base;
+ struct device *dev = uniphy->dev;
+ int i, ret;
+
+ ret = regulator_enable(uniphy->vreg);
+ if (ret) {
+ dev_err(dev, "failed to enable regulator: %d\n", ret);
+ return ret;
+ }
+
+ /* Perform phy reset */
+ ret = reset_control_assert(uniphy->reset);
+ if (ret) {
+ dev_err(dev, "Failed to assert reset: %d\n", ret);
+ goto err_disable_regulator;
+ }
+
+ usleep_range(1, 5);
+
+ ret = reset_control_deassert(uniphy->reset);
+ if (ret) {
+ dev_err(dev, "Failed to deassert reset: %d\n", ret);
+ goto err_assert_reset;
+ }
+
+ ret = clk_bulk_prepare_enable(uniphy->num_clks, uniphy->clks);
+ if (ret) {
+ dev_err(dev, "failed to enable clocks: %d\n", ret);
+ goto err_assert_reset;
+ }
+
+ /* phy autoload delay */
+ usleep_range(35, 40);
+
+ for (i = 0; i < cfg->num_init_seq; i++)
+ writel(tbl[i].val, base + tbl[i].offset);
+
+ return 0;
+
+err_assert_reset:
+ /* Assert phy reset */
+ reset_control_assert(uniphy->reset);
+
+err_disable_regulator:
+ regulator_disable(uniphy->vreg);
+
+ return ret;
+}
+
+static int uniphy_usb_shutdown(struct phy *phy)
+{
+ struct uniphy_usb *uniphy = phy_get_drvdata(phy);
+
+ clk_bulk_disable_unprepare(uniphy->num_clks, uniphy->clks);
+
+ /* Assert phy reset */
+ reset_control_assert(uniphy->reset);
+
+ regulator_disable(uniphy->vreg);
+
+ return 0;
+}
+
+static const struct phy_ops uniphy_usb_ops = {
+ .power_on = uniphy_usb_init,
+ .power_off = uniphy_usb_shutdown,
+ .owner = THIS_MODULE,
+};
+
+/*
+ * Register a fixed rate pipe clock.
+ *
+ * The <s>_pipe_clksrc generated by PHY goes to the GCC that gate
+ * controls it. The <s>_pipe_clk coming out of the GCC is requested
+ * by the PHY driver for its operations.
+ * We register the <s>_pipe_clksrc here. The gcc driver takes care
+ * of assigning this <s>_pipe_clksrc as parent to <s>_pipe_clk.
+ * Below picture shows this relationship.
+ *
+ * +---------------+
+ * | PHY block |<<---------------------------------------+
+ * | | |
+ * | +-------+ | +-----+ |
+ * I/P---^-->| PLL |---^--->pipe_clksrc--->| GCC |--->pipe_clk---+
+ * clk | +-------+ | +-----+
+ * +---------------+
+ */
+static int phy_pipe_clk_register(struct uniphy_usb *uniphy, int id)
+{
+ struct device *dev = uniphy->dev;
+ struct clk_hw *hw;
+ char name[64];
+
+ snprintf(name, sizeof(name), "usbphy%d_pipe_clk_src", id);
+ hw = devm_clk_hw_register_fixed_rate(dev, name, NULL, 0,
+ uniphy->cfg->pipe_clk_rate);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw);
+}
+
+static int qcom_uniphy_usb_mux_select(struct uniphy_usb *uniphy, bool enable)
+{
+ return regmap_update_bits(uniphy->tcsr, uniphy->mux_offset,
+ TCSR_USB_MUX_SEL,
+ enable ? TCSR_USB_MUX_SEL : 0);
+}
+
+static int qcom_uniphy_usb_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct uniphy_usb *uniphy;
+ int ret;
+
+ uniphy = devm_kzalloc(dev, sizeof(*uniphy), GFP_KERNEL);
+ if (!uniphy)
+ return -ENOMEM;
+
+ uniphy->dev = dev;
+
+ uniphy->cfg = of_device_get_match_data(dev);
+ if (!uniphy->cfg)
+ return -EINVAL;
+
+ uniphy->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(uniphy->base))
+ return dev_err_probe(dev, PTR_ERR(uniphy->base),
+ "failed to map base\n");
+
+ ret = devm_clk_bulk_get_all(dev, &uniphy->clks);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to get clocks\n");
+
+ uniphy->num_clks = ret;
+
+ uniphy->reset = devm_reset_control_get_exclusive_by_index(dev, 0);
+ if (IS_ERR(uniphy->reset))
+ return dev_err_probe(dev, PTR_ERR(uniphy->reset),
+ "failed to get reset\n");
+
+ uniphy->vreg = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(uniphy->vreg))
+ return dev_err_probe(dev, PTR_ERR(uniphy->vreg),
+ "failed to get vreg\n");
+
+ uniphy->tcsr = syscon_regmap_lookup_by_phandle_args(dev->of_node,
+ "qcom,phy-usb-mux-sel",
+ 1, &uniphy->mux_offset);
+ if (IS_ERR(uniphy->tcsr)) {
+ ret = PTR_ERR(uniphy->tcsr);
+ uniphy->tcsr = NULL;
+ return dev_err_probe(dev, ret, "failed to get regmap\n");
+ }
+
+ /*
+ * Select the USB position of the phy mux before anything comes out
+ * of reset. Selecting it later leaves the SuperSpeed pads muxed away
+ * while the controller resets, and the SS link doesn't train and is
+ * stuck in Rx.Detect (USB2 is unaffected as it isn't muxed).
+ */
+ ret = qcom_uniphy_usb_mux_select(uniphy, true);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to select USB mux\n");
+
+ uniphy->phy = devm_phy_create(dev, NULL, &uniphy_usb_ops);
+ if (IS_ERR(uniphy->phy)) {
+ ret = PTR_ERR(uniphy->phy);
+ return dev_err_probe(dev, ret, "failed to create PHY\n");
+ }
+
+ ret = phy_pipe_clk_register(uniphy, uniphy->phy->id);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register pipe clk\n");
+
+ phy_set_drvdata(uniphy->phy, uniphy);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ ret = PTR_ERR_OR_ZERO(phy_provider);
+
+ return ret;
+}
+
+static void qcom_uniphy_usb_remove(struct platform_device *pdev)
+{
+ struct uniphy_usb *uniphy = platform_get_drvdata(pdev);
+
+ qcom_uniphy_usb_mux_select(uniphy, false);
+}
+
+static const struct of_device_id qcom_uniphy_usb_of_match[] = {
+ { .compatible = "qcom,ipq5018-uniphy-usb-ss-phy", .data = &ipq5018_cfg},
+ { .compatible = "qcom,ipq5332-uniphy-usb-ss-phy", .data = &ipq5332_cfg},
+ { },
+};
+MODULE_DEVICE_TABLE(of, qcom_uniphy_usb_of_match);
+
+static struct platform_driver qcom_uniphy_usb_driver = {
+ .probe = qcom_uniphy_usb_probe,
+ .remove = qcom_uniphy_usb_remove,
+ .driver = {
+ .of_match_table = qcom_uniphy_usb_of_match,
+ .name = "qcom,uniphy-usb-ss-phy",
+ }
+};
+module_platform_driver(qcom_uniphy_usb_driver);
+
+MODULE_DESCRIPTION("Qualcomm UNIPHY Super-Speed USB PHY driver");
+MODULE_LICENSE("GPL");
--
2.53.0