Shikra USB3 QMP PHY uses a single-PHY DT binding (#phy-cells = <0>), which is not supported by the existing QMP combo PHY driver.
Add a dedicated USB3-only QMP PHY driver for Shikra, porting the PHY initialization sequence and configuration tables from Linux drivers/phy/qualcomm/phy-qcom-qmp-usbc.c. The driver is selected through PHY_QCOM_QMP_USB3_LEGACY and enables USB3 PHY bring-up on Shikra platforms. Signed-off-by: Aswin Murugan <[email protected]> --- drivers/phy/qcom/Kconfig | 7 + drivers/phy/qcom/Makefile | 1 + drivers/phy/qcom/phy-qcom-qmp-usb3-legacy.c | 534 ++++++++++++++++++++ 3 files changed, 542 insertions(+) create mode 100644 drivers/phy/qcom/phy-qcom-qmp-usb3-legacy.c diff --git a/drivers/phy/qcom/Kconfig b/drivers/phy/qcom/Kconfig index 1fdadaccb12..ffb523fffbb 100644 --- a/drivers/phy/qcom/Kconfig +++ b/drivers/phy/qcom/Kconfig @@ -32,6 +32,13 @@ config PHY_QCOM_QMP_UFS help Enable this to support the UFS QMP PHY on various Qualcomm chipsets. +config PHY_QCOM_QMP_USB3_LEGACY + bool "Qualcomm QMP USB3-only legacy PHY driver" + depends on PHY && ARCH_SNAPDRAGON + help + Enable this to support the legacy USB3-only QMP PHY (v3) found on + older Qualcomm chipsets such as QCM2290/QCS615 and derivatives. + config PHY_QCOM_QUSB2 bool "Qualcomm USB QUSB2 PHY driver" depends on PHY && ARCH_SNAPDRAGON diff --git a/drivers/phy/qcom/Makefile b/drivers/phy/qcom/Makefile index 714013dc572..dfad1f1e56a 100644 --- a/drivers/phy/qcom/Makefile +++ b/drivers/phy/qcom/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_MSM8916_USB_PHY) += msm8916-usbh-phy.o obj-$(CONFIG_PHY_QCOM_QMP_COMBO) += phy-qcom-qmp-combo.o obj-$(CONFIG_PHY_QCOM_QMP_PCIE) += phy-qcom-qmp-pcie.o obj-$(CONFIG_PHY_QCOM_QMP_UFS) += phy-qcom-qmp-ufs.o +obj-$(CONFIG_PHY_QCOM_QMP_USB3_LEGACY) += phy-qcom-qmp-usb3-legacy.o obj-$(CONFIG_PHY_QCOM_QUSB2) += phy-qcom-qusb2.o obj-$(CONFIG_PHY_QCOM_USB_SNPS_FEMTO_V2) += phy-qcom-snps-femto-v2.o obj-$(CONFIG_PHY_QCOM_SNPS_EUSB2) += phy-qcom-snps-eusb2.o diff --git a/drivers/phy/qcom/phy-qcom-qmp-usb3-legacy.c b/drivers/phy/qcom/phy-qcom-qmp-usb3-legacy.c new file mode 100644 index 00000000000..d1aad6e7952 --- /dev/null +++ b/drivers/phy/qcom/phy-qcom-qmp-usb3-legacy.c @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <clk.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <dm/devres.h> +#include <dm/uclass.h> +#include <generic-phy.h> +#include <reset.h> +#include <power/regulator.h> +#include <usb/tcpm.h> +#include <asm/io.h> +#include <linux/bitops.h> +#include <linux/compat.h> +#include <linux/delay.h> +#include <linux/iopoll.h> +#include <linux/err.h> + +#include "phy-qcom-qmp-common.h" + +#include "phy-qcom-qmp.h" +#include "phy-qcom-qmp-pcs-misc-v3.h" + +/* QPHY_V3_PCS_MISC_TYPEC_CTRL-equivalent bit, legacy USB3-only PHY */ +#define SW_PORTSELECT_MUX BIT(1) +#define SW_PORTSELECT_VAL BIT(0) + +#define PHY_INIT_COMPLETE_TIMEOUT 10000 + +#define RESET_SETTLE_DELAY_US 10 + +struct qmp_usb3_legacy_offsets { + u16 serdes; + u16 tx; + u16 rx; + u16 tx2; + u16 rx2; + u16 pcs; + u16 pcs_misc; +}; + +static const struct qmp_usb3_legacy_offsets qmp_usb3_legacy_offsets_qcm2290 = { + .serdes = 0x000, + .tx = 0x200, + .rx = 0x400, + .tx2 = 0x600, + .rx2 = 0x800, + .pcs_misc = 0xa00, + .pcs = 0xc00, +}; + +static const struct qmp_phy_init_tbl qcm2290_usb3_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x14), + QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x30), + QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x06), + QMP_PHY_INIT_CFG(QSERDES_COM_RESETSM_CNTRL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_RESETSM_CNTRL2, 0x08), + QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03), + QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28), + QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80), + QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_CORECLK_DIV, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x15), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x34), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_EN, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_CFG, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0xde), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x07), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06), + QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_INITVAL, 0x80), + QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CTRL_BY_PSM, 0x01), +}; + +static const struct qmp_phy_init_tbl qcm2290_usb3_tx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_TX_HIGHZ_DRVR_EN, 0x10), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RCV_DETECT_LVL_2, 0x12), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_LANE_MODE_1, 0xc6), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_RX, 0x00), +}; + +static const struct qmp_phy_init_tbl qcm2290_usb3_rx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_PI_CONTROLS, 0x80), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_COUNT_LOW, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_COUNT_HIGH, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FO_GAIN, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_GAIN, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x75), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4e), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL4, 0x18), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_VGA_CAL_CNTRL2, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_CNTRL, 0x03), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_ENABLES, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_MODE_00, 0x00), +}; + +static const struct qmp_phy_init_tbl qcm2290_usb3_pcs_tbl[] = { + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V0, 0x9f), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V0, 0x17), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V0, 0x0f), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL2, 0x83), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL1, 0x02), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_L, 0x09), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_H_TOL, 0xa2), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_MAN_CODE, 0x85), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG1, 0xd1), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG2, 0x1f), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG3, 0x47), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RXEQTRAINING_WAIT_TIME, 0x75), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RXEQTRAINING_RUN_TIME, 0x13), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LFPS_TX_ECSTART_EQTLOCK, 0x86), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x04), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TSYNC_RSYNC_TIME, 0x44), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_L, 0xe7), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_H, 0x03), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_U3_L, 0x40), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_U3_H, 0x00), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_SIGDET_LVL, 0x88), +}; + +/* Per-compatible config; add a new instance here to support another SoC */ +struct qmp_usb3_legacy_cfg { + const struct qmp_usb3_legacy_offsets *offsets; + + const struct qmp_phy_init_tbl *serdes_tbl; + int serdes_tbl_num; + const struct qmp_phy_init_tbl *tx_tbl; + int tx_tbl_num; + const struct qmp_phy_init_tbl *rx_tbl; + int rx_tbl_num; + const struct qmp_phy_init_tbl *pcs_tbl; + int pcs_tbl_num; + + /* clock names fetched by clk_get_by_name(); "pipe" is fetched separately */ + const char * const *clk_list; + int num_clks; + + const char * const *vreg_list; + int num_vregs; +}; + +static const char * const shikra_usb3_clk_l[] = { + "cfg_ahb", "ref", "com_aux", +}; + +static const struct qmp_usb3_legacy_cfg shikra_usb3phy_cfg = { + .offsets = &qmp_usb3_legacy_offsets_qcm2290, + .serdes_tbl = qcm2290_usb3_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(qcm2290_usb3_serdes_tbl), + .tx_tbl = qcm2290_usb3_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(qcm2290_usb3_tx_tbl), + .rx_tbl = qcm2290_usb3_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(qcm2290_usb3_rx_tbl), + .pcs_tbl = qcm2290_usb3_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(qcm2290_usb3_pcs_tbl), + .clk_list = shikra_usb3_clk_l, + .num_clks = ARRAY_SIZE(shikra_usb3_clk_l), + .vreg_list = NULL, + .num_vregs = 0, +}; + +static const char * const talos_usb3_clk_l[] = { + "aux", "ref", "cfg_ahb", +}; + +static const char * const talos_usb3_vreg_l[] = { + "vdda-phy-supply", "vdda-pll-supply", +}; + +static const struct qmp_usb3_legacy_cfg talos_usb3phy_cfg = { + .offsets = &qmp_usb3_legacy_offsets_qcm2290, + .serdes_tbl = qcm2290_usb3_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(qcm2290_usb3_serdes_tbl), + .tx_tbl = qcm2290_usb3_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(qcm2290_usb3_tx_tbl), + .rx_tbl = qcm2290_usb3_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(qcm2290_usb3_rx_tbl), + .pcs_tbl = qcm2290_usb3_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(qcm2290_usb3_pcs_tbl), + .clk_list = talos_usb3_clk_l, + .num_clks = ARRAY_SIZE(talos_usb3_clk_l), + .vreg_list = talos_usb3_vreg_l, + .num_vregs = ARRAY_SIZE(talos_usb3_vreg_l), +}; + +struct qmp_usb3_legacy { + struct udevice *dev; + void __iomem *serdes; + void __iomem *tx; + void __iomem *rx; + void __iomem *tx2; + void __iomem *rx2; + void __iomem *pcs; + void __iomem *pcs_misc; + struct clk *clks; + struct clk pipe_clk; + int num_clks; + struct reset_ctl_bulk resets; + struct udevice **vregs; + int num_vregs; + const struct qmp_usb3_legacy_cfg *cfg; +}; + +static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg |= val; + writel(reg, base + offset); + readl(base + offset); +} + +static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg &= ~val; + writel(reg, base + offset); + readl(base + offset); +} + +/* Quiesce the PHY; shared by power_off() and power_on()'s error path */ +static void qmp_usb3_legacy_quiesce(struct qmp_usb3_legacy *qmp) +{ + qphy_clrbits(qmp->pcs, QPHY_V3_PCS_START_CONTROL, SERDES_START | PCS_START); + qphy_setbits(qmp->pcs, QPHY_V3_PCS_SW_RESET, SW_RESET); + qphy_clrbits(qmp->pcs, QPHY_V3_PCS_POWER_DOWN_CONTROL, SW_PWRDN); +} + +/* Fixed orientation by default; use TCPM orientation if available */ +static u32 qmp_usb3_legacy_get_portselect(struct qmp_usb3_legacy *qmp) +{ + u32 val = SW_PORTSELECT_MUX; + + if (IS_ENABLED(CONFIG_TYPEC_TCPM)) { + struct udevice *tcpm_dev; + enum typec_orientation orientation; + int ret; + + ret = uclass_first_device_check(UCLASS_TCPM, &tcpm_dev); + if (!ret && tcpm_dev) { + orientation = tcpm_get_orientation(tcpm_dev); + if (orientation == TYPEC_ORIENTATION_REVERSE) + val |= SW_PORTSELECT_VAL; + } + } + + return val; +} + +static int qmp_usb3_legacy_power_on(struct phy *phy) +{ + struct qmp_usb3_legacy *qmp = dev_get_priv(phy->dev); + const struct qmp_usb3_legacy_cfg *cfg = qmp->cfg; + void __iomem *status; + u32 val; + int ret; + + qphy_setbits(qmp->pcs, QPHY_V3_PCS_POWER_DOWN_CONTROL, SW_PWRDN); + + writel(qmp_usb3_legacy_get_portselect(qmp), qmp->pcs_misc); + + qmp_configure(qmp->dev, qmp->serdes, cfg->serdes_tbl, cfg->serdes_tbl_num); + + ret = clk_enable(&qmp->pipe_clk); + if (ret) { + dev_err(qmp->dev, "pipe_clk enable failed err=%d\n", ret); + return ret; + } + + qmp_configure_lane(qmp->dev, qmp->tx, cfg->tx_tbl, cfg->tx_tbl_num, 1); + qmp_configure_lane(qmp->dev, qmp->rx, cfg->rx_tbl, cfg->rx_tbl_num, 1); + + qmp_configure_lane(qmp->dev, qmp->tx2, cfg->tx_tbl, cfg->tx_tbl_num, 2); + qmp_configure_lane(qmp->dev, qmp->rx2, cfg->rx_tbl, cfg->rx_tbl_num, 2); + + qmp_configure(qmp->dev, qmp->pcs, cfg->pcs_tbl, cfg->pcs_tbl_num); + + qphy_clrbits(qmp->pcs, QPHY_V3_PCS_SW_RESET, SW_RESET); + + qphy_setbits(qmp->pcs, QPHY_V3_PCS_START_CONTROL, SERDES_START | PCS_START); + + status = qmp->pcs + QPHY_V3_PCS_PCS_STATUS; + ret = readl_poll_timeout(status, val, !(val & PHYSTATUS), + PHY_INIT_COMPLETE_TIMEOUT); + if (ret) { + dev_err(qmp->dev, "phy initialization timed-out\n"); + clk_disable(&qmp->pipe_clk); + qmp_usb3_legacy_quiesce(qmp); + return ret; + } + + return 0; +} + +static int qmp_usb3_legacy_power_off(struct phy *phy) +{ + struct qmp_usb3_legacy *qmp = dev_get_priv(phy->dev); + + clk_disable(&qmp->pipe_clk); + qmp_usb3_legacy_quiesce(qmp); + + return 0; +} + +static void qmp_usb3_legacy_clk_disable_upto(struct qmp_usb3_legacy *qmp, int upto) +{ + int i; + + for (i = 0; i < upto; i++) + clk_disable(&qmp->clks[i]); +} + +static int qmp_usb3_legacy_clk_init(struct qmp_usb3_legacy *qmp) +{ + const struct qmp_usb3_legacy_cfg *cfg = qmp->cfg; + struct udevice *dev = qmp->dev; + int num = cfg->num_clks; + int i, ret; + + qmp->clks = devm_kcalloc(dev, num, sizeof(*qmp->clks), GFP_KERNEL); + if (!qmp->clks) + return -ENOMEM; + + qmp->num_clks = 0; + + for (i = 0; i < num; i++) { + ret = clk_get_by_name(dev, cfg->clk_list[i], &qmp->clks[i]); + if (ret) { + dev_err(dev, "failed to get %s clock: %d\n", + cfg->clk_list[i], ret); + qmp_usb3_legacy_clk_disable_upto(qmp, i); + return ret; + } + + ret = clk_enable(&qmp->clks[i]); + if (ret) { + dev_err(dev, "failed to enable %s clock: %d\n", + cfg->clk_list[i], ret); + qmp_usb3_legacy_clk_disable_upto(qmp, i); + return ret; + } + } + qmp->num_clks = num; + + ret = clk_get_by_name(dev, "pipe", &qmp->pipe_clk); + if (ret) { + dev_err(dev, "failed to get pipe clock: %d\n", ret); + qmp_usb3_legacy_clk_disable_upto(qmp, qmp->num_clks); + return ret; + } + + return 0; +} + +static int qmp_usb3_legacy_vreg_init(struct qmp_usb3_legacy *qmp) +{ + const struct qmp_usb3_legacy_cfg *cfg = qmp->cfg; + struct udevice *dev = qmp->dev; + int i, ret; + + qmp->num_vregs = 0; + + if (!cfg->num_vregs) + return 0; + + qmp->vregs = devm_kcalloc(dev, cfg->num_vregs, sizeof(*qmp->vregs), GFP_KERNEL); + if (!qmp->vregs) + return -ENOMEM; + + for (i = 0; i < cfg->num_vregs; i++) { + ret = device_get_supply_regulator(dev, cfg->vreg_list[i], &qmp->vregs[i]); + if (ret) { + dev_err(dev, "failed to get regulator %s: %d\n", + cfg->vreg_list[i], ret); + return ret; + } + } + + qmp->num_vregs = cfg->num_vregs; + return 0; +} + +static int qmp_usb3_legacy_vreg_enable(struct qmp_usb3_legacy *qmp) +{ + int i, ret; + + for (i = 0; i < qmp->num_vregs; i++) { + ret = regulator_set_enable(qmp->vregs[i], true); + if (ret) { + dev_err(qmp->dev, "failed to enable regulator %d: %d\n", i, ret); + while (--i >= 0) + regulator_set_enable(qmp->vregs[i], false); + return ret; + } + } + + return 0; +} + +static void qmp_usb3_legacy_vreg_disable(struct qmp_usb3_legacy *qmp) +{ + int i; + + for (i = qmp->num_vregs - 1; i >= 0; i--) + regulator_set_enable(qmp->vregs[i], false); +} + +static int qmp_usb3_legacy_parse_dt(struct qmp_usb3_legacy *qmp) +{ + const struct qmp_usb3_legacy_cfg *cfg = qmp->cfg; + const struct qmp_usb3_legacy_offsets *offs = cfg->offsets; + struct udevice *dev = qmp->dev; + void __iomem *base; + int ret; + + base = (void __iomem *)dev_read_addr(dev); + if (IS_ERR(base)) + return PTR_ERR(base); + + qmp->serdes = base + offs->serdes; + qmp->tx = base + offs->tx; + qmp->rx = base + offs->rx; + qmp->tx2 = base + offs->tx2; + qmp->rx2 = base + offs->rx2; + qmp->pcs = base + offs->pcs; + qmp->pcs_misc = base + offs->pcs_misc; + + ret = qmp_usb3_legacy_vreg_init(qmp); + if (ret) + return ret; + + ret = qmp_usb3_legacy_vreg_enable(qmp); + if (ret) + return ret; + + ret = reset_get_bulk(dev, &qmp->resets); + if (ret) { + dev_err(dev, "failed to get resets: %d\n", ret); + goto err_disable_vregs; + } + + ret = reset_assert_bulk(&qmp->resets); + if (ret) { + dev_err(dev, "failed to assert resets: %d\n", ret); + goto err_disable_vregs; + } + + udelay(RESET_SETTLE_DELAY_US); + + ret = reset_deassert_bulk(&qmp->resets); + if (ret) { + dev_err(dev, "failed to deassert resets: %d\n", ret); + goto err_assert_resets; + } + + ret = qmp_usb3_legacy_clk_init(qmp); + if (ret) + goto err_assert_resets; + + return 0; + +err_assert_resets: + reset_assert_bulk(&qmp->resets); +err_disable_vregs: + qmp_usb3_legacy_vreg_disable(qmp); + + return ret; +} + +static int qmp_usb3_legacy_probe(struct udevice *dev) +{ + struct qmp_usb3_legacy *qmp = dev_get_priv(dev); + + qmp->dev = dev; + qmp->cfg = (const struct qmp_usb3_legacy_cfg *)dev_get_driver_data(dev); + if (!qmp->cfg) { + dev_err(dev, "failed to get PHY configuration\n"); + return -EINVAL; + } + + return qmp_usb3_legacy_parse_dt(qmp); +} + +static struct phy_ops qmp_usb3_legacy_ops = { + .power_on = qmp_usb3_legacy_power_on, + .power_off = qmp_usb3_legacy_power_off, +}; + +static const struct udevice_id qmp_usb3_legacy_ids[] = { + { + .compatible = "qcom,shikra-qmp-usb3-phy", + .data = (ulong)&shikra_usb3phy_cfg, + }, + { } +}; + +U_BOOT_DRIVER(qcom_qmp_usb3_legacy_phy) = { + .name = "qcom-qmp-usb3-legacy-phy", + .id = UCLASS_PHY, + .of_match = qmp_usb3_legacy_ids, + .ops = &qmp_usb3_legacy_ops, + .probe = qmp_usb3_legacy_probe, + .priv_auto = sizeof(struct qmp_usb3_legacy), +}; -- 2.34.1
