From cdfeda6ef7d40b5a5c7a61aab2241aa477ee6107 Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Thu, 22 Jan 2026 12:34:04 +0530 Subject: [PATCH 01/10] tee: optee: Export OP-TEE message UID check API OP-TEE message UID check API can be useful to know whether OP-TEE is enabled on not assuming the corresponding SMC call is properly handled if OP-TEE is not supported. This API can be used by platform code to know OP-TEE presence and on that basis OP-TEE DT node can be added as part of DT fixups for the OP-TEE driver probe to happen for both U-Boot and Linux. Signed-off-by: Sumit Garg --- drivers/tee/optee/core.c | 5 +++++ include/tee/optee.h | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/tee/optee/core.c b/drivers/tee/optee/core.c index 5fc0505c788d..4d67c948ec19 100644 --- a/drivers/tee/optee/core.c +++ b/drivers/tee/optee/core.c @@ -795,6 +795,11 @@ static optee_invoke_fn *get_invoke_func(struct udevice *dev) return ERR_PTR(-EINVAL); } +bool is_optee_smc_api(void) +{ + return is_optee_api(optee_smccc_smc); +} + static int optee_of_to_plat(struct udevice *dev) { struct optee_pdata *pdata = dev_get_plat(dev); diff --git a/include/tee/optee.h b/include/tee/optee.h index 77729450bb6b..d11944937808 100644 --- a/include/tee/optee.h +++ b/include/tee/optee.h @@ -65,4 +65,13 @@ static inline int optee_copy_fdt_nodes(void *new_blob) } #endif +#if defined(CONFIG_OPTEE) +bool is_optee_smc_api(void); +#else +static inline bool is_optee_smc_api(void) +{ + return false; +} +#endif + #endif /* _OPTEE_H */ From 53cd27d566ceb99ecf5bd8c70600584c0575e22d Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Mon, 29 Dec 2025 16:16:47 +0530 Subject: [PATCH 02/10] mach-snapdragon: of_fixup: Add OP-TEE DT fixup support Add support for OP-TEE live tree DT fixup support which enables U-Boot OP-TEE driver to be probed. As well as the EFI DT fixup protocol allows the live tree fixup to be carried over to the OS for the OP-TEE driver in the OS to probe as well. Note that this fixup only gets applied if OP-TEE support is detected via checking for OP-TEE message UID. Signed-off-by: Sumit Garg --- arch/arm/mach-snapdragon/of_fixup.c | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/arch/arm/mach-snapdragon/of_fixup.c b/arch/arm/mach-snapdragon/of_fixup.c index 5b6076ea8e57..644afc22d886 100644 --- a/arch/arm/mach-snapdragon/of_fixup.c +++ b/arch/arm/mach-snapdragon/of_fixup.c @@ -25,6 +25,7 @@ #include #include #include +#include #include /* U-Boot only supports USB high-speed mode on Qualcomm platforms with DWC3 @@ -164,6 +165,37 @@ static void fixup_power_domains(struct device_node *root) } } +static void add_optee_node(struct device_node *root) +{ + struct device_node *fw = NULL, *optee = NULL; + int ret; + + fw = of_find_node_by_path("/firmware"); + if (!fw) { + log_err("Failed to find /firmware node\n"); + return; + } + + ret = of_add_subnode(fw, "optee", strlen("optee") + 1, &optee); + if (ret) { + log_err("Failed to add 'optee' subnode: %d\n", ret); + return; + } + + ret = of_write_prop(optee, "compatible", strlen("linaro,optee-tz") + 1, + "linaro,optee-tz"); + if (ret) { + log_err("Failed to add optee 'compatible' property: %d\n", ret); + return; + } + + ret = of_write_prop(optee, "method", strlen("smc") + 1, "smc"); + if (ret) { + log_err("Failed to add optee 'method' property: %d\n", ret); + return; + } +} + #define time_call(func, ...) \ do { \ u64 start = timer_get_us(); \ @@ -178,6 +210,9 @@ static int qcom_of_fixup_nodes(void * __maybe_unused ctx, struct event *event) time_call(fixup_usb_nodes, root); time_call(fixup_power_domains, root); + if (IS_ENABLED(CONFIG_OPTEE) && is_optee_smc_api()) + time_call(add_optee_node, root); + return 0; } From 387b3e2f37a06337789531ffaaf3ac3e3c573ce2 Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Thu, 22 Jan 2026 13:08:25 +0530 Subject: [PATCH 03/10] board/qualcomm: Introduce TF-A and OP-TEE config fragment Recently upstream TF-A/OP-TEE has started gaining support for Qcom platforms. RB3Gen2 being the first one and more to come. U-Boot in corresponding boot flow is packaged as a position independent executable. So, lets add a generic U-Boot config fragment for Qcom platforms to support TF-A/OP-TEE based TrustZone stack. Build command: $ ./scripts/kconfig/merge_config.sh \ configs/qcom_defconfig \ board/qualcomm/tfa-optee.config $ make -j`nproc` DEVICE_TREE=qcom/qcs6490-rb3gen2 For more information refer here: https://trustedfirmware-a.readthedocs.io/en/latest/plat/qti/rb3gen2.html Signed-off-by: Sumit Garg --- board/qualcomm/tfa-optee.config | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 board/qualcomm/tfa-optee.config diff --git a/board/qualcomm/tfa-optee.config b/board/qualcomm/tfa-optee.config new file mode 100644 index 000000000000..1e8364c114f9 --- /dev/null +++ b/board/qualcomm/tfa-optee.config @@ -0,0 +1,4 @@ +# Enables support for TF-A based OP-TEE as the open +# source TrustZone stack on Qcom platforms +CONFIG_TEE=y +CONFIG_OPTEE=y From 4b35eca114c2b16d67f09dd20b1575c0225829c7 Mon Sep 17 00:00:00 2001 From: Varadarajan Narayanan Date: Thu, 22 Jan 2026 12:04:42 +0530 Subject: [PATCH 04/10] fs: fat: Handle 'FAT sector size mismatch' Do FAT read and write based on the device sector size instead of the size recorded in the FAT meta data. FAT code issues i/o in terms of the sector size. Convert that to device sector size before doing the actual i/o. Additionally, handle leading/trailing blocks when the meta data based block no and i/o size is not an exact multiple of the device sector size or vice versa. Tested on UFS device with sector size 4096 and meta data recorded sector size 512. Signed-off-by: Varadarajan Narayanan --- fs/fat/fat.c | 218 ++++++++++++++++++++++++++++++++++++++++++--- fs/fat/fat_write.c | 19 ---- 2 files changed, 205 insertions(+), 32 deletions(-) diff --git a/fs/fat/fat.c b/fs/fat/fat.c index 9ce5df59f9ba..05acd24d8bd4 100644 --- a/fs/fat/fat.c +++ b/fs/fat/fat.c @@ -45,24 +45,214 @@ static void downcase(char *str, size_t len) static struct blk_desc *cur_dev; static struct disk_partition cur_part_info; +static int fat_sect_size; #define DOS_BOOT_MAGIC_OFFSET 0x1fe #define DOS_FS_TYPE_OFFSET 0x36 #define DOS_FS32_TYPE_OFFSET 0x52 -static int disk_read(__u32 block, __u32 nr_blocks, void *buf) +inline __u32 sect_to_block(__u32 sect, __u32 *off) { - ulong ret; + *off = 0; + if (fat_sect_size && fat_sect_size < cur_part_info.blksz) { + int div = cur_part_info.blksz / fat_sect_size; + + *off = sect % div; + return sect / div; + } else if (fat_sect_size && (fat_sect_size > cur_part_info.blksz)) { + return sect * (fat_sect_size / cur_part_info.blksz); + } - if (!cur_dev) - return -1; + return sect; +} - ret = blk_dread(cur_dev, cur_part_info.start + block, nr_blocks, buf); +inline __u32 size_to_blocks(__u32 size) +{ + return (size + (cur_part_info.blksz - 1)) / cur_part_info.blksz; +} - if (ret != nr_blocks) - return -1; +static int disk_read(__u32 sect, __u32 nr_sect, void *buf) +{ + int ret; + __u8 *block = NULL; + __u32 rem, size; + __u32 s, n; - return ret; + rem = nr_sect * fat_sect_size; + /* + * block N block N + 1 block N + 2 + * +-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | | | |s|e|c|t|o|r| | |s|e|c|t|o|r| | |s|e|c|t|o|r| | | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * . . . | | | | . . . + * ------+---------------+---------------+---------------+------ + * |<--- FAT reads in sectors --->| + * + * | part 1 | part 2 | part 3 | + * + */ + + /* Do part 1 */ + if (fat_sect_size) { + __u32 offset; + + /* Read one block and copy out the leading sectors */ + block = malloc_cache_aligned(cur_dev->blksz); + if (!block) { + printf("Error: allocating block: %lu\n", cur_dev->blksz); + return -1; + } + + s = sect_to_block(sect, &offset); + offset = offset * fat_sect_size; + + ret = blk_dread(cur_dev, cur_part_info.start + s, 1, block); + if (ret != 1) { + ret = -1; + goto exit; + } + + if (rem > (cur_part_info.blksz - offset)) + size = cur_part_info.blksz - offset; + else + size = rem; + + memcpy(buf, block + offset, size); + rem -= size; + buf += size; + s++; + } else { + /* + * fat_sect_size not being set implies, this is the first read + * to partition. The first sector is being read to get the + * FS meta data. The FAT sector size is got from this meta data. + */ + ret = blk_dread(cur_dev, cur_part_info.start + s, 1, buf); + if (ret != 1) + return -1; + } + + /* Do part 2, read directly into the given buffer */ + if (rem > cur_part_info.blksz) { + n = rem / cur_part_info.blksz; + ret = blk_dread(cur_dev, cur_part_info.start + s, n, buf); + if (ret != n) { + ret = -1; + goto exit; + } + buf += n * cur_part_info.blksz; + rem = rem % cur_part_info.blksz; + s += n; + } + + /* Do part 3, read a block and copy the trailing sectors */ + if (rem) { + ret = blk_dread(cur_dev, cur_part_info.start + s, 1, block); + if (ret != 1) { + ret = -1; + goto exit; + } else { + memcpy(buf, block, rem); + } + } +exit: + if (block) + free(block); + + return (ret == -1) ? -1 : nr_sect; +} + +int disk_write(__u32 sect, __u32 nr_sect, void *buf) +{ + int ret; + __u8 *block = NULL; + __u32 rem, size; + __u32 s, n; + + rem = nr_sect * fat_sect_size; + /* + * block N block N + 1 block N + 2 + * +-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | | | |s|e|c|t|o|r| | |s|e|c|t|o|r| | |s|e|c|t|o|r| | | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * . . . | | | | . . . + * ------+---------------+---------------+---------------+------ + * |<--- FAT reads in sectors --->| + * + * | part 1 | part 2 | part 3 | + * + */ + + /* Do part 1 */ + if (fat_sect_size) { + __u32 offset; + + /* Read one block and overwrite the leading sectors */ + block = malloc_cache_aligned(cur_dev->blksz); + if (!block) { + printf("Error: allocating block: %lu\n", cur_dev->blksz); + return -1; + } + + s = sect_to_block(sect, &offset); + offset = offset * fat_sect_size; + + ret = blk_dread(cur_dev, cur_part_info.start + s, 1, block); + if (ret != 1) { + ret = -1; + goto exit; + } + + if (rem > (cur_part_info.blksz - offset)) + size = cur_part_info.blksz - offset; + else + size = rem; + + memcpy(block + offset, buf, size); + ret = blk_dwrite(cur_dev, cur_part_info.start + s, 1, block); + if (ret != 1) { + ret = -1; + goto exit; + } + + rem -= size; + buf += size; + s++; + } + + /* Do part 2, write directly from the given buffer */ + if (rem > cur_part_info.blksz) { + n = rem / cur_part_info.blksz; + ret = blk_dwrite(cur_dev, cur_part_info.start + s, n, buf); + if (ret != n) { + ret = -1; + goto exit; + } + buf += n * cur_part_info.blksz; + rem = rem % cur_part_info.blksz; + s += n; + } + + /* Do part 3, read a block and copy the trailing sectors */ + if (rem) { + ret = blk_dread(cur_dev, cur_part_info.start + s, 1, block); + if (ret != 1) { + ret = -1; + goto exit; + } else { + memcpy(block, buf, rem); + } + ret = blk_dwrite(cur_dev, cur_part_info.start + s, 1, block); + if (ret != 1) { + ret = -1; + goto exit; + } + } +exit: + if (block) + free(block); + + return (ret == -1) ? -1 : nr_sect; } int fat_set_blk_dev(struct blk_desc *dev_desc, struct disk_partition *info) @@ -581,6 +771,8 @@ read_bootsectandvi(boot_sector *bs, volume_info *volinfo, int *fatsize) return -1; } + fat_sect_size = 0; + if (disk_read(0, 1, block) < 0) { debug("Error: reading block\n"); ret = -1; @@ -651,12 +843,12 @@ static int get_fs_info(fsdata *mydata) mydata->rootdir_sect = mydata->fat_sect + mydata->fatlength * bs.fats; mydata->sect_size = get_unaligned_le16(bs.sector_size); + fat_sect_size = mydata->sect_size; mydata->clust_size = bs.cluster_size; - if (mydata->sect_size != cur_part_info.blksz) { - log_err("FAT sector size mismatch (fs=%u, dev=%lu)\n", - mydata->sect_size, cur_part_info.blksz); - return -1; - } + if (mydata->sect_size != cur_part_info.blksz) + log_info("FAT sector size mismatch (fs=%u, dev=%lu)\n", + mydata->sect_size, cur_part_info.blksz); + if (mydata->clust_size == 0) { log_err("FAT cluster size not set\n"); return -1; diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index 0b924541187c..b686afd32c69 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -192,25 +192,6 @@ static int set_name(fat_itr *itr, const char *filename, char *shortname) } static int total_sector; -static int disk_write(__u32 block, __u32 nr_blocks, void *buf) -{ - ulong ret; - - if (!cur_dev) - return -1; - - if (cur_part_info.start + block + nr_blocks > - cur_part_info.start + total_sector) { - printf("error: overflow occurs\n"); - return -1; - } - - ret = blk_dwrite(cur_dev, cur_part_info.start + block, nr_blocks, buf); - if (nr_blocks && ret == 0) - return -1; - - return ret; -} /* * Write fat buffer into block device From 2f08b1ceed19ceb15681d365db226ca4b1ce8c8b Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Fri, 14 Nov 2025 12:10:42 +0530 Subject: [PATCH 05/10] dts: qcs6490-rb3gen2: Switch USB controller to peripheral mode Change USB controller dr_mode from "host" to "peripheral" to enable fastboot support on the QCS6490 RB3Gen2 board. Signed-off-by: Balaji Selvanathan --- arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi b/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi index 8d4871135fa6..227e7d6ff898 100644 --- a/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi +++ b/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi @@ -18,7 +18,7 @@ }; &usb_1 { - dr_mode = "host"; + dr_mode = "peripheral"; }; // RAM Entry 0 : Base 0x0080000000 Size 0x003A800000 From 16302edee049fcafd3298d5e5e719302adc2d112 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Wed, 3 Dec 2025 16:37:30 +0530 Subject: [PATCH 06/10] drivers: clk: qcom: sc7280: Add USB3 PHY pipe clock Add support for GCC_USB3_PRIM_PHY_PIPE_CLK which is required by the USB3 PHY on SC7280/QCM6490 platforms. Signed-off-by: Balaji Selvanathan Reviewed-by: Neil Armstrong Reviewed-by: Casey Connolly --- drivers/clk/qcom/clock-sc7280.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/qcom/clock-sc7280.c b/drivers/clk/qcom/clock-sc7280.c index 7b6ed8260236..403995e5a0aa 100644 --- a/drivers/clk/qcom/clock-sc7280.c +++ b/drivers/clk/qcom/clock-sc7280.c @@ -116,6 +116,7 @@ static const struct gate_clk sc7280_clks[] = { GATE_CLK(GCC_USB30_PRIM_MOCK_UTMI_CLK, 0xf01c, 1), GATE_CLK(GCC_USB3_PRIM_PHY_AUX_CLK, 0xf054, 1), GATE_CLK(GCC_USB3_PRIM_PHY_COM_AUX_CLK, 0xf058, 1), + GATE_CLK(GCC_USB3_PRIM_PHY_PIPE_CLK, 0xf05c, 1), GATE_CLK(GCC_CFG_NOC_USB3_SEC_AXI_CLK, 0x9e07c, 1), GATE_CLK(GCC_USB30_SEC_MASTER_CLK, 0x9e010, 1), GATE_CLK(GCC_AGGRE_USB3_SEC_AXI_CLK, 0x9e080, 1), From 33b066592fc9c65847e5431411342e442ad6ccfe Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Wed, 3 Dec 2025 16:37:31 +0530 Subject: [PATCH 07/10] drivers: usb: dwc3: Add delay after core soft reset Add a 100 ms delay after clearing the core soft reset bit to ensure the DWC3 controller has sufficient time to complete its reset sequence before subsequent register accesses. Without this delay, USB initialization can fail on some Qualcomm platforms, particularly when using super-speed capable PHYs like the QMP USB3-DP Combo PHY on SC7280/QCM6490. Taken from Linux commit f88359e1588b ("usb: dwc3: core: Do core softreset when switch mode") Signed-off-by: Balaji Selvanathan Reviewed-by: Varadarajan Narayanan --- drivers/usb/dwc3/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 847fa1f82c37..ff0bca0dd8e8 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -94,6 +94,8 @@ static int dwc3_core_soft_reset(struct dwc3 *dwc) reg &= ~DWC3_GCTL_CORESOFTRESET; dwc3_writel(dwc->regs, DWC3_GCTL, reg); + mdelay(100); + return 0; } From fff7bdd5cd785d5c401d29fe53f22d0c32790f92 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Wed, 3 Dec 2025 16:37:32 +0530 Subject: [PATCH 08/10] drivers: phy: qcom: Add QMP USB3-DP Combo PHY driver Add support for the Qualcomm QMP USB3-DP Combo PHY found on SC7280 and QCM6490 platforms. This driver currently implements USB3 super-speed functionality of the combo PHY. The QMP Combo PHY is a dual-mode PHY that can operate in either USB3 mode or DisplayPort mode. This initial implementation focuses on USB3 mode to enable Super-Speed USB support. Taken from Linux commit 3d25d46a255a ("pmdomain: qcom: rpmhpd: Add rpmhpd support for SM8750") This patch is dependent on this patch: https://lore.kernel.org/u-boot/20251112164204.1557934-1-aswin.murugan@oss.qualcomm.com/ Enabled and tested the driver on Qualcomm RB3 Gen2 (QCS6490) board. Signed-off-by: Balaji Selvanathan Reviewed-by: Varadarajan Narayanan --- drivers/phy/qcom/Kconfig | 8 + drivers/phy/qcom/Makefile | 1 + drivers/phy/qcom/phy-qcom-qmp-combo.c | 644 +++++++++++++++++++++ drivers/phy/qcom/phy-qcom-qmp-common.h | 62 ++ drivers/phy/qcom/phy-qcom-qmp-dp-com-v3.h | 18 + drivers/phy/qcom/phy-qcom-qmp-pcs-usb-v4.h | 34 ++ drivers/phy/qcom/phy-qcom-qmp.h | 17 + 7 files changed, 784 insertions(+) create mode 100644 drivers/phy/qcom/phy-qcom-qmp-combo.c create mode 100644 drivers/phy/qcom/phy-qcom-qmp-common.h create mode 100644 drivers/phy/qcom/phy-qcom-qmp-dp-com-v3.h create mode 100644 drivers/phy/qcom/phy-qcom-qmp-pcs-usb-v4.h diff --git a/drivers/phy/qcom/Kconfig b/drivers/phy/qcom/Kconfig index 0dd69f7ffd0c..49f830abf01a 100644 --- a/drivers/phy/qcom/Kconfig +++ b/drivers/phy/qcom/Kconfig @@ -12,6 +12,14 @@ config PHY_QCOM_IPQ4019_USB help Support for the USB PHY-s on Qualcomm IPQ40xx SoC-s. +config PHY_QCOM_QMP_COMBO + bool "Qualcomm QMP USB3-DP Combo PHY driver" + depends on PHY && ARCH_SNAPDRAGON + help + Enable this to support the USB3-DP Combo QMP PHY on various Qualcomm + chipsets. This driver supports the USB3 PHY functionality of the combo + PHY (USB3 + DisplayPort). Currently only USB3 mode is supported. + config PHY_QCOM_QMP_PCIE tristate "Qualcomm QMP PCIe PHY driver" depends on PHY && ARCH_SNAPDRAGON diff --git a/drivers/phy/qcom/Makefile b/drivers/phy/qcom/Makefile index 1c4e7d8d3917..714013dc572d 100644 --- a/drivers/phy/qcom/Makefile +++ b/drivers/phy/qcom/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_PHY_QCOM_IPQ4019_USB) += phy-qcom-ipq4019-usb.o 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_QUSB2) += phy-qcom-qusb2.o diff --git a/drivers/phy/qcom/phy-qcom-qmp-combo.c b/drivers/phy/qcom/phy-qcom-qmp-combo.c new file mode 100644 index 000000000000..0095cbf8b4c3 --- /dev/null +++ b/drivers/phy/qcom/phy-qcom-qmp-combo.c @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phy-qcom-qmp-common.h" + +#include "phy-qcom-qmp.h" +#include "phy-qcom-qmp-pcs-misc-v3.h" +#include "phy-qcom-qmp-pcs-usb-v4.h" +#include "phy-qcom-qmp-dp-com-v3.h" + +/* QPHY_V3_DP_COM_RESET_OVRD_CTRL register bits */ +/* DP PHY soft reset */ +#define SW_DPPHY_RESET BIT(0) +/* mux to select DP PHY reset control, 0:HW control, 1: software reset */ +#define SW_DPPHY_RESET_MUX BIT(1) +/* USB3 PHY soft reset */ +#define SW_USB3PHY_RESET BIT(2) +/* mux to select USB3 PHY reset control, 0:HW control, 1: software reset */ +#define SW_USB3PHY_RESET_MUX BIT(3) + +/* QPHY_V3_DP_COM_PHY_MODE_CTRL register bits */ +#define USB3_MODE BIT(0) /* enables USB3 mode */ +#define DP_MODE BIT(1) /* enables DP mode */ + +/* QPHY_V3_DP_COM_TYPEC_CTRL register bits */ +#define SW_PORTSELECT_MUX BIT(1) + +/* PHY slot identifiers for device tree phandle arguments */ +#define QMP_USB43DP_USB3_PHY 0 +#define QMP_USB43DP_DP_PHY 1 + +#define PHY_INIT_COMPLETE_TIMEOUT 10000 + +struct qmp_combo_offsets { + u16 com; + u16 txa; + u16 rxa; + u16 txb; + u16 rxb; + u16 usb3_serdes; + u16 usb3_pcs_misc; + u16 usb3_pcs; + u16 usb3_pcs_usb; +}; + +/* + * Initialisation tables + */ + +static const struct qmp_combo_offsets qmp_combo_offsets_v3 = { + .com = 0x0000, + .txa = 0x1200, + .rxa = 0x1400, + .txb = 0x1600, + .rxb = 0x1800, + .usb3_serdes = 0x1000, + .usb3_pcs_misc = 0x1a00, + .usb3_pcs = 0x1c00, + .usb3_pcs_usb = 0x1f00, +}; + +static const struct qmp_phy_init_tbl sm8150_usb3_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_EN_CENTER, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_PER1, 0x31), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_PER2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_STEP_SIZE1_MODE0, 0xde), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_STEP_SIZE2_MODE0, 0x07), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_STEP_SIZE1_MODE1, 0xde), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_SSC_STEP_SIZE2_MODE1, 0x07), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_SYSCLK_BUF_ENABLE, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_CMN_IPTRIM, 0x20), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_CP_CTRL_MODE0, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_CP_CTRL_MODE1, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_PLL_RCTRL_MODE1, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_PLL_CCTRL_MODE0, 0x36), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_PLL_CCTRL_MODE1, 0x36), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_SYSCLK_EN_SEL, 0x1a), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_LOCK_CMP_EN, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_LOCK_CMP1_MODE0, 0x14), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_LOCK_CMP2_MODE0, 0x34), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_LOCK_CMP1_MODE1, 0x34), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_LOCK_CMP2_MODE1, 0x82), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_DEC_START_MODE1, 0x82), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_DIV_FRAC_START1_MODE0, 0xab), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_DIV_FRAC_START2_MODE0, 0xea), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_DIV_FRAC_START3_MODE0, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_VCO_TUNE_MAP, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_DIV_FRAC_START1_MODE1, 0xab), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_DIV_FRAC_START2_MODE1, 0xea), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_DIV_FRAC_START3_MODE1, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_VCO_TUNE1_MODE0, 0x24), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_VCO_TUNE1_MODE1, 0x24), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_VCO_TUNE2_MODE1, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_HSCLK_SEL, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_CORECLK_DIV_MODE1, 0x08), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_BIN_VCOCAL_CMP_CODE1_MODE0, 0xca), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_BIN_VCOCAL_CMP_CODE2_MODE0, 0x1e), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_BIN_VCOCAL_CMP_CODE1_MODE1, 0xca), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_BIN_VCOCAL_CMP_CODE2_MODE1, 0x1e), + QMP_PHY_INIT_CFG(QSERDES_V4_COM_BIN_VCOCAL_HSCLK_SEL, 0x11), +}; + +static const struct qmp_phy_init_tbl sm8250_usb3_tx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V4_TX_RES_CODE_LANE_TX, 0x60), + QMP_PHY_INIT_CFG(QSERDES_V4_TX_RES_CODE_LANE_RX, 0x60), + QMP_PHY_INIT_CFG(QSERDES_V4_TX_RES_CODE_LANE_OFFSET_TX, 0x11), + QMP_PHY_INIT_CFG(QSERDES_V4_TX_RES_CODE_LANE_OFFSET_RX, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V4_TX_LANE_MODE_1, 0xd5), + QMP_PHY_INIT_CFG(QSERDES_V4_TX_RCV_DETECT_LVL_2, 0x12), + QMP_PHY_INIT_CFG_LANE(QSERDES_V4_TX_PI_QEC_CTRL, 0x40, 1), + QMP_PHY_INIT_CFG_LANE(QSERDES_V4_TX_PI_QEC_CTRL, 0x54, 2), +}; + +static const struct qmp_phy_init_tbl sm8250_usb3_rx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SO_GAIN, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FASTLOCK_FO_GAIN, 0x2f), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x7f), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FASTLOCK_COUNT_LOW, 0xff), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_FASTLOCK_COUNT_HIGH, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_PI_CONTROLS, 0x99), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_THRESH1, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_THRESH2, 0x08), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_GAIN1, 0x05), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_UCDR_SB2_GAIN2, 0x05), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_VGA_CAL_CNTRL1, 0x54), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_VGA_CAL_CNTRL2, 0x0c), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4a), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQU_ADAPTOR_CNTRL4, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_IDAC_TSETTLE_LOW, 0xc0), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_IDAC_TSETTLE_HIGH, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_SIGDET_CNTRL, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_SIGDET_DEGLITCH_CNTRL, 0x0e), + QMP_PHY_INIT_CFG_LANE(QSERDES_V4_RX_RX_MODE_00_LOW, 0xff, 1), + QMP_PHY_INIT_CFG_LANE(QSERDES_V4_RX_RX_MODE_00_LOW, 0x7f, 2), + QMP_PHY_INIT_CFG_LANE(QSERDES_V4_RX_RX_MODE_00_HIGH, 0x7f, 1), + QMP_PHY_INIT_CFG_LANE(QSERDES_V4_RX_RX_MODE_00_HIGH, 0xff, 2), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH2, 0x7f), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH3, 0x7f), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_00_HIGH4, 0x97), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_LOW, 0xdc), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH, 0xdc), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH2, 0x5c), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH3, 0x7b), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH4, 0xb4), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_DFE_EN_TIMER, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_DFE_CTLE_POST_CAL_OFFSET, 0x38), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_AUX_DATA_TCOARSE_TFINE, 0xa0), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_DCC_CTRL1, 0x0c), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_GM_CAL, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_V4_RX_VTH_CODE, 0x10), +}; + +static const struct qmp_phy_init_tbl sm8250_usb3_pcs_tbl[] = { + QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG1, 0xd0), + QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG2, 0x07), + QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG3, 0x20), + QMP_PHY_INIT_CFG(QPHY_V4_PCS_LOCK_DETECT_CONFIG6, 0x13), + QMP_PHY_INIT_CFG(QPHY_V4_PCS_REFGEN_REQ_CONFIG1, 0x21), + QMP_PHY_INIT_CFG(QPHY_V4_PCS_RX_SIGDET_LVL, 0xa9), + QMP_PHY_INIT_CFG(QPHY_V4_PCS_CDR_RESET_TIME, 0x0a), + QMP_PHY_INIT_CFG(QPHY_V4_PCS_ALIGN_DETECT_CONFIG1, 0x88), + QMP_PHY_INIT_CFG(QPHY_V4_PCS_ALIGN_DETECT_CONFIG2, 0x13), + QMP_PHY_INIT_CFG(QPHY_V4_PCS_PCS_TX_RX_CONFIG, 0x0c), + QMP_PHY_INIT_CFG(QPHY_V4_PCS_EQ_CONFIG1, 0x4b), + QMP_PHY_INIT_CFG(QPHY_V4_PCS_EQ_CONFIG5, 0x10), +}; + +static const struct qmp_phy_init_tbl sm8250_usb3_pcs_usb_tbl[] = { + QMP_PHY_INIT_CFG(QPHY_V4_PCS_USB3_LFPS_DET_HIGH_COUNT_VAL, 0xf8), + QMP_PHY_INIT_CFG(QPHY_V4_PCS_USB3_RXEQTRAINING_DFE_TIME_S2, 0x07), +}; + +struct qmp_phy_cfg { + const struct qmp_combo_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; + const struct qmp_phy_init_tbl *pcs_usb_tbl; + int pcs_usb_tbl_num; + const char * const *vreg_list; + int num_vregs; + /* true, if PHY needs delay after POWER_DOWN */ + bool has_pwrdn_delay; +}; + +/* list of clocks required by phy */ +static const char * const qmp_combo_phy_clk_l[] = { + "aux", "com_aux", +}; + +/* list of regulators */ +static const char * const qmp_phy_vreg_l[] = { + "vdda-phy-supply", + "vdda-pll-supply", +}; + +struct qmp_combo { + struct udevice *dev; + void __iomem *com; + void __iomem *serdes; + void __iomem *tx; + void __iomem *rx; + void __iomem *tx2; + void __iomem *rx2; + void __iomem *pcs; + void __iomem *pcs_usb; + void __iomem *pcs_misc; + struct clk *clks; + struct clk *pipe_clk; + int num_clks; + struct reset_ctl_bulk resets; + int num_resets; + struct udevice **vregs; + int num_vregs; + const struct qmp_phy_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); + + /* ensure that above write is through */ + 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); + + /* ensure that above write is through */ + readl(base + offset); +} + +static int qmp_combo_com_exit(struct qmp_combo *qmp) +{ + int i, ret; + + for (i = 0; i < qmp->num_clks; i++) + clk_disable(&qmp->clks[i]); + + reset_assert_bulk(&qmp->resets); + + for (i = qmp->num_vregs - 1; i >= 0; i--) { + ret = regulator_set_enable(qmp->vregs[i], false); + if (ret) + dev_warn(qmp->dev, "failed to disable %s: %d\n", + qmp->cfg->vreg_list[i], ret); + } + + return 0; +} + +static int qmp_combo_com_init(struct qmp_combo *qmp) +{ + const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *com = qmp->com; + void __iomem *pcs = qmp->pcs; + u32 val; + int ret, i; + + ret = reset_assert_bulk(&qmp->resets); + if (ret) { + printf("Failed to assert resets: %d\n", ret); + return ret; + } + + ret = reset_deassert_bulk(&qmp->resets); + if (ret) { + printf("Failed to deassert resets: %d\n", ret); + return 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); + reset_assert_bulk(&qmp->resets); + return ret; + } + } + + for (i = 0; i < qmp->num_clks; i++) { + ret = clk_enable(&qmp->clks[i]); + if (ret) { + printf("Failed to enable clock %d: %d\n", i, ret); + while (--i >= 0) + clk_disable(&qmp->clks[i]); + for (i = qmp->num_vregs - 1; i >= 0; i--) + regulator_set_enable(qmp->vregs[i], false); + reset_assert_bulk(&qmp->resets); + return ret; + } + } + + /* Common block register writes */ + qphy_setbits(com, QPHY_V3_DP_COM_POWER_DOWN_CTRL, SW_PWRDN); + qphy_setbits(com, QPHY_V3_DP_COM_RESET_OVRD_CTRL, + SW_DPPHY_RESET_MUX | SW_DPPHY_RESET | + SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET); + + val = SW_PORTSELECT_MUX; + writel(val, com + QPHY_V3_DP_COM_TYPEC_CTRL); + + writel(USB3_MODE | DP_MODE, com + QPHY_V3_DP_COM_PHY_MODE_CTRL); + + qphy_clrbits(com, QPHY_V3_DP_COM_RESET_OVRD_CTRL, + SW_DPPHY_RESET_MUX | SW_DPPHY_RESET | + SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET); + + qphy_clrbits(com, QPHY_V3_DP_COM_SWI_CTRL, 0x03); + + qphy_clrbits(com, QPHY_V3_DP_COM_SW_RESET, SW_RESET); + + qphy_setbits(pcs, QPHY_V4_PCS_POWER_DOWN_CONTROL, SW_PWRDN); + + return 0; +} + +static int qmp_combo_usb_power_on(struct qmp_combo *qmp) +{ + const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *serdes = qmp->serdes; + void __iomem *tx = qmp->tx; + void __iomem *rx = qmp->rx; + void __iomem *tx2 = qmp->tx2; + void __iomem *rx2 = qmp->rx2; + void __iomem *pcs = qmp->pcs; + void __iomem *pcs_usb = qmp->pcs_usb; + u32 val; + int ret; + + /* Serdes configuration */ + qmp_configure(qmp->dev, serdes, cfg->serdes_tbl, cfg->serdes_tbl_num); + + ret = clk_prepare_enable(qmp->pipe_clk); + if (ret) { + dev_err(qmp->dev, "pipe_clk enable failed err=%d\n", ret); + return ret; + } + + /* Tx, Rx configurations */ + qmp_configure_lane(qmp->dev, tx, cfg->tx_tbl, cfg->tx_tbl_num, 1); + qmp_configure_lane(qmp->dev, tx2, cfg->tx_tbl, cfg->tx_tbl_num, 2); + + qmp_configure_lane(qmp->dev, rx, cfg->rx_tbl, cfg->rx_tbl_num, 1); + qmp_configure_lane(qmp->dev, rx2, cfg->rx_tbl, cfg->rx_tbl_num, 2); + + /* PCS configuration */ + qmp_configure(qmp->dev, pcs, cfg->pcs_tbl, cfg->pcs_tbl_num); + + if (pcs_usb) { + qmp_configure(qmp->dev, pcs_usb, + cfg->pcs_usb_tbl, + cfg->pcs_usb_tbl_num); + } + + if (cfg->has_pwrdn_delay) + udelay(20); + + /* Pull PHY out of reset */ + qphy_clrbits(pcs, QPHY_V4_PCS_SW_RESET, SW_RESET); + + /* Start SerDes and Phy-Coding-Sublayer */ + qphy_setbits(pcs, QPHY_V4_PCS_START_CONTROL, + SERDES_START | PCS_START); + + /* Wait for PHY initialization */ + ret = readl_poll_timeout(pcs + QPHY_V4_PCS_PCS_STATUS1, val, + !(val & PHYSTATUS), PHY_INIT_COMPLETE_TIMEOUT); + + if (ret) { + printf("QMP USB3 PHY initialization timeout\n"); + clk_disable(qmp->pipe_clk); + return ret; + } + + return 0; +} + +static int qmp_combo_power_on(struct phy *phy) +{ + struct qmp_combo *qmp = dev_get_priv(phy->dev); + int ret; + + /* Initialize common block */ + ret = qmp_combo_com_init(qmp); + if (ret) + return ret; + + /* Initialize USB3-specific configuration */ + ret = qmp_combo_usb_power_on(qmp); + if (ret) { + qmp_combo_com_exit(qmp); + return ret; + } + + return 0; +} + +static int qmp_combo_power_off(struct phy *phy) +{ + struct qmp_combo *qmp = dev_get_priv(phy->dev); + void __iomem *com = qmp->com; + + clk_disable(qmp->pipe_clk); + + /* PHY reset */ + qphy_setbits(qmp->pcs, QPHY_V4_PCS_SW_RESET, SW_RESET); + + /* Stop SerDes and Phy-Coding-Sublayer */ + qphy_clrbits(qmp->pcs, QPHY_V4_PCS_START_CONTROL, + SERDES_START | PCS_START); + + /* Put PHY into POWER DOWN state: active low */ + qphy_clrbits(qmp->pcs, QPHY_V4_PCS_POWER_DOWN_CONTROL, SW_PWRDN); + + /* Power down common block */ + qphy_clrbits(com, QPHY_V3_DP_COM_POWER_DOWN_CTRL, SW_PWRDN); + + return qmp_combo_com_exit(qmp); +} + +static int qmp_combo_reset_init(struct qmp_combo *qmp) +{ + struct udevice *dev = qmp->dev; + int ret; + + ret = reset_get_bulk(dev, &qmp->resets); + if (ret) { + printf("Failed to get resets: %d\n", ret); + return ret; + } + + qmp->num_resets = qmp->resets.count; + + return 0; +} + +static int qmp_combo_clk_init(struct qmp_combo *qmp) +{ + struct udevice *dev = qmp->dev; + int num = ARRAY_SIZE(qmp_combo_phy_clk_l); + int i, ret; + + qmp->clks = devm_kcalloc(dev, num, sizeof(*qmp->clks), GFP_KERNEL); + if (!qmp->clks) + return -ENOMEM; + + for (i = 0; i < num; i++) { + ret = clk_get_by_name(dev, qmp_combo_phy_clk_l[i], &qmp->clks[i]); + if (ret) { + dev_err(dev, "failed to get %s clock: %d\n", + qmp_combo_phy_clk_l[i], ret); + return ret; + } + } + + qmp->num_clks = num; + return 0; +} + +static int qmp_combo_vreg_init(struct qmp_combo *qmp) +{ + const struct qmp_phy_cfg *cfg = qmp->cfg; + struct udevice *dev = qmp->dev; + int num = cfg->num_vregs; + int i, ret; + + if (!num) + return 0; + + qmp->vregs = devm_kcalloc(dev, num, sizeof(*qmp->vregs), GFP_KERNEL); + if (!qmp->vregs) + return -ENOMEM; + + for (i = 0; i < num; 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 = num; + return 0; +} + +static int qmp_combo_parse_dt(struct qmp_combo *qmp) +{ + const struct qmp_phy_cfg *cfg = qmp->cfg; + const struct qmp_combo_offsets *offs = cfg->offsets; + struct udevice *dev = qmp->dev; + void __iomem *base; + int ret; + + if (!offs) + return -EINVAL; + + base = (void __iomem *)dev_read_addr(dev); + if (IS_ERR(base)) + return PTR_ERR(base); + + qmp->com = base + offs->com; + qmp->serdes = base + offs->usb3_serdes; + qmp->tx = base + offs->txa; + qmp->rx = base + offs->rxa; + qmp->tx2 = base + offs->txb; + qmp->rx2 = base + offs->rxb; + qmp->pcs = base + offs->usb3_pcs; + qmp->pcs_usb = base + offs->usb3_pcs_usb; + qmp->pcs_misc = base + offs->usb3_pcs_misc; + + ret = qmp_combo_clk_init(qmp); + if (ret) + return ret; + + qmp->pipe_clk = devm_clk_get(dev, "usb3_pipe"); + if (IS_ERR(qmp->pipe_clk)) { + dev_err(dev, "failed to get pipe clock (%ld)\n", + PTR_ERR(qmp->pipe_clk)); + return ret; + } + + ret = qmp_combo_reset_init(qmp); + if (ret) + return ret; + + ret = qmp_combo_vreg_init(qmp); + if (ret) + return ret; + + return 0; +} + +static int qmp_combo_probe(struct udevice *dev) +{ + struct qmp_combo *qmp = dev_get_priv(dev); + int ret; + + qmp->dev = dev; + qmp->cfg = (const struct qmp_phy_cfg *)dev_get_driver_data(dev); + if (!qmp->cfg) { + printf("Failed to get PHY configuration\n"); + return -EINVAL; + } + + ret = qmp_combo_parse_dt(qmp); + + return ret; +} + +static const struct qmp_phy_cfg sc7280_usb3dpphy_cfg = { + .offsets = &qmp_combo_offsets_v3, + .serdes_tbl = sm8150_usb3_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(sm8150_usb3_serdes_tbl), + .tx_tbl = sm8250_usb3_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(sm8250_usb3_tx_tbl), + .rx_tbl = sm8250_usb3_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(sm8250_usb3_rx_tbl), + .pcs_tbl = sm8250_usb3_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(sm8250_usb3_pcs_tbl), + .pcs_usb_tbl = sm8250_usb3_pcs_usb_tbl, + .pcs_usb_tbl_num = ARRAY_SIZE(sm8250_usb3_pcs_usb_tbl), + .vreg_list = qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), + + .has_pwrdn_delay = true, +}; + +static int qmp_combo_xlate(struct phy *phy, struct ofnode_phandle_args *args) +{ + if (args->args_count != 1) { + debug("Invalid args_count: %d\n", args->args_count); + return -EINVAL; + } + + /* We only support the USB3 phy at slot 0 */ + if (args->args[0] == QMP_USB43DP_DP_PHY) + return -EINVAL; + + phy->id = QMP_USB43DP_USB3_PHY; + + return 0; +} + +static struct phy_ops qmp_combo_ops = { + .init = qmp_combo_power_on, + .exit = qmp_combo_power_off, + .of_xlate = qmp_combo_xlate, +}; + +static const struct udevice_id qmp_combo_ids[] = { + { + .compatible = "qcom,sc7280-qmp-usb3-dp-phy", + .data = (ulong)&sc7280_usb3dpphy_cfg, + }, + { } +}; + +U_BOOT_DRIVER(qmp_combo) = { + .name = "qcom-qmp-usb3-dp-phy", + .id = UCLASS_PHY, + .of_match = qmp_combo_ids, + .ops = &qmp_combo_ops, + .probe = qmp_combo_probe, + .priv_auto = sizeof(struct qmp_combo), +}; diff --git a/drivers/phy/qcom/phy-qcom-qmp-common.h b/drivers/phy/qcom/phy-qcom-qmp-common.h new file mode 100644 index 000000000000..71356fb7dd02 --- /dev/null +++ b/drivers/phy/qcom/phy-qcom-qmp-common.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + */ + +#ifndef QCOM_PHY_QMP_COMMON_H_ +#define QCOM_PHY_QMP_COMMON_H_ + +struct qmp_phy_init_tbl { + unsigned int offset; + unsigned int val; + char *name; + /* + * mask of lanes for which this register is written + * for cases when second lane needs different values + */ + u8 lane_mask; +}; + +#define QMP_PHY_INIT_CFG(o, v) \ + { \ + .offset = o, \ + .val = v, \ + .name = #o, \ + .lane_mask = 0xff, \ + } + +#define QMP_PHY_INIT_CFG_LANE(o, v, l) \ + { \ + .offset = o, \ + .val = v, \ + .name = #o, \ + .lane_mask = l, \ + } + +static inline void qmp_configure_lane(struct udevice *dev, void __iomem *base, + const struct qmp_phy_init_tbl tbl[], + int num, u8 lane_mask) +{ + int i; + const struct qmp_phy_init_tbl *t = tbl; + + if (!t) + return; + + for (i = 0; i < num; i++, t++) { + if (!(t->lane_mask & lane_mask)) + continue; + + dev_dbg(dev, "Writing Reg: %s Offset: 0x%04x Val: 0x%02x\n", + t->name, t->offset, t->val); + writel(t->val, base + t->offset); + } +} + +static inline void qmp_configure(struct udevice *dev, void __iomem *base, + const struct qmp_phy_init_tbl tbl[], int num) +{ + qmp_configure_lane(dev, base, tbl, num, 0xff); +} + +#endif diff --git a/drivers/phy/qcom/phy-qcom-qmp-dp-com-v3.h b/drivers/phy/qcom/phy-qcom-qmp-dp-com-v3.h new file mode 100644 index 000000000000..396179ef38b0 --- /dev/null +++ b/drivers/phy/qcom/phy-qcom-qmp-dp-com-v3.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + */ + +#ifndef QCOM_PHY_QMP_DP_COM_V3_H_ +#define QCOM_PHY_QMP_DP_COM_V3_H_ + +/* Only for QMP V3 & V4 PHY - DP COM registers */ +#define QPHY_V3_DP_COM_PHY_MODE_CTRL 0x00 +#define QPHY_V3_DP_COM_SW_RESET 0x04 +#define QPHY_V3_DP_COM_POWER_DOWN_CTRL 0x08 +#define QPHY_V3_DP_COM_SWI_CTRL 0x0c +#define QPHY_V3_DP_COM_TYPEC_CTRL 0x10 +#define QPHY_V3_DP_COM_TYPEC_PWRDN_CTRL 0x14 +#define QPHY_V3_DP_COM_RESET_OVRD_CTRL 0x1c + +#endif diff --git a/drivers/phy/qcom/phy-qcom-qmp-pcs-usb-v4.h b/drivers/phy/qcom/phy-qcom-qmp-pcs-usb-v4.h new file mode 100644 index 000000000000..d7fd4ac0fc55 --- /dev/null +++ b/drivers/phy/qcom/phy-qcom-qmp-pcs-usb-v4.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + */ + +#ifndef QCOM_PHY_QMP_PCS_USB_V4_H_ +#define QCOM_PHY_QMP_PCS_USB_V4_H_ + +/* Only for QMP V4 PHY - USB3 PCS registers */ +#define QPHY_V4_PCS_USB3_POWER_STATE_CONFIG1 0x000 +#define QPHY_V4_PCS_USB3_AUTONOMOUS_MODE_STATUS 0x004 +#define QPHY_V4_PCS_USB3_AUTONOMOUS_MODE_CTRL 0x008 +#define QPHY_V4_PCS_USB3_AUTONOMOUS_MODE_CTRL2 0x00c +#define QPHY_V4_PCS_USB3_LFPS_RXTERM_IRQ_SOURCE_STATUS 0x010 +#define QPHY_V4_PCS_USB3_LFPS_RXTERM_IRQ_CLEAR 0x014 +#define QPHY_V4_PCS_USB3_LFPS_DET_HIGH_COUNT_VAL 0x018 +#define QPHY_V4_PCS_USB3_LFPS_TX_ECSTART 0x01c +#define QPHY_V4_PCS_USB3_LFPS_PER_TIMER_VAL 0x020 +#define QPHY_V4_PCS_USB3_LFPS_TX_END_CNT_U3_START 0x024 +#define QPHY_V4_PCS_USB3_RXEQTRAINING_LOCK_TIME 0x028 +#define QPHY_V4_PCS_USB3_RXEQTRAINING_WAIT_TIME 0x02c +#define QPHY_V4_PCS_USB3_RXEQTRAINING_CTLE_TIME 0x030 +#define QPHY_V4_PCS_USB3_RXEQTRAINING_WAIT_TIME_S2 0x034 +#define QPHY_V4_PCS_USB3_RXEQTRAINING_DFE_TIME_S2 0x038 +#define QPHY_V4_PCS_USB3_RCVR_DTCT_DLY_U3_L 0x03c +#define QPHY_V4_PCS_USB3_RCVR_DTCT_DLY_U3_H 0x040 +#define QPHY_V4_PCS_USB3_ARCVR_DTCT_EN_PERIOD 0x044 +#define QPHY_V4_PCS_USB3_ARCVR_DTCT_CM_DLY 0x048 +#define QPHY_V4_PCS_USB3_TXONESZEROS_RUN_LENGTH 0x04c +#define QPHY_V4_PCS_USB3_ALFPS_DEGLITCH_VAL 0x050 +#define QPHY_V4_PCS_USB3_SIGDET_STARTUP_TIMER_VAL 0x054 +#define QPHY_V4_PCS_USB3_TEST_CONTROL 0x058 + +#endif diff --git a/drivers/phy/qcom/phy-qcom-qmp.h b/drivers/phy/qcom/phy-qcom-qmp.h index 99f4d447caf6..06dac21ddc4a 100644 --- a/drivers/phy/qcom/phy-qcom-qmp.h +++ b/drivers/phy/qcom/phy-qcom-qmp.h @@ -12,12 +12,17 @@ #include "phy-qcom-qmp-qserdes-com-v3.h" #include "phy-qcom-qmp-qserdes-txrx-v3.h" +#include "phy-qcom-qmp-qserdes-com-v4.h" +#include "phy-qcom-qmp-qserdes-txrx-v4.h" + #include "phy-qcom-qmp-qserdes-pll.h" #include "phy-qcom-qmp-pcs-v2.h" #include "phy-qcom-qmp-pcs-v3.h" +#include "phy-qcom-qmp-pcs-v4.h" + /* Only for QMP V3 & V4 PHY - DP COM registers */ #define QPHY_V3_DP_COM_PHY_MODE_CTRL 0x00 #define QPHY_V3_DP_COM_SW_RESET 0x04 @@ -112,4 +117,16 @@ #define QSERDES_V6_DP_PHY_AUX_INTERRUPT_STATUS 0x0e0 #define QSERDES_V6_DP_PHY_STATUS 0x0e4 +/* QPHY_SW_RESET bit */ +#define SW_RESET BIT(0) +/* QPHY_POWER_DOWN_CONTROL */ +#define SW_PWRDN BIT(0) + +/* QPHY_START_CONTROL bits */ +#define SERDES_START BIT(0) +#define PCS_START BIT(1) + +/* QPHY_PCS_STATUS bit */ +#define PHYSTATUS BIT(6) + #endif From 758d44c69b147c7d44924c0433070b542b3870e7 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Wed, 3 Dec 2025 16:37:33 +0530 Subject: [PATCH 09/10] arch: arm: mach-snapdragon: Auto-detect USB SSPHY driver support Automatically detect super-speed USB PHY driver availability and skip the USB speed fixup if driver is available, eliminating the need for manual configuration. Previously, U-Boot unconditionally limited USB to high-speed mode on all Qualcomm platforms because most lacked super-speed PHY drivers. This change implements runtime detection that checks if a PHY driver exists for the super-speed PHY node referenced by the DWC3 controller. The fixup is automatically skipped when a compatible driver is found, allowing the hardware to operate at full capability. Platforms without super-speed PHY drivers continue to receive the fixup automatically. --- arch/arm/mach-snapdragon/of_fixup.c | 125 +++++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 12 deletions(-) diff --git a/arch/arm/mach-snapdragon/of_fixup.c b/arch/arm/mach-snapdragon/of_fixup.c index 644afc22d886..002fd45a10a5 100644 --- a/arch/arm/mach-snapdragon/of_fixup.c +++ b/arch/arm/mach-snapdragon/of_fixup.c @@ -4,7 +4,7 @@ * * This file implements runtime fixups for Qualcomm DT to improve * compatibility with U-Boot. This includes adjusting the USB nodes - * to only use USB high-speed. + * to only use USB high-speed if SSPHY driver is not available. * * We use OF_LIVE for this rather than early FDT fixup for a couple * of reasons: it has a much nicer API, is most likely more efficient, @@ -21,13 +21,101 @@ #include #include #include +#include +#include #include #include #include +#include #include #include #include +/** + * find_ssphy_node() - Find the super-speed PHY node referenced by DWC3 + * @dwc3: DWC3 device node + * + * Returns: Pointer to SS-PHY node if found, NULL otherwise + */ +static struct device_node *find_ssphy_node(struct device_node *dwc3) +{ + const __be32 *phandles; + const char *phy_name; + int len, i, ret; + + phandles = of_get_property(dwc3, "phys", &len); + if (!phandles) + return NULL; + + len /= sizeof(*phandles); + + /* Iterate through PHY phandles to find the SS-PHY */ + for (i = 0; i < len; i++) { + ret = of_property_read_string_index(dwc3, "phy-names", i, &phy_name); + if (ret) + continue; + + /* Check if this is the super-speed PHY */ + if (!strncmp("usb3-phy", phy_name, strlen("usb3-phy")) || + !strncmp("usb3_phy", phy_name, strlen("usb3_phy"))) { + return of_find_node_by_phandle(NULL, be32_to_cpu(phandles[i])); + } + } + + return NULL; +} + +/** + * has_driver_for_node() - Check if any PHY driver can bind to this node + * @np: Device node to check + * + * Returns: true if a PHY driver with matching compatible string exists, false otherwise + */ +static bool has_driver_for_node(struct device_node *np) +{ + struct driver *driver = ll_entry_start(struct driver, driver); + const int n_ents = ll_entry_count(struct driver, driver); + const char *compat_list, *compat; + int compat_length, i; + struct driver *entry; + + if (!np) + return false; + + /* Get compatible strings from the node */ + compat_list = of_get_property(np, "compatible", &compat_length); + if (!compat_list) + return false; + + /* Check each compatible string against PHY drivers only */ + for (i = 0; i < compat_length; i += strlen(compat) + 1) { + compat = compat_list + i; + + /* Iterate through all registered drivers */ + for (entry = driver; entry != driver + n_ents; entry++) { + const struct udevice_id *of_match = entry->of_match; + + /* Skip non-PHY drivers to improve performance */ + if (entry->id != UCLASS_PHY) + continue; + + if (!of_match) + continue; + + while (of_match->compatible) { + if (!strcmp(of_match->compatible, compat)) { + debug("Found PHY driver '%s' for SS-PHY compatible '%s'\n", + entry->name, compat); + return true; + } + of_match++; + } + } + } + + return false; +} + /* U-Boot only supports USB high-speed mode on Qualcomm platforms with DWC3 * USB controllers. Rather than requiring source level DT changes, we fix up * DT here. This improves compatibility with upstream DT and simplifies the @@ -35,7 +123,7 @@ */ static int fixup_qcom_dwc3(struct device_node *root, struct device_node *glue_np, bool flat) { - struct device_node *dwc3; + struct device_node *dwc3, *ssphy_np; int ret, len, hsphy_idx = 1; const __be32 *phandles; const char *second_phy_name; @@ -55,30 +143,43 @@ static int fixup_qcom_dwc3(struct device_node *root, struct device_node *glue_np } } - /* Tell the glue driver to configure the wrapper for high-speed only operation */ - ret = of_write_prop(glue_np, "qcom,select-utmi-as-pipe-clk", 0, NULL); - if (ret) { - log_err("Failed to add property 'qcom,select-utmi-as-pipe-clk': %d\n", ret); - return ret; - } + debug("Checking USB configuration for %s\n", glue_np->name); phandles = of_get_property(dwc3, "phys", &len); len /= sizeof(*phandles); if (len == 1) { - log_debug("Only one phy, not a superspeed controller\n"); + debug("Only one phy, not a superspeed controller\n"); return 0; } - /* Figure out if the superspeed phy is present and if so then which phy is it? */ + /* Figure out if the superspeed phy is present */ ret = of_property_read_string_index(dwc3, "phy-names", 1, &second_phy_name); if (ret == -ENODATA) { - log_debug("Only one phy, not a super-speed controller\n"); + debug("Only one phy, not a super-speed controller\n"); return 0; } else if (ret) { log_err("Failed to read second phy name: %d\n", ret); return ret; } + /* Find the super-speed PHY node and check if a driver is available */ + ssphy_np = find_ssphy_node(dwc3); + if (ssphy_np && has_driver_for_node(ssphy_np)) { + debug("Skipping USB fixup for %s (SS-PHY driver available)\n", + glue_np->name); + return 0; + } + + /* No driver available - apply the fixup */ + debug("Applying USB high-speed fixup to %s\n", glue_np->name); + + /* Tell the glue driver to configure the wrapper for high-speed only operation */ + ret = of_write_prop(glue_np, "qcom,select-utmi-as-pipe-clk", 0, NULL); + if (ret) { + log_err("Failed to add property 'qcom,select-utmi-as-pipe-clk': %d\n", ret); + return ret; + } + /* * Determine which phy is the superspeed phy by checking the name of the second phy * since it is typically the superspeed one. @@ -200,7 +301,7 @@ static void add_optee_node(struct device_node *root) do { \ u64 start = timer_get_us(); \ func(__VA_ARGS__); \ - debug(#func " took %lluus\n", timer_get_us() - start); \ + printf(#func " took %lluus\n", timer_get_us() - start); \ } while (0) static int qcom_of_fixup_nodes(void * __maybe_unused ctx, struct event *event) From f1a6ae45d3eed1b79ebe95595612e142f4d95e14 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Wed, 3 Dec 2025 16:37:34 +0530 Subject: [PATCH 10/10] configs: qcm6490: Enable super-speed USB support Enable the QMP Combo PHY driver to allow super-speed USB operation on QCM6490 platforms. Signed-off-by: Balaji Selvanathan Reviewed-by: Varadarajan Narayanan --- configs/qcm6490_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configs/qcm6490_defconfig b/configs/qcm6490_defconfig index 618098c8860e..9756687d8f91 100644 --- a/configs/qcm6490_defconfig +++ b/configs/qcm6490_defconfig @@ -15,3 +15,5 @@ CONFIG_REMAKE_ELF=y CONFIG_DEFAULT_DEVICE_TREE="qcom/qcs6490-rb3gen2" CONFIG_FASTBOOT_BUF_ADDR=0xd8800000 + +CONFIG_PHY_QCOM_QMP_COMBO=y