From dced755df3bc49e417ee4de45eec89bf5570b192 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 31 Jul 2025 12:25:44 -0700 Subject: [PATCH 001/132] thunderbolt: Compare HMAC values in constant time To prevent timing attacks, HMAC value comparison needs to be constant time. Replace the memcmp() with the correct function, crypto_memneq(). Fixes: f67cf491175a ("thunderbolt: Add support for Internal Connection Manager (ICM)") Signed-off-by: Eric Biggers Signed-off-by: Mika Westerberg --- drivers/thunderbolt/domain.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 45239703745e..7e0eb3c07f1c 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "tb.h" @@ -748,7 +749,7 @@ int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw) goto err_free_shash; /* The returned HMAC must match the one we calculated */ - if (memcmp(response, hmac, sizeof(hmac))) { + if (crypto_memneq(response, hmac, sizeof(hmac))) { ret = -EKEYREJECTED; goto err_free_shash; } From 0eff12ce88e1e77c465cbaa22d450fd268cd9b74 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 31 Jul 2025 12:25:45 -0700 Subject: [PATCH 002/132] thunderbolt: Use HMAC-SHA256 library instead of crypto_shash Use the hmac_sha256_usingrawkey() library function instead of the "hmac(sha256)" crypto_shash. This is simpler and faster. As a cleanup, change the input data parameters from "challenge, sizeof(hmac)" to "challenge, sizeof(challenge)", so that the size is being taken of the correct buffer. This is not a functional change, since it happens that sizeof(hmac) == sizeof(challenge). Replace the selection of CRYPTO and CRYPTO_HASH with CRYPTO_LIB_SHA256 and CRYPTO_LIB_UTILS. The latter is needed for crypto_memneq() which was previously being pulled in via CRYPTO. Signed-off-by: Eric Biggers Signed-off-by: Mika Westerberg --- drivers/thunderbolt/Kconfig | 4 ++-- drivers/thunderbolt/domain.c | 44 +++++------------------------------- 2 files changed, 8 insertions(+), 40 deletions(-) diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig index 0abdb69ee9f4..db3b0bef48f4 100644 --- a/drivers/thunderbolt/Kconfig +++ b/drivers/thunderbolt/Kconfig @@ -4,8 +4,8 @@ menuconfig USB4 depends on PCI select APPLE_PROPERTIES if EFI_STUB && X86 select CRC32 - select CRYPTO - select CRYPTO_HASH + select CRYPTO_LIB_SHA256 + select CRYPTO_LIB_UTILS select NVMEM help USB4 and Thunderbolt driver. USB4 is the public specification diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 7e0eb3c07f1c..5272c255e046 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include "tb.h" @@ -709,8 +709,6 @@ int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw) u8 response[TB_SWITCH_KEY_SIZE]; u8 hmac[TB_SWITCH_KEY_SIZE]; struct tb_switch *parent_sw; - struct crypto_shash *tfm; - struct shash_desc *shash; int ret; if (!tb->cm_ops->approve_switch || !tb->cm_ops->challenge_switch_key) @@ -726,45 +724,15 @@ int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw) if (ret) return ret; - tfm = crypto_alloc_shash("hmac(sha256)", 0, 0); - if (IS_ERR(tfm)) - return PTR_ERR(tfm); - - ret = crypto_shash_setkey(tfm, sw->key, TB_SWITCH_KEY_SIZE); - if (ret) - goto err_free_tfm; - - shash = kzalloc(sizeof(*shash) + crypto_shash_descsize(tfm), - GFP_KERNEL); - if (!shash) { - ret = -ENOMEM; - goto err_free_tfm; - } - - shash->tfm = tfm; - - memset(hmac, 0, sizeof(hmac)); - ret = crypto_shash_digest(shash, challenge, sizeof(hmac), hmac); - if (ret) - goto err_free_shash; + static_assert(sizeof(hmac) == SHA256_DIGEST_SIZE); + hmac_sha256_usingrawkey(sw->key, TB_SWITCH_KEY_SIZE, + challenge, sizeof(challenge), hmac); /* The returned HMAC must match the one we calculated */ - if (crypto_memneq(response, hmac, sizeof(hmac))) { - ret = -EKEYREJECTED; - goto err_free_shash; - } - - crypto_free_shash(tfm); - kfree(shash); + if (crypto_memneq(response, hmac, sizeof(hmac))) + return -EKEYREJECTED; return tb->cm_ops->approve_switch(tb, sw); - -err_free_shash: - kfree(shash); -err_free_tfm: - crypto_free_shash(tfm); - - return ret; } /** From aaa76d1cbd73a7e8ddb9d92423b017eb98d2b335 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 11 Aug 2025 10:02:34 +0300 Subject: [PATCH 003/132] thunderbolt: Use Linux Foundation IDs for XDomain discovery There are other vendors now that have their own USB4 host router hardware so using the Intel donated IDs may confuse users. For this reason switch to use USB IDs provided by the Linux Foundation for XDomain discovery. Link: https://lore.kernel.org/linux-usb/20250722175026.1994846-1-Raju.Rangoju@amd.com/ Cc: Raju Rangoju Acked-by: Greg Kroah-Hartman Reviewed-by: Mario Limonciello (AMD) > Signed-off-by: Mika Westerberg --- drivers/thunderbolt/xdomain.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index b0630e6d9472..fffa28cc491d 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -2562,10 +2562,9 @@ int tb_xdomain_init(void) * Rest of the properties are filled dynamically based on these * when the P2P connection is made. */ - tb_property_add_immediate(xdomain_property_dir, "vendorid", - PCI_VENDOR_ID_INTEL); - tb_property_add_text(xdomain_property_dir, "vendorid", "Intel Corp."); - tb_property_add_immediate(xdomain_property_dir, "deviceid", 0x1); + tb_property_add_immediate(xdomain_property_dir, "vendorid", 0x1d6b); + tb_property_add_text(xdomain_property_dir, "vendorid", "Linux"); + tb_property_add_immediate(xdomain_property_dir, "deviceid", 0x0004); tb_property_add_immediate(xdomain_property_dir, "devicerv", 0x80000100); xdomain_property_block_gen = get_random_u32(); From 784ad808ae864490b93db0f650b8b6417eb7ce68 Mon Sep 17 00:00:00 2001 From: Prashanth K Date: Fri, 25 Jul 2025 11:51:58 +0530 Subject: [PATCH 004/132] usb: dwc3: qcom: Add shutdown handler Currently during system reboot, SMMU disables its translations while devices like USB may still be actively using DMA buffers. This can lead to NOC errors and system crashes due to invalid memory access. Address this by adding a shutdown callback to dwc3-qcom, which ensures proper teardown of UDC stack and prevents DWC3 controller from accessing memory after SMMU translation is disabled. Reuse the existing remove callback for this purpose. Signed-off-by: Prashanth K Acked-by: Thinh Nguyen Link: https://lore.kernel.org/r/20250725062158.2418961-1-prashanth.k@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index ca7e1c02773a..308360a32c93 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -764,11 +764,14 @@ static void dwc3_qcom_remove(struct platform_device *pdev) struct dwc3 *dwc = platform_get_drvdata(pdev); struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + if (pm_runtime_resume_and_get(qcom->dev) < 0) + return; + dwc3_core_remove(&qcom->dwc); - clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks); - dwc3_qcom_interconnect_exit(qcom); + + pm_runtime_put_noidle(qcom->dev); } static int dwc3_qcom_pm_suspend(struct device *dev) @@ -873,6 +876,7 @@ MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match); static struct platform_driver dwc3_qcom_driver = { .probe = dwc3_qcom_probe, .remove = dwc3_qcom_remove, + .shutdown = dwc3_qcom_remove, .driver = { .name = "dwc3-qcom", .pm = pm_ptr(&dwc3_qcom_dev_pm_ops), From e58ebd14a5f15eccf3dd45edf4bc900b346096bf Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 5 Aug 2025 12:47:29 +0100 Subject: [PATCH 005/132] dt-bindings: usb: renesas,usbhs: Add RZ/T2H and RZ/N2H support Document the USBHS controller for the Renesas RZ/T2H (r9a09g077) and RZ/N2H (r9a09g087) SoCs. While the USBHS block is similar to the one found on the RZ/G2L SoC, it differs slightly in terms of interrupt configuration, clock/reset requirements, and register bit definitions. Due to these differences, a new compatible string `renesas,usbhs-r9a09g077` is introduced for the RZ/T2H SoC. The USBHS controller on the RZ/N2H (r9a09g087) SoC is identical to that on the RZ/T2H, so it uses the `renesas,usbhs-r9a09g077` compatible string as a fallback. Signed-off-by: Lad Prabhakar Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20250805114730.2491238-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/renesas,usbhs.yaml | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml b/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml index a19816bbb1fd..0b8b90dd1951 100644 --- a/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml +++ b/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml @@ -59,6 +59,12 @@ properties: - renesas,usbhs-r8a77995 # R-Car D3 - const: renesas,rcar-gen3-usbhs + - const: renesas,usbhs-r9a09g077 # RZ/T2H + + - items: + - const: renesas,usbhs-r9a09g087 # RZ/N2H + - const: renesas,usbhs-r9a09g077 # RZ/T2H + reg: maxItems: 1 @@ -141,9 +147,25 @@ allOf: required: - resets else: - properties: - interrupts: - maxItems: 1 + if: + properties: + compatible: + contains: + const: renesas,usbhs-r9a09g077 + then: + properties: + resets: false + clocks: + maxItems: 1 + interrupts: + items: + - description: USB function interrupt USB_FI + - description: USB function DMA0 transmit completion interrupt USB_FDMA0 + - description: USB function DMA1 transmit completion interrupt USB_FDMA1 + else: + properties: + interrupts: + maxItems: 1 additionalProperties: false From 015709748ca921975ac1c91b40f664ca8c370eb6 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 5 Aug 2025 12:47:30 +0100 Subject: [PATCH 006/132] usb: renesas_usbhs: Add support for RZ/T2H SoC Add support for the Renesas RZ/T2H (r9a09g077) SoC by adding a compatible string to the usbhs_of_match table. The USBHS controller on the RZ/T2H is functionally similar to the one found on the RZ/G2L. While there are minor differences in register fields (for example, the AWAIT[8:13] bits in SYSCFG1 register), the current driver does not configure these, allowing reuse of the existing usbhs_rzg2l_plat_info for the RZ/T2H. Signed-off-by: Lad Prabhakar Link: https://lore.kernel.org/r/20250805114730.2491238-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/renesas_usbhs/common.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 18a6ef4dce51..8f536f2c500f 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -579,6 +579,10 @@ static const struct of_device_id usbhs_of_match[] = { .compatible = "renesas,usbhs-r9a07g054", .data = &usbhs_rzg2l_plat_info, }, + { + .compatible = "renesas,usbhs-r9a09g077", + .data = &usbhs_rzg2l_plat_info, + }, { .compatible = "renesas,rcar-gen2-usbhs", .data = &usbhs_rcar_gen2_plat_info, From b12daf363f3d4ded1c12d01ab7c45b6179c586ab Mon Sep 17 00:00:00 2001 From: Dominique Martinet Date: Fri, 1 Aug 2025 16:52:02 +0900 Subject: [PATCH 007/132] USB: lower "Device is not authorized for usage" message to info This message is not a useful error in practice: - when using tools such as usbguard, the message is always printed but it does not presume anything regarding the actual device acceptance (later 'authorized to connect' message is at info level, and not displayed on console) - this can be a source of flood if a usb device connection is flaky - ... and it is only displayed as the result of an admin action (modifying authorized_default), working as intended, so not likely to be an error. This is still useful to know when looking at usb devices problems, so info seems appropriate for this class of messages together with the later eventual authorized message. Signed-off-by: Dominique Martinet Link: https://lore.kernel.org/r/20250801-usb-auth-v1-1-a59bfdf0293f@atmark-techno.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/driver.c | 4 ++-- drivers/usb/core/generic.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index c3177034b779..69216c3951fd 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -332,10 +332,10 @@ static int usb_probe_interface(struct device *dev) return error; if (udev->authorized == 0) { - dev_err(&intf->dev, "Device is not authorized for usage\n"); + dev_info(&intf->dev, "Device is not authorized for usage\n"); return error; } else if (intf->authorized == 0) { - dev_err(&intf->dev, "Interface %d is not authorized for usage\n", + dev_info(&intf->dev, "Interface %d is not authorized for usage\n", intf->altsetting->desc.bInterfaceNumber); return error; } diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c index 9c6ae5e1198b..a48994e11ef3 100644 --- a/drivers/usb/core/generic.c +++ b/drivers/usb/core/generic.c @@ -243,7 +243,7 @@ int usb_generic_driver_probe(struct usb_device *udev) * with the driver core and lets interface drivers bind to them. */ if (udev->authorized == 0) - dev_err(&udev->dev, "Device is not authorized for usage\n"); + dev_info(&udev->dev, "Device is not authorized for usage\n"); else { c = usb_choose_configuration(udev); if (c >= 0) { From 4b58e0638c4b0f4b80c87b1c711b065eca559551 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Tue, 5 Aug 2025 11:33:37 +0200 Subject: [PATCH 008/132] usb: misc: eud: Remove error print for devm_add_action_or_reset() When `devm_add_action_or_reset()` fails, it is due to a failed memory allocation and will thus return `-ENOMEM`. `dev_err_probe()` doesn't do anything when error is `-ENOMEM`. Therefore, remove the useless call to `dev_err_probe()` when `devm_add_action_or_reset()` fails, and just return the value instead. Signed-off-by: Waqar Hameed Reviewed-by: Bjorn Andersson Link: https://lore.kernel.org/r/pndo6sukt8u.a.out@axis.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/qcom_eud.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c index 83079c414b4f..67832790acad 100644 --- a/drivers/usb/misc/qcom_eud.c +++ b/drivers/usb/misc/qcom_eud.c @@ -193,8 +193,7 @@ static int eud_probe(struct platform_device *pdev) ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip); if (ret) - return dev_err_probe(chip->dev, ret, - "failed to add role switch release action\n"); + return ret; chip->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(chip->base)) From aeb0169217ebf91b65927c7ce5f27318b2059aa4 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Tue, 29 Jul 2025 14:57:08 +0530 Subject: [PATCH 009/132] usb: dwc3: qcom: Remove extcon functionality from glue layer Historically Qualcomm DWC3 glue driver supported both extcon and usb-role-switch kinds of notifications. When Bjorn contributed [1] the flattened representation for the DWC3 host controller, he also kept both extcon and usb-role-switch support in the flattened driver & bindings. Currently there are no in-kernel users for flattened DWC3 and extcon. As device's DT needs to be manually converted from legacy to the flat DWC3 representation, we can drop (legacy / deprecated) extcon support from the new DWC3 glue driver, significantly simplifying the code. This potentially affects flattening effort for the following platforms: Platforms currently using linux,extcon-usb-gpio device that need to switch to gpio-usb-b-connector: - apq8096-db820c, - msm8996-sony-xperia-tone-dora Platforms currently using linux,extcon-usb-gpio device that need to switch to gpio-usb-c-connector (not supported at this moment) or to implement typec support - msm8996-sony-xperia-tone-kagura - msm8996-sony-xperia-tone-keyaki - msm8998-fxtec-pro1 - msm8998-sony-xperia-yoshino-lilac - msm8998-sony-xperia-yoshino-maple - msm8998-sony-xperia-yoshino-poplar - sda660-inforce-ifc6560 - sdm630-sony-xperia-nile-discovery - sdm630-sony-xperia-nile-pioneer - sdm630-sony-xperia-nile-voyager - sdm660-xiaomi-lavender - sm6125-sony-xperia-seine-pdx201 - sm6125-xiaomi-ginkgo - sm6125-xiaomi-laurel-sprout Platforms using TI TUSB320L chip need to switch to represent the USB-C connector properly (and to have a typec-class driver for the TUSB320L chip): - msm8996-xiaomi-gemini - msm8996pro-xiaomi-natrium - msm8996pro-xiaomi-scoprpio Commit message suggested by Dmitry Baryshkov. [1]: https://lore.kernel.org/all/20250414-dwc3-refactor-v7-0-f015b358722d@oss.qualcomm.com/ Signed-off-by: Krishna Kurapati Acked-by: Thinh Nguyen Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20250729092708.3628187-1-krishna.kurapati@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 90 +----------------------------------- 1 file changed, 1 insertion(+), 89 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 308360a32c93..8a9018ca650c 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -79,11 +78,6 @@ struct dwc3_qcom { struct dwc3_qcom_port ports[DWC3_QCOM_MAX_PORTS]; u8 num_ports; - struct extcon_dev *edev; - struct extcon_dev *host_edev; - struct notifier_block vbus_nb; - struct notifier_block host_nb; - enum usb_dr_mode mode; bool is_suspended; bool pm_suspended; @@ -119,8 +113,7 @@ static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val) /* * TODO: Make the in-core role switching code invoke dwc3_qcom_vbus_override_enable(), - * validate that the in-core extcon support is functional, and drop extcon - * handling from the glue + * validate that the in-core extcon support is functional */ static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable) { @@ -137,80 +130,6 @@ static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable) } } -static int dwc3_qcom_vbus_notifier(struct notifier_block *nb, - unsigned long event, void *ptr) -{ - struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, vbus_nb); - - /* enable vbus override for device mode */ - dwc3_qcom_vbus_override_enable(qcom, event); - qcom->mode = event ? USB_DR_MODE_PERIPHERAL : USB_DR_MODE_HOST; - - return NOTIFY_DONE; -} - -static int dwc3_qcom_host_notifier(struct notifier_block *nb, - unsigned long event, void *ptr) -{ - struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, host_nb); - - /* disable vbus override in host mode */ - dwc3_qcom_vbus_override_enable(qcom, !event); - qcom->mode = event ? USB_DR_MODE_HOST : USB_DR_MODE_PERIPHERAL; - - return NOTIFY_DONE; -} - -static int dwc3_qcom_register_extcon(struct dwc3_qcom *qcom) -{ - struct device *dev = qcom->dev; - struct extcon_dev *host_edev; - int ret; - - if (!of_property_present(dev->of_node, "extcon")) - return 0; - - qcom->edev = extcon_get_edev_by_phandle(dev, 0); - if (IS_ERR(qcom->edev)) - return dev_err_probe(dev, PTR_ERR(qcom->edev), - "Failed to get extcon\n"); - - qcom->vbus_nb.notifier_call = dwc3_qcom_vbus_notifier; - - qcom->host_edev = extcon_get_edev_by_phandle(dev, 1); - if (IS_ERR(qcom->host_edev)) - qcom->host_edev = NULL; - - ret = devm_extcon_register_notifier(dev, qcom->edev, EXTCON_USB, - &qcom->vbus_nb); - if (ret < 0) { - dev_err(dev, "VBUS notifier register failed\n"); - return ret; - } - - if (qcom->host_edev) - host_edev = qcom->host_edev; - else - host_edev = qcom->edev; - - qcom->host_nb.notifier_call = dwc3_qcom_host_notifier; - ret = devm_extcon_register_notifier(dev, host_edev, EXTCON_USB_HOST, - &qcom->host_nb); - if (ret < 0) { - dev_err(dev, "Host notifier register failed\n"); - return ret; - } - - /* Update initial VBUS override based on extcon state */ - if (extcon_get_state(qcom->edev, EXTCON_USB) || - !extcon_get_state(host_edev, EXTCON_USB_HOST)) - dwc3_qcom_vbus_notifier(&qcom->vbus_nb, true, qcom->edev); - else - dwc3_qcom_vbus_notifier(&qcom->vbus_nb, false, qcom->edev); - - return 0; -} - static int dwc3_qcom_interconnect_enable(struct dwc3_qcom *qcom) { int ret; @@ -737,11 +656,6 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (qcom->mode != USB_DR_MODE_HOST) dwc3_qcom_vbus_override_enable(qcom, true); - /* register extcon to override sw_vbus on Vbus change later */ - ret = dwc3_qcom_register_extcon(qcom); - if (ret) - goto interconnect_exit; - wakeup_source = of_property_read_bool(dev->of_node, "wakeup-source"); device_init_wakeup(&pdev->dev, wakeup_source); @@ -749,8 +663,6 @@ static int dwc3_qcom_probe(struct platform_device *pdev) return 0; -interconnect_exit: - dwc3_qcom_interconnect_exit(qcom); remove_core: dwc3_core_remove(&qcom->dwc); clk_disable: From 186e8f2bdba551f3ae23396caccd452d985c23e3 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 7 Aug 2025 18:55:00 +0300 Subject: [PATCH 010/132] usb: host: max3421-hcd: Fix error pointer dereference in probe cleanup The kthread_run() function returns error pointers so the max3421_hcd->spi_thread pointer can be either error pointers or NULL. Check for both before dereferencing it. Fixes: 05dfa5c9bc37 ("usb: host: max3421-hcd: fix "spi_rd8" uses dynamic stack allocation warning") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/aJTMVAPtRe5H6jug@stanley.mountain Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/max3421-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c index dcf31a592f5d..4b5f03f683f7 100644 --- a/drivers/usb/host/max3421-hcd.c +++ b/drivers/usb/host/max3421-hcd.c @@ -1916,7 +1916,7 @@ max3421_probe(struct spi_device *spi) if (hcd) { kfree(max3421_hcd->tx); kfree(max3421_hcd->rx); - if (max3421_hcd->spi_thread) + if (!IS_ERR_OR_NULL(max3421_hcd->spi_thread)) kthread_stop(max3421_hcd->spi_thread); usb_put_hcd(hcd); } From 738812440b3c9083188191caa62ef950cf1ed9ed Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Thu, 7 Aug 2025 16:43:50 -0500 Subject: [PATCH 011/132] dt-bindings: usb: Drop duplicate nvidia,tegra20-ehci.txt The nvidia,tegra20-ehci binding is already documented in ci-hdrc-usb2.yaml, so drop the old text binding. Signed-off-by: "Rob Herring (Arm)" Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20250807214351.4172243-1-robh@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/nvidia,tegra20-ehci.txt | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/nvidia,tegra20-ehci.txt diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra20-ehci.txt b/Documentation/devicetree/bindings/usb/nvidia,tegra20-ehci.txt deleted file mode 100644 index f60785f73d3d..000000000000 --- a/Documentation/devicetree/bindings/usb/nvidia,tegra20-ehci.txt +++ /dev/null @@ -1,23 +0,0 @@ -Tegra SOC USB controllers - -The device node for a USB controller that is part of a Tegra -SOC is as described in the document "Open Firmware Recommended -Practice : Universal Serial Bus" with the following modifications -and additions : - -Required properties : - - compatible : For Tegra20, must contain "nvidia,tegra20-ehci". - For Tegra30, must contain "nvidia,tegra30-ehci". Otherwise, must contain - "nvidia,-ehci" plus at least one of the above, where is - tegra114, tegra124, tegra132, or tegra210. - - nvidia,phy : phandle of the PHY that the controller is connected to. - - clocks : Must contain one entry, for the module clock. - See ../clocks/clock-bindings.txt for details. - - resets : Must contain an entry for each entry in reset-names. - See ../reset/reset.txt for details. - - reset-names : Must include the following entries: - - usb - -Optional properties: - - nvidia,needs-double-reset : boolean is to be set for some of the Tegra20 - USB ports, which need reset twice due to hardware issues. From c0485e864a2eaa1d5a84c71e573dd236d0e885ae Mon Sep 17 00:00:00 2001 From: Komal Bajaj Date: Thu, 31 Jul 2025 14:31:32 +0530 Subject: [PATCH 012/132] usb: misc: qcom_eud: Access EUD_MODE_MANAGER2 through secure calls EUD_MODE_MANAGER2 register is mapped to a memory region that is marked as read-only for operating system running at EL1, enforcing access restrictions that prohibit direct memory-mapped writes via writel(). Attempts to write to this region from HLOS can result in silent failures or memory access violations, particularly when toggling EUD (Embedded USB Debugger) state. To ensure secure register access, modify the driver to use qcom_scm_io_writel(), which routes the write operation to Qualcomm Secure Channel Monitor (SCM). SCM has the necessary permissions to access protected memory regions, enabling reliable control over EUD state. SC7280, the only user of EUD is also affected, indicating that this could never have worked on a properly fused device. Fixes: 9a1bf58ccd44 ("usb: misc: eud: Add driver support for Embedded USB Debugger(EUD)") Signed-off-by: Melody Olvera Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Reviewed-by: Souradeep Chowdhury Signed-off-by: Komal Bajaj Link: https://lore.kernel.org/r/20250731-eud_mode_manager_secure_access-v8-1-4a5dcbb79f41@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/Kconfig | 1 + drivers/usb/misc/qcom_eud.c | 33 ++++++++++++++++++++++++--------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 6497c4e81e95..9bf8fc6247ba 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -147,6 +147,7 @@ config USB_APPLEDISPLAY config USB_QCOM_EUD tristate "QCOM Embedded USB Debugger(EUD) Driver" depends on ARCH_QCOM || COMPILE_TEST + select QCOM_SCM select USB_ROLE_SWITCH help This module enables support for Qualcomm Technologies, Inc. diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c index 67832790acad..926419ca560f 100644 --- a/drivers/usb/misc/qcom_eud.c +++ b/drivers/usb/misc/qcom_eud.c @@ -15,6 +15,7 @@ #include #include #include +#include #define EUD_REG_INT1_EN_MASK 0x0024 #define EUD_REG_INT_STATUS_1 0x0044 @@ -34,7 +35,7 @@ struct eud_chip { struct device *dev; struct usb_role_switch *role_sw; void __iomem *base; - void __iomem *mode_mgr; + phys_addr_t mode_mgr; unsigned int int_status; int irq; bool enabled; @@ -43,18 +44,29 @@ struct eud_chip { static int enable_eud(struct eud_chip *priv) { + int ret; + + ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 1); + if (ret) + return ret; + writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN); writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE, priv->base + EUD_REG_INT1_EN_MASK); - writel(1, priv->mode_mgr + EUD_REG_EUD_EN2); return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE); } -static void disable_eud(struct eud_chip *priv) +static int disable_eud(struct eud_chip *priv) { + int ret; + + ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 0); + if (ret) + return ret; + writel(0, priv->base + EUD_REG_CSR_EUD_EN); - writel(0, priv->mode_mgr + EUD_REG_EUD_EN2); + return 0; } static ssize_t enable_show(struct device *dev, @@ -82,11 +94,12 @@ static ssize_t enable_store(struct device *dev, chip->enabled = enable; else disable_eud(chip); + } else { - disable_eud(chip); + ret = disable_eud(chip); } - return count; + return ret < 0 ? ret : count; } static DEVICE_ATTR_RW(enable); @@ -178,6 +191,7 @@ static void eud_role_switch_release(void *data) static int eud_probe(struct platform_device *pdev) { struct eud_chip *chip; + struct resource *res; int ret; chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); @@ -199,9 +213,10 @@ static int eud_probe(struct platform_device *pdev) if (IS_ERR(chip->base)) return PTR_ERR(chip->base); - chip->mode_mgr = devm_platform_ioremap_resource(pdev, 1); - if (IS_ERR(chip->mode_mgr)) - return PTR_ERR(chip->mode_mgr); + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) + return -ENODEV; + chip->mode_mgr = res->start; chip->irq = platform_get_irq(pdev, 0); if (chip->irq < 0) From 5d03847175e81e86d4865456c15638faaf7c0634 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Tue, 12 Aug 2025 15:42:29 +0200 Subject: [PATCH 013/132] thunderbolt: Use is_pciehp instead of is_hotplug_bridge The thunderbolt driver sets up device link dependencies from hotplug ports to the Host Router (aka Native Host Interface, NHI). When resuming from system sleep, this allows the Host Router to re-establish tunnels to attached Thunderbolt devices before the hotplug ports resume. To identify the hotplug ports, the driver utilizes the is_hotplug_bridge flag which also encompasses ACPI slots handled by the ACPI hotplug driver. Thunderbolt hotplug ports are always Hot-Plug Capable PCIe ports, so it is more apt to identify them with the is_pciehp flag. Similarly, hotplug ports on older Thunderbolt controllers have broken MSI support and are quirked to use legacy INTx interrupts instead. The quirk identifies them with is_hotplug_bridge, even though all affected ports are also matched by is_pciehp. So use is_pciehp here as well. Signed-off-by: Lukas Wunner Acked-by: Bjorn Helgaas Signed-off-by: Mika Westerberg --- drivers/pci/quirks.c | 2 +- drivers/thunderbolt/tb.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index d97335a40193..17315a825674 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3829,7 +3829,7 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_MELLANOX, 0xcf80, quirk_no_pm_reset); */ static void quirk_thunderbolt_hotplug_msi(struct pci_dev *pdev) { - if (pdev->is_hotplug_bridge && + if (pdev->is_pciehp && (pdev->device != PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C || pdev->revision <= 1)) pdev->no_msi = 1; diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index c14ab1fbeeaf..83a33fc1486a 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -3336,7 +3336,7 @@ static bool tb_apple_add_links(struct tb_nhi *nhi) if (!pci_is_pcie(pdev)) continue; if (pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM || - !pdev->is_hotplug_bridge) + !pdev->is_pciehp) continue; link = device_link_add(&pdev->dev, &nhi->pdev->dev, From c851b71fd6cdbf3000bf0a2354592a0f09db3ba3 Mon Sep 17 00:00:00 2001 From: Venkat Jayaraman Date: Thu, 14 Aug 2025 09:30:28 -0700 Subject: [PATCH 014/132] usb: typec: ucsi: Add support for READ_POWER_LEVEL command Add support for UCSI READ_POWER_LEVEL command as per UCSI specification v2.1 and above to debugfs. Following power related fields will be exposed as files in debugfs:- peak_current (Peak current), avg_current (Average current) and vbus_voltage (VBUS voltage) These files will be updated either when a READ_POWER_LEVEL command is sent from OS or when a device is connected. Reviewed-by: Heikki Krogerus Signed-off-by: Venkat Jayaraman Link: https://lore.kernel.org/r/20250814163028.18058-1-venkat.jayaraman@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/debugfs.c | 31 +++++++++++++++++++++++++++++++ drivers/usb/typec/ucsi/ucsi.c | 16 ++++++++++++++++ drivers/usb/typec/ucsi/ucsi.h | 13 +++++++++++++ 3 files changed, 60 insertions(+) diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c index 92ebf1a2defd..f73f2b54554e 100644 --- a/drivers/usb/typec/ucsi/debugfs.c +++ b/drivers/usb/typec/ucsi/debugfs.c @@ -35,6 +35,7 @@ static int ucsi_cmd(void *data, u64 val) case UCSI_SET_SINK_PATH: case UCSI_SET_NEW_CAM: case UCSI_SET_USB: + case UCSI_READ_POWER_LEVEL: ret = ucsi_send_command(ucsi, val, NULL, 0); break; case UCSI_GET_CAPABILITY: @@ -80,6 +81,33 @@ static int ucsi_resp_show(struct seq_file *s, void *not_used) } DEFINE_SHOW_ATTRIBUTE(ucsi_resp); +static int ucsi_peak_curr_show(struct seq_file *m, void *v) +{ + struct ucsi *ucsi = m->private; + + seq_printf(m, "%u mA\n", ucsi->connector->peak_current); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ucsi_peak_curr); + +static int ucsi_avg_curr_show(struct seq_file *m, void *v) +{ + struct ucsi *ucsi = m->private; + + seq_printf(m, "%u mA\n", ucsi->connector->avg_current); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ucsi_avg_curr); + +static int ucsi_vbus_volt_show(struct seq_file *m, void *v) +{ + struct ucsi *ucsi = m->private; + + seq_printf(m, "%u mV\n", ucsi->connector->vbus_voltage); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ucsi_vbus_volt); + void ucsi_debugfs_register(struct ucsi *ucsi) { ucsi->debugfs = kzalloc(sizeof(*ucsi->debugfs), GFP_KERNEL); @@ -89,6 +117,9 @@ void ucsi_debugfs_register(struct ucsi *ucsi) ucsi->debugfs->dentry = debugfs_create_dir(dev_name(ucsi->dev), ucsi_debugfs_root); debugfs_create_file("command", 0200, ucsi->debugfs->dentry, ucsi, &ucsi_cmd_fops); debugfs_create_file("response", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_resp_fops); + debugfs_create_file("peak_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_peak_curr_fops); + debugfs_create_file("avg_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_avg_curr_fops); + debugfs_create_file("vbus_voltage", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_vbus_volt_fops); } void ucsi_debugfs_unregister(struct ucsi *ucsi) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 5739ea2abdd1..0d6b0cf5a7cd 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1217,9 +1217,11 @@ static void ucsi_handle_connector_change(struct work_struct *work) struct ucsi_connector *con = container_of(work, struct ucsi_connector, work); struct ucsi *ucsi = con->ucsi; + u8 curr_scale, volt_scale; enum typec_role role; u16 change; int ret; + u32 val; mutex_lock(&con->lock); @@ -1291,6 +1293,20 @@ static void ucsi_handle_connector_change(struct work_struct *work) if (change & UCSI_CONSTAT_BC_CHANGE) ucsi_port_psy_changed(con); + if (UCSI_CONSTAT(con, PWR_READING_READY_V2_1)) { + curr_scale = UCSI_CONSTAT(con, CURRENT_SCALE_V2_1); + volt_scale = UCSI_CONSTAT(con, VOLTAGE_SCALE_V2_1); + + val = UCSI_CONSTAT(con, PEAK_CURRENT_V2_1); + con->peak_current = UCSI_CONSTAT_CURR_SCALE_MULT * curr_scale * val; + + val = UCSI_CONSTAT(con, AVG_CURRENT_V2_1); + con->avg_current = UCSI_CONSTAT_CURR_SCALE_MULT * curr_scale * val; + + val = UCSI_CONSTAT(con, VBUS_VOLTAGE_V2_1); + con->vbus_voltage = UCSI_CONSTAT_VOLT_SCALE_MULT * volt_scale * val; + } + out_unlock: mutex_unlock(&con->lock); } diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index ebd7c27c2cc7..e301d9012936 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -131,6 +131,7 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num); #define UCSI_GET_PD_MESSAGE 0x15 #define UCSI_GET_CAM_CS 0x18 #define UCSI_SET_SINK_PATH 0x1c +#define UCSI_READ_POWER_LEVEL 0x1e #define UCSI_SET_USB 0x21 #define UCSI_GET_LPM_PPM_INFO 0x22 @@ -359,6 +360,14 @@ struct ucsi_cable_property { #define UCSI_CONSTAT_BC_SLOW_CHARGING 2 #define UCSI_CONSTAT_BC_TRICKLE_CHARGING 3 #define UCSI_CONSTAT_PD_VERSION_V1_2 UCSI_DECLARE_BITFIELD_V1_2(70, 16) +#define UCSI_CONSTAT_PWR_READING_READY_V2_1 UCSI_DECLARE_BITFIELD_V2_1(89, 1) +#define UCSI_CONSTAT_CURRENT_SCALE_V2_1 UCSI_DECLARE_BITFIELD_V2_1(90, 3) +#define UCSI_CONSTAT_PEAK_CURRENT_V2_1 UCSI_DECLARE_BITFIELD_V2_1(93, 16) +#define UCSI_CONSTAT_AVG_CURRENT_V2_1 UCSI_DECLARE_BITFIELD_V2_1(109, 16) +#define UCSI_CONSTAT_VOLTAGE_SCALE_V2_1 UCSI_DECLARE_BITFIELD_V2_1(125, 4) +#define UCSI_CONSTAT_VBUS_VOLTAGE_V2_1 UCSI_DECLARE_BITFIELD_V2_1(129, 16) +#define UCSI_CONSTAT_CURR_SCALE_MULT 5 +#define UCSI_CONSTAT_VOLT_SCALE_MULT 5 /* Connector Status Change Bits. */ #define UCSI_CONSTAT_EXT_SUPPLY_CHANGE BIT(1) @@ -519,6 +528,10 @@ struct ucsi_connector { u32 src_pdos[PDO_MAX_OBJECTS]; int num_pdos; + u32 peak_current; + u32 avg_current; + u32 vbus_voltage; + /* USB PD objects */ struct usb_power_delivery *pd; struct usb_power_delivery_capabilities *port_source_caps; From 23cd838a178a81183da9db6cb3390bb540b000a5 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 15 Aug 2025 14:31:21 +0300 Subject: [PATCH 015/132] USB: Check no positive return values from pm_runtime_resume_and_get() pm_runtime_resume_and_get() always returns a negative error code or zero; there's no need to check for positive values such as returned by pm_runtime_get_sync(). Simply drop the check. Signed-off-by: Sakari Ailus Link: https://lore.kernel.org/r/20250815113121.925641-1-sakari.ailus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/driver.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 69216c3951fd..25358cf3e324 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1723,8 +1723,6 @@ int usb_autoresume_device(struct usb_device *udev) dev_vdbg(&udev->dev, "%s: cnt %d -> %d\n", __func__, atomic_read(&udev->dev.power.usage_count), status); - if (status > 0) - status = 0; return status; } @@ -1829,8 +1827,6 @@ int usb_autopm_get_interface(struct usb_interface *intf) dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n", __func__, atomic_read(&intf->dev.power.usage_count), status); - if (status > 0) - status = 0; return status; } EXPORT_SYMBOL_GPL(usb_autopm_get_interface); From 956606bafb5fc6e5968aadcda86fc0037e1d7548 Mon Sep 17 00:00:00 2001 From: raub camaioni Date: Fri, 15 Aug 2025 09:07:21 -0400 Subject: [PATCH 016/132] usb: gadget: f_ncm: Fix MAC assignment NCM ethernet This fix is already present in f_ecm.c and was never propagated to f_ncm.c When creating multiple NCM ethernet devices on a composite usb gadget device each MAC address on the HOST side will be identical. Having the same MAC on different network interfaces is bad. This fix updates the MAC address inside the ncm_strings_defs global during the ncm_bind call. This ensures each device has a unique MAC. In f_ecm.c ecm_string_defs is updated in the same way. The defunct MAC assignment in ncm_alloc has been removed. Signed-off-by: raub camaioni Link: https://lore.kernel.org/r/20250815131358.1047525-1-raubcameo@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_ncm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index 58b0dd575af3..cad111f33552 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -1463,6 +1463,8 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) ncm_opts->bound = true; + ncm_string_defs[1].s = ncm->ethaddr; + us = usb_gstrings_attach(cdev, ncm_strings, ARRAY_SIZE(ncm_string_defs)); if (IS_ERR(us)) { @@ -1771,7 +1773,6 @@ static struct usb_function *ncm_alloc(struct usb_function_instance *fi) mutex_unlock(&opts->lock); return ERR_PTR(-EINVAL); } - ncm_string_defs[STRING_MAC_IDX].s = ncm->ethaddr; spin_lock_init(&ncm->lock); ncm_reset_values(ncm); From 1f43a3401debb9d06aa434ec6fecae676963d6f3 Mon Sep 17 00:00:00 2001 From: Qianfeng Rong Date: Tue, 19 Aug 2025 17:01:24 +0800 Subject: [PATCH 017/132] usb: typec: qcom-pmic-typec: use kcalloc() instead of kzalloc() Replace devm_kzalloc() with devm_kcalloc() in qcom_pmic_typec_pdphy_probe() and qcom_pmic_typec_port_probe() for safer memory allocation with built-in overflow protection. Signed-off-by: Qianfeng Rong Reviewed-by: Dmitry Baryshkov Reviewed-by: Bryan O'Donoghue Link: https://lore.kernel.org/r/20250819090125.540682-1-rongqianfeng@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c | 2 +- drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c index 18303b34594b..c8b1463e6e8b 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c @@ -567,7 +567,7 @@ int qcom_pmic_typec_pdphy_probe(struct platform_device *pdev, if (!res->nr_irqs || res->nr_irqs > PMIC_PDPHY_MAX_IRQS) return -EINVAL; - irq_data = devm_kzalloc(dev, sizeof(*irq_data) * res->nr_irqs, + irq_data = devm_kcalloc(dev, res->nr_irqs, sizeof(*irq_data), GFP_KERNEL); if (!irq_data) return -ENOMEM; diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c index 4fc83dcfae64..8051eaa46991 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c @@ -713,7 +713,7 @@ int qcom_pmic_typec_port_probe(struct platform_device *pdev, if (!res->nr_irqs || res->nr_irqs > PMIC_TYPEC_MAX_IRQS) return -EINVAL; - irq_data = devm_kzalloc(dev, sizeof(*irq_data) * res->nr_irqs, + irq_data = devm_kcalloc(dev, res->nr_irqs, sizeof(*irq_data), GFP_KERNEL); if (!irq_data) return -ENOMEM; From d15fbd3ea75bc893b46e9f4df6f9469db0f93897 Mon Sep 17 00:00:00 2001 From: Xichao Zhao Date: Tue, 19 Aug 2025 19:24:51 +0800 Subject: [PATCH 018/132] usb: typec: mux: Remove the use of dev_err_probe() The dev_err_probe() doesn't do anything when error is '-ENOMEM'. Therefore, remove the useless call to dev_err_probe(), and just return the value instead. Signed-off-by: Xichao Zhao Reviewed-by: Romain Gantois Link: https://lore.kernel.org/r/20250819112451.587817-1-zhao.xichao@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/tusb1046.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/mux/tusb1046.c b/drivers/usb/typec/mux/tusb1046.c index b4f45c217b59..3c1a4551c2fb 100644 --- a/drivers/usb/typec/mux/tusb1046.c +++ b/drivers/usb/typec/mux/tusb1046.c @@ -129,7 +129,7 @@ static int tusb1046_i2c_probe(struct i2c_client *client) priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) - return dev_err_probe(dev, -ENOMEM, "failed to allocate driver data\n"); + return -ENOMEM; priv->client = client; From b06578497e6f32fa181235f5a64746c7d40259e6 Mon Sep 17 00:00:00 2001 From: Chelsy Ratnawat Date: Sun, 24 Aug 2025 08:13:16 -0700 Subject: [PATCH 019/132] thunderbolt: Use string choices helpers Use string_choices.h helpers instead of hard-coded strings. Signed-off-by: Chelsy Ratnawat Signed-off-by: Mika Westerberg --- drivers/thunderbolt/debugfs.c | 3 ++- drivers/thunderbolt/nhi.c | 3 ++- drivers/thunderbolt/usb4.c | 13 +++++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index f8328ca7e22e..46a2a3550be7 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "tb.h" @@ -691,7 +692,7 @@ static int margining_caps_show(struct seq_file *s, void *not_used) seq_printf(s, "0x%08x\n", margining->caps[i]); seq_printf(s, "# software margining: %s\n", - supports_software(margining) ? "yes" : "no"); + str_yes_no(supports_software(margining))); if (supports_hardware(margining)) { seq_puts(s, "# hardware margining: yes\n"); seq_puts(s, "# minimum BER level contour: "); diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index f3a2264e012b..8a38b05d25c5 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "nhi.h" @@ -146,7 +147,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) dev_WARN(&ring->nhi->pdev->dev, "interrupt for %s %d is already %s\n", RING_TYPE(ring), ring->hop, - active ? "enabled" : "disabled"); + str_enabled_disabled(active)); if (active) iowrite32(new, ring->nhi->iobase + reg); diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index fdae76c8f728..e160afadd501 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -9,6 +9,7 @@ #include #include +#include #include #include "sb_regs.h" @@ -172,8 +173,8 @@ void usb4_switch_check_wakes(struct tb_switch *sw) return; tb_sw_dbg(sw, "PCIe wake: %s, USB3 wake: %s\n", - (val & ROUTER_CS_6_WOPS) ? "yes" : "no", - (val & ROUTER_CS_6_WOUS) ? "yes" : "no"); + str_yes_no(val & ROUTER_CS_6_WOPS), + str_yes_no(val & ROUTER_CS_6_WOUS)); wakeup = val & (ROUTER_CS_6_WOPS | ROUTER_CS_6_WOUS); } @@ -191,9 +192,9 @@ void usb4_switch_check_wakes(struct tb_switch *sw) break; tb_port_dbg(port, "USB4 wake: %s, connection wake: %s, disconnection wake: %s\n", - (val & PORT_CS_18_WOU4S) ? "yes" : "no", - (val & PORT_CS_18_WOCS) ? "yes" : "no", - (val & PORT_CS_18_WODS) ? "yes" : "no"); + str_yes_no(val & PORT_CS_18_WOU4S), + str_yes_no(val & PORT_CS_18_WOCS), + str_yes_no(val & PORT_CS_18_WODS)); wakeup_usb4 = val & (PORT_CS_18_WOU4S | PORT_CS_18_WOCS | PORT_CS_18_WODS); @@ -260,7 +261,7 @@ int usb4_switch_setup(struct tb_switch *sw) tbt3 = !(val & ROUTER_CS_6_TNS); tb_sw_dbg(sw, "TBT3 support: %s, xHCI: %s\n", - tbt3 ? "yes" : "no", xhci ? "yes" : "no"); + str_yes_no(tbt3), str_yes_no(xhci)); ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); if (ret) From b3a333f8ac1645b5f6a9d5f0a33c8523c1cedb94 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 26 Aug 2025 09:17:12 +0100 Subject: [PATCH 020/132] USB: serial: oti6858: remove extranenous ; after comment There is a redundant semicolon after a comment, remove it. Signed-off-by: Colin Ian King Signed-off-by: Johan Hovold --- drivers/usb/serial/oti6858.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/serial/oti6858.c b/drivers/usb/serial/oti6858.c index 24068368892c..bd206cb9cc08 100644 --- a/drivers/usb/serial/oti6858.c +++ b/drivers/usb/serial/oti6858.c @@ -106,7 +106,7 @@ struct oti6858_control_pkt { #define PIN_DTR 0x04 /* output pin */ #define PIN_RI 0x02 /* input pin, active low */ #define PIN_DCD 0x01 /* input pin, active low */ - u8 rx_bytes_avail; /* number of bytes in rx buffer */; + u8 rx_bytes_avail; /* number of bytes in rx buffer */ }; #define OTI6858_CTRL_PKT_SIZE sizeof(struct oti6858_control_pkt) From 91709d2ce5cdf568b42fcc33473929843ed95cd3 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 30 Aug 2025 13:04:20 +0200 Subject: [PATCH 021/132] usb: ucsi: stm32: Use min() to improve ucsi_stm32g0_fw_cb() Use min() to improve ucsi_stm32g0_fw_cb() and avoid calculating 'end - data' twice. Signed-off-by: Thorsten Blum Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250830110426.10007-2-thorsten.blum@linux.dev Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_stm32g0.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_stm32g0.c b/drivers/usb/typec/ucsi/ucsi_stm32g0.c index 57ef7d83a412..838ac0185082 100644 --- a/drivers/usb/typec/ucsi/ucsi_stm32g0.c +++ b/drivers/usb/typec/ucsi/ucsi_stm32g0.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -523,11 +524,7 @@ static void ucsi_stm32g0_fw_cb(const struct firmware *fw, void *context) data = fw->data; end = fw->data + fw->size; while (data < end) { - if ((end - data) < STM32G0_I2C_BL_SZ) - size = end - data; - else - size = STM32G0_I2C_BL_SZ; - + size = min(end - data, STM32G0_I2C_BL_SZ); ret = ucsi_stm32g0_bl_write(g0->ucsi, addr, data, size); if (ret) { dev_err(g0->dev, "Write failed %d\n", ret); From 43ae982cd0ec7fb6fea40fabef2c872e6f9b213d Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Fri, 29 Aug 2025 19:37:12 +0200 Subject: [PATCH 022/132] usb: usblp: Use min_t() to improve usblp_read() Use min_t() to improve usblp_read() and avoid calculating 'avail - usblp->readcount' twice. Use min_t(ssize_t,,) instead of min() to avoid a signedness error. Signed-off-by: Thorsten Blum Link: https://lore.kernel.org/r/20250829173713.56222-1-thorsten.blum@linux.dev Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/usblp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/class/usblp.c b/drivers/usb/class/usblp.c index acbefccbdb2a..a7a1d38b6bef 100644 --- a/drivers/usb/class/usblp.c +++ b/drivers/usb/class/usblp.c @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -871,7 +872,7 @@ static ssize_t usblp_read(struct file *file, char __user *buffer, size_t len, lo goto done; } - count = len < avail - usblp->readcount ? len : avail - usblp->readcount; + count = min_t(ssize_t, len, avail - usblp->readcount); if (count != 0 && copy_to_user(buffer, usblp->readbuf + usblp->readcount, count)) { count = -EFAULT; From 811ee632b1f7e543ccaf61d7c241e73c386a6eb2 Mon Sep 17 00:00:00 2001 From: Petr Vorel Date: Fri, 29 Aug 2025 18:17:08 +0200 Subject: [PATCH 023/132] usb: misc: Update link to EHSET pdf doc Obviously file on the website was renamed. Signed-off-by: Petr Vorel Link: https://lore.kernel.org/r/20250829161708.106813-1-petr.vorel@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 9bf8fc6247ba..26eaae32f738 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -232,8 +232,8 @@ config USB_EHSET_TEST_FIXTURE VID/PID pairs. This driver then initiates a corresponding test mode on the downstream port to which the test fixture is attached. - See for more - information. + See + for more information. config USB_ISIGHTFW tristate "iSight firmware loading support" From 5195edb359855d9a6460f19b7c3915a8611fceed Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 28 Aug 2025 18:26:24 +0200 Subject: [PATCH 024/132] usb: storage: realtek_cr: Simplify residue calculation in rts51x_bulk_transport() Simplify the calculation of 'residue' in rts51x_bulk_transport() and avoid unnecessarily reassigning 'residue' to itself. Acked-by: Alan Stern Signed-off-by: Thorsten Blum Link: https://lore.kernel.org/r/20250828162623.4840-3-thorsten.blum@linux.dev Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/realtek_cr.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/storage/realtek_cr.c b/drivers/usb/storage/realtek_cr.c index cb5bbb19060e..3cc243956fd4 100644 --- a/drivers/usb/storage/realtek_cr.c +++ b/drivers/usb/storage/realtek_cr.c @@ -260,8 +260,8 @@ static int rts51x_bulk_transport(struct us_data *us, u8 lun, * try to compute the actual residue, based on how much data * was really transferred and what the device tells us */ - if (residue) - residue = residue < buf_len ? residue : buf_len; + if (residue > buf_len) + residue = buf_len; if (act_len) *act_len = buf_len - residue; From b570b346ddd727c4b41743a6a2f49e7217c5317f Mon Sep 17 00:00:00 2001 From: Xichao Zhao Date: Fri, 22 Aug 2025 17:22:24 +0800 Subject: [PATCH 025/132] usb: phy: twl6030: Fix incorrect type for ret In the twl6030_usb_probe(), the variable ret is declared as a u32 type. However, since ret may receive -ENODEV when accepting the return value of omap_usb2_set_comparator().Therefore, its type should be changed to int. Fixes: 0e98de67bacba ("usb: otg: make twl6030_usb as a comparator driver to omap_usb2") Signed-off-by: Xichao Zhao Link: https://lore.kernel.org/r/20250822092224.30645-1-zhao.xichao@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-twl6030-usb.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/phy/phy-twl6030-usb.c b/drivers/usb/phy/phy-twl6030-usb.c index 49d79c1257f3..8c09db750bfd 100644 --- a/drivers/usb/phy/phy-twl6030-usb.c +++ b/drivers/usb/phy/phy-twl6030-usb.c @@ -328,9 +328,8 @@ static int twl6030_set_vbus(struct phy_companion *comparator, bool enabled) static int twl6030_usb_probe(struct platform_device *pdev) { - u32 ret; struct twl6030_usb *twl; - int status, err; + int status, err, ret; struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; From 55f4ac8f93f4281134dfcf47de7b4588bb6074f2 Mon Sep 17 00:00:00 2001 From: Kuen-Han Tsai Date: Fri, 22 Aug 2025 17:23:45 +0800 Subject: [PATCH 026/132] usb: dwc3: Add trace event for dwc3_set_prtcap Changes to the port capability can be indirectly observed by tracing register writes to DWC3_GCTL. However, this requires interpreting the raw value, which is neither intuitive nor precise for debugging. Monitoring these mode changes is essential for resolving issues related to USB role switching and enumeration. Introduce a dedicated trace event to provide a human-readable log when the port capability is configured. Signed-off-by: Kuen-Han Tsai Acked-by: Thinh Nguyen Link: https://lore.kernel.org/r/20250822092411.173519-1-khtsai@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 1 + drivers/usb/dwc3/debug.h | 18 ++++++++++++++++++ drivers/usb/dwc3/trace.h | 17 +++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 8002c23a5a02..370fc524a468 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -156,6 +156,7 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy) dwc3_writel(dwc->regs, DWC3_GCTL, reg); dwc->current_dr_role = mode; + trace_dwc3_set_prtcap(mode); } static void __dwc3_set_mode(struct work_struct *work) diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h index 09d703852a92..6e1cdcdce7cc 100644 --- a/drivers/usb/dwc3/debug.h +++ b/drivers/usb/dwc3/debug.h @@ -13,6 +13,24 @@ #include "core.h" +/** + * dwc3_mode_string - returns mode name + * @mode: GCTL.PrtCapDir value + */ +static inline const char *dwc3_mode_string(u32 mode) +{ + switch (mode) { + case DWC3_GCTL_PRTCAP_HOST: + return "host"; + case DWC3_GCTL_PRTCAP_DEVICE: + return "device"; + case DWC3_GCTL_PRTCAP_OTG: + return "otg"; + default: + return "UNKNOWN"; + } +} + /** * dwc3_gadget_ep_cmd_string - returns endpoint command string * @cmd: command code diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index bdeb1aaf65d8..b6ba984bafcd 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -19,6 +19,23 @@ #include "core.h" #include "debug.h" +DECLARE_EVENT_CLASS(dwc3_log_set_prtcap, + TP_PROTO(u32 mode), + TP_ARGS(mode), + TP_STRUCT__entry( + __field(u32, mode) + ), + TP_fast_assign( + __entry->mode = mode; + ), + TP_printk("mode %s", dwc3_mode_string(__entry->mode)) +); + +DEFINE_EVENT(dwc3_log_set_prtcap, dwc3_set_prtcap, + TP_PROTO(u32 mode), + TP_ARGS(mode) +); + DECLARE_EVENT_CLASS(dwc3_log_io, TP_PROTO(void *base, u32 offset, u32 value), TP_ARGS(base, offset, value), From 4c9860fbe66177fddb8b7161d6809110d3960a6e Mon Sep 17 00:00:00 2001 From: Kuen-Han Tsai Date: Fri, 22 Aug 2025 17:23:46 +0800 Subject: [PATCH 027/132] usb: dwc3: Refactor dwc3_mode_show Use dwc3_mode_string as the single source of truth for mode-to-string conversion. Signed-off-by: Kuen-Han Tsai Acked-by: Thinh Nguyen Link: https://lore.kernel.org/r/20250822092411.173519-2-khtsai@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/debugfs.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index ebf03468fac4..d18bf5e32cc8 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -402,6 +402,7 @@ static int dwc3_mode_show(struct seq_file *s, void *unused) struct dwc3 *dwc = s->private; unsigned long flags; u32 reg; + u32 mode; int ret; ret = pm_runtime_resume_and_get(dwc->dev); @@ -412,18 +413,15 @@ static int dwc3_mode_show(struct seq_file *s, void *unused) reg = dwc3_readl(dwc->regs, DWC3_GCTL); spin_unlock_irqrestore(&dwc->lock, flags); - switch (DWC3_GCTL_PRTCAP(reg)) { + mode = DWC3_GCTL_PRTCAP(reg); + switch (mode) { case DWC3_GCTL_PRTCAP_HOST: - seq_puts(s, "host\n"); - break; case DWC3_GCTL_PRTCAP_DEVICE: - seq_puts(s, "device\n"); - break; case DWC3_GCTL_PRTCAP_OTG: - seq_puts(s, "otg\n"); + seq_printf(s, "%s\n", dwc3_mode_string(mode)); break; default: - seq_printf(s, "UNKNOWN %08x\n", DWC3_GCTL_PRTCAP(reg)); + seq_printf(s, "UNKNOWN %08x\n", mode); } pm_runtime_put_sync(dwc->dev); From c79bf528738c0e98e0744f7a63c27fab35c93b43 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 20 Aug 2025 14:11:01 +0200 Subject: [PATCH 028/132] dt-bindings: usb: IXP4xx UDC bindings This adds device tree bindings for the IXP4xx USB Device Controller (UDC). Signed-off-by: Linus Walleij Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250820-ixp4xx-udc-bindings-v1-1-640f29140164@linaro.org Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/intel,ixp4xx-udc.yaml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/intel,ixp4xx-udc.yaml diff --git a/Documentation/devicetree/bindings/usb/intel,ixp4xx-udc.yaml b/Documentation/devicetree/bindings/usb/intel,ixp4xx-udc.yaml new file mode 100644 index 000000000000..4ed602746897 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/intel,ixp4xx-udc.yaml @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/intel,ixp4xx-udc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Intel IXP4xx SoC USB Device Controller (UDC) + +description: The IXP4xx SoCs has a full-speed USB Device + Controller with 16 endpoints and a built-in transceiver. + +maintainers: + - Linus Walleij + +properties: + compatible: + const: intel,ixp4xx-udc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + #include + usb@c800b000 { + compatible = "intel,ixp4xx-udc"; + reg = <0xc800b000 0x1000>; + interrupts = <12 IRQ_TYPE_LEVEL_HIGH>; + }; From 7d3f780122bee082f035aeb6a3cf073dc77dbd1f Mon Sep 17 00:00:00 2001 From: Jihed Chaibi Date: Sun, 24 Aug 2025 13:23:37 +0200 Subject: [PATCH 029/132] usb: dt-bindings: ti,twl4030-usb: convert to DT schema Convert the legacy TXT binding for the TWL4030 USB module to the modern YAML DT schema format. This adds formal validation and improves documentation using a conditional schema. Remove the twl4030 section from the obsolete .txt binding file Signed-off-by: Jihed Chaibi Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20250824112338.64953-2-jihed.chaibi.dev@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/ti,twl4030-usb.yaml | 74 +++++++++++++++++++ .../devicetree/bindings/usb/twlxxxx-usb.txt | 22 ------ 2 files changed, 74 insertions(+), 22 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/ti,twl4030-usb.yaml diff --git a/Documentation/devicetree/bindings/usb/ti,twl4030-usb.yaml b/Documentation/devicetree/bindings/usb/ti,twl4030-usb.yaml new file mode 100644 index 000000000000..6ef337507425 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/ti,twl4030-usb.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/ti,twl4030-usb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments TWL4030 USB PHY and Comparator + +maintainers: + - Peter Ujfalusi + +description: + Bindings for the USB PHY and comparator module found within the + TWL4030 family of companion chips. If a sibling node is compatible with + "ti,twl4030-bci", the driver for that node will query this device for + USB power status. + +properties: + compatible: + const: ti,twl4030-usb + + interrupts: + minItems: 1 + items: + - description: OTG interrupt number for ID events. + - description: USB interrupt number for VBUS events. + + usb1v5-supply: + description: Phandle to the vusb1v5 regulator. + + usb1v8-supply: + description: Phandle to the vusb1v8 regulator. + + usb3v1-supply: + description: Phandle to the vusb3v1 regulator. + + usb_mode: + description: | + The mode used by the PHY to connect to the controller: + 1: ULPI mode + 2: CEA2011_3PIN mode + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2] + + '#phy-cells': + const: 0 + +required: + - compatible + - interrupts + - usb1v5-supply + - usb1v8-supply + - usb3v1-supply + - usb_mode + +additionalProperties: false + +examples: + - | + #include + + usb-phy { + compatible = "ti,twl4030-usb"; + + interrupts = <10 IRQ_TYPE_LEVEL_HIGH>; + interrupt-parent = <&gic>; + + usb1v5-supply = <®_vusb1v5>; + usb1v8-supply = <®_vusb1v8>; + usb3v1-supply = <®_vusb3v1>; + usb_mode = <1>; + + #phy-cells = <0>; + }; diff --git a/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt b/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt index 17327a296110..7194c9504b28 100644 --- a/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt +++ b/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt @@ -19,25 +19,3 @@ Board specific device node entry &twl6030-usb { usb-supply = <&vusb>; }; - -TWL4030 USB PHY AND COMPARATOR - - compatible : Should be "ti,twl4030-usb" - - interrupts : The interrupt numbers to the cpu should be specified. First - interrupt number is the otg interrupt number that raises ID interrupts - and VBUS interrupts. The second interrupt number is optional. - - -supply : phandle to the regulator device tree node. - should be vusb1v5, vusb1v8 and vusb3v1 - - usb_mode : The mode used by the phy to connect to the controller. "1" - specifies "ULPI" mode and "2" specifies "CEA2011_3PIN" mode. - -If a sibling node is compatible "ti,twl4030-bci", then it will find -this device and query it for USB power status. - -twl4030-usb { - compatible = "ti,twl4030-usb"; - interrupts = < 10 4 >; - usb1v5-supply = <&vusb1v5>; - usb1v8-supply = <&vusb1v8>; - usb3v1-supply = <&vusb3v1>; - usb_mode = <1>; -}; From 1daa7f57b85038eb3a4e6220a9623557a759758c Mon Sep 17 00:00:00 2001 From: Jihed Chaibi Date: Sun, 24 Aug 2025 13:23:38 +0200 Subject: [PATCH 030/132] usb: dt-bindings: ti,twl6030-usb: convert to DT schema Convert the legacy TXT binding for the TWL6030 USB module to the modern YAML DT schema format. This adds formal validation and improves documentation using a conditional schema. The legacy twlxxxx-usb.txt file is no longer needed and is removed. Signed-off-by: Jihed Chaibi Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20250824112338.64953-3-jihed.chaibi.dev@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/ti,twl6030-usb.yaml | 48 +++++++++++++++++++ .../devicetree/bindings/usb/twlxxxx-usb.txt | 21 -------- 2 files changed, 48 insertions(+), 21 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/ti,twl6030-usb.yaml delete mode 100644 Documentation/devicetree/bindings/usb/twlxxxx-usb.txt diff --git a/Documentation/devicetree/bindings/usb/ti,twl6030-usb.yaml b/Documentation/devicetree/bindings/usb/ti,twl6030-usb.yaml new file mode 100644 index 000000000000..33b6da50660a --- /dev/null +++ b/Documentation/devicetree/bindings/usb/ti,twl6030-usb.yaml @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/ti,twl6030-usb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments TWL6030 USB Comparator + +maintainers: + - Peter Ujfalusi + +description: + Bindings for the USB comparator module found within the TWL6030 + family of companion chips. + +properties: + compatible: + const: ti,twl6030-usb + + interrupts: + items: + - description: OTG for ID events in host mode + - description: USB device mode for VBUS events + + usb-supply: + description: + Phandle to the VUSB regulator. For TWL6030, this should be the 'vusb' + regulator. For TWL6032 subclass, it should be the 'ldousb' regulator. + +required: + - compatible + - interrupts + - usb-supply + +additionalProperties: false + +examples: + - | + #include + + usb { + compatible = "ti,twl6030-usb"; + + interrupts = <4 IRQ_TYPE_LEVEL_HIGH>, <10 IRQ_TYPE_LEVEL_HIGH>; + interrupt-parent = <&gic>; + + usb-supply = <®_vusb>; + }; diff --git a/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt b/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt deleted file mode 100644 index 7194c9504b28..000000000000 --- a/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt +++ /dev/null @@ -1,21 +0,0 @@ -USB COMPARATOR OF TWL CHIPS - -TWL6030 USB COMPARATOR - - compatible : Should be "ti,twl6030-usb" - - interrupts : Two interrupt numbers to the cpu should be specified. First - interrupt number is the otg interrupt number that raises ID interrupts when - the controller has to act as host and the second interrupt number is the - usb interrupt number that raises VBUS interrupts when the controller has to - act as device - - usb-supply : phandle to the regulator device tree node. It should be vusb - if it is twl6030 or ldousb if it is twl6032 subclass. - -twl6030-usb { - compatible = "ti,twl6030-usb"; - interrupts = < 4 10 >; -}; - -Board specific device node entry -&twl6030-usb { - usb-supply = <&vusb>; -}; From 0d7395046bb3e412975bf20bb08b41f34c1cd5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gon=C3=A7alves?= Date: Mon, 25 Aug 2025 04:17:39 +0000 Subject: [PATCH 031/132] tools/usb/usbip: fix spelling mistakes in usbipd.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct two occurences of 'seperate' typos on comments in lines 399 and 520. Signed-off-by: Eric Gonçalves Link: https://lore.kernel.org/r/20250825041739.8117-1-ghatto404@gmail.com Signed-off-by: Greg Kroah-Hartman --- tools/usb/usbip/src/usbipd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/usb/usbip/src/usbipd.c b/tools/usb/usbip/src/usbipd.c index 48398a78e88a..3e22b651c754 100644 --- a/tools/usb/usbip/src/usbipd.c +++ b/tools/usb/usbip/src/usbipd.c @@ -396,7 +396,7 @@ static int listen_all_addrinfo(struct addrinfo *ai_head, int sockfdlist[], usbip_net_set_reuseaddr(sock); usbip_net_set_nodelay(sock); - /* We use seperate sockets for IPv4 and IPv6 + /* We use separate sockets for IPv4 and IPv6 * (see do_standalone_mode()) */ usbip_net_set_v6only(sock); @@ -517,7 +517,7 @@ static int do_standalone_mode(int daemonize, int ipv4, int ipv6) /* * To suppress warnings on systems with bindv6only disabled - * (default), we use seperate sockets for IPv6 and IPv4 and set + * (default), we use separate sockets for IPv6 and IPv4 and set * IPV6_V6ONLY on the IPv6 sockets. */ if (ipv4 && ipv6) From ab96716991f3b7634566bc000d69a66b8b2ecc15 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Mon, 25 Aug 2025 14:49:00 +0200 Subject: [PATCH 032/132] usb: musb: dsps: use platform_get_irq_byname_optional() for vbus IRQ The vbus IRQ is optional, so no error message should be printed if it is not available. Signed-off-by: Matthias Schiffer Signed-off-by: Alexander Stein Link: https://lore.kernel.org/r/20250825124901.2190539-1-alexander.stein@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musb_dsps.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/musb/musb_dsps.c b/drivers/usb/musb/musb_dsps.c index 12f587ab8511..a08ce96c08d3 100644 --- a/drivers/usb/musb/musb_dsps.c +++ b/drivers/usb/musb/musb_dsps.c @@ -839,7 +839,7 @@ static int dsps_setup_optional_vbus_irq(struct platform_device *pdev, { int error; - glue->vbus_irq = platform_get_irq_byname(pdev, "vbus"); + glue->vbus_irq = platform_get_irq_byname_optional(pdev, "vbus"); if (glue->vbus_irq == -EPROBE_DEFER) return -EPROBE_DEFER; From e271cc0d25015f4be6c88bd7731444644eb352c2 Mon Sep 17 00:00:00 2001 From: William Wu Date: Fri, 22 Aug 2025 11:36:09 +0800 Subject: [PATCH 033/132] usb: gadget: configfs: Correctly set use_os_string at bind Once the use_os_string flag is set to true for some functions (e.g. adb/mtp) which need to response the OS string, and then if we re-bind the ConfigFS gadget to use the other functions (e.g. hid) which should not to response the OS string, however, because the use_os_string flag is still true, so the usb gadget response the OS string descriptor incorrectly, this can cause the USB device to be unrecognizable on the Windows system. An example of this as follows: echo 1 > os_desc/use ln -s functions/ffs.adb configs/b.1/function0 start adbd echo "" > UDC #succeed stop adbd rm configs/b.1/function0 echo 0 > os_desc/use ln -s functions/hid.gs0 configs/b.1/function0 echo "" > UDC #fail to connect on Windows This patch sets the use_os_string flag to false at bind if the functions not support OS Descriptors. Signed-off-by: William Wu Fixes: 87213d388e92 ("usb: gadget: configfs: OS String support") Link: https://lore.kernel.org/r/1755833769-25434-1-git-send-email-william.wu@rock-chips.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/configfs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index f94ea196ce54..6bcac85c5550 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -1750,6 +1750,8 @@ static int configfs_composite_bind(struct usb_gadget *gadget, cdev->use_os_string = true; cdev->b_vendor_code = gi->b_vendor_code; memcpy(cdev->qw_sign, gi->qw_sign, OS_STRING_QW_SIGN_LEN); + } else { + cdev->use_os_string = false; } if (gadget_is_otg(gadget) && !otg_desc[0]) { From ed6f727c575b1eb8136e744acfd5e7306c9548f6 Mon Sep 17 00:00:00 2001 From: William Wu Date: Tue, 26 Aug 2025 18:28:07 +0800 Subject: [PATCH 034/132] usb: gadget: f_hid: Fix zero length packet transfer Set the hid req->zero flag of ep0/in_ep to true by default, then the UDC drivers can transfer a zero length packet at the end if the hid transfer with size divisible to EPs max packet size according to the USB 2.0 spec. Signed-off-by: William Wu Link: https://lore.kernel.org/r/1756204087-26111-1-git-send-email-william.wu@rock-chips.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_hid.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index 8e1d1e884050..307ea563af95 100644 --- a/drivers/usb/gadget/function/f_hid.c +++ b/drivers/usb/gadget/function/f_hid.c @@ -511,7 +511,7 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer, } req->status = 0; - req->zero = 0; + req->zero = 1; req->length = count; req->complete = f_hidg_req_complete; req->context = hidg; @@ -967,7 +967,7 @@ static int hidg_setup(struct usb_function *f, return -EOPNOTSUPP; respond: - req->zero = 0; + req->zero = 1; req->length = length; status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); if (status < 0) From dc60a1cd7a76f2945159a461768010b8154ac8f8 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 31 Aug 2025 14:22:23 +0200 Subject: [PATCH 035/132] usb: ohci: s3c2410: Drop support for S3C2410 systems Samsung S3C24xx family of SoCs was removed the Linux kernel in the commit 61b7f8920b17 ("ARM: s3c: remove all s3c24xx support"), in January 2023. There are no in-kernel users of remaining S3C24xx compatibles. The driver (named s3c2410) is still being used via platform code for S3C64xx platforms. Signed-off-by: Krzysztof Kozlowski Acked-by: Alan Stern Link: https://lore.kernel.org/r/20250831122222.50332-3-krzysztof.kozlowski@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-s3c2410.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index 66d970854357..e623e24d3f8e 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -448,13 +448,6 @@ static const struct dev_pm_ops ohci_hcd_s3c2410_pm_ops = { .resume = ohci_hcd_s3c2410_drv_resume, }; -static const struct of_device_id ohci_hcd_s3c2410_dt_ids[] = { - { .compatible = "samsung,s3c2410-ohci" }, - { /* sentinel */ } -}; - -MODULE_DEVICE_TABLE(of, ohci_hcd_s3c2410_dt_ids); - static struct platform_driver ohci_hcd_s3c2410_driver = { .probe = ohci_hcd_s3c2410_probe, .remove = ohci_hcd_s3c2410_remove, @@ -462,7 +455,6 @@ static struct platform_driver ohci_hcd_s3c2410_driver = { .driver = { .name = "s3c2410-ohci", .pm = &ohci_hcd_s3c2410_pm_ops, - .of_match_table = ohci_hcd_s3c2410_dt_ids, }, }; From 771713aeaca19b4dfb47595bea514859f55747b9 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 31 Aug 2025 14:22:24 +0200 Subject: [PATCH 036/132] dt-bindings: usb: s3c2410-usb: Drop entirely S3C2410 Samsung S3C24xx family of SoCs was removed the Linux kernel in the commit 61b7f8920b17 ("ARM: s3c: remove all s3c24xx support"), in January 2023. There are no in-kernel users of remaining S3C24xx compatibles. Signed-off-by: Krzysztof Kozlowski Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250831122222.50332-4-krzysztof.kozlowski@linaro.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/s3c2410-usb.txt | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/s3c2410-usb.txt diff --git a/Documentation/devicetree/bindings/usb/s3c2410-usb.txt b/Documentation/devicetree/bindings/usb/s3c2410-usb.txt deleted file mode 100644 index 26c85afd0b53..000000000000 --- a/Documentation/devicetree/bindings/usb/s3c2410-usb.txt +++ /dev/null @@ -1,22 +0,0 @@ -Samsung S3C2410 and compatible SoC USB controller - -OHCI - -Required properties: - - compatible: should be "samsung,s3c2410-ohci" for USB host controller - - reg: address and length of the controller memory mapped region - - interrupts: interrupt number for the USB OHCI controller - - clocks: Should reference the bus and host clocks - - clock-names: Should contain two strings - "usb-bus-host" for the USB bus clock - "usb-host" for the USB host clock - -Example: - -usb0: ohci@49000000 { - compatible = "samsung,s3c2410-ohci"; - reg = <0x49000000 0x100>; - interrupts = <0 0 26 3>; - clocks = <&clocks UCLK>, <&clocks HCLK_USBH>; - clock-names = "usb-bus-host", "usb-host"; -}; From ea32cd911368e32eb770c9078158467845c82b9a Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Tue, 26 Aug 2025 07:45:07 +0800 Subject: [PATCH 037/132] dt-bindings: usb: usb251xb: support usage case without I2C control Currently, the usb251xb assumes i2c control, but from HW point of view, the hub supports usage case without any i2c control, I.E we only want the gpio controls, for example the following dt node: usb-hub { compatible = "microchip,usb2512b"; reset-gpios = <&porta 8 GPIO_ACTIVE_LOW>; }; Modify the dt-binding of usb2512b to support this usage case, and add the usage example to the examples section. Signed-off-by: Jisheng Zhang Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20250825234509.1041-2-jszhang@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb251xb.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/usb251xb.yaml b/Documentation/devicetree/bindings/usb/usb251xb.yaml index ac5b99710332..0329a6aaaa92 100644 --- a/Documentation/devicetree/bindings/usb/usb251xb.yaml +++ b/Documentation/devicetree/bindings/usb/usb251xb.yaml @@ -240,7 +240,6 @@ additionalProperties: false required: - compatible - - reg examples: - | @@ -269,3 +268,11 @@ examples: swap-dx-lanes = <1 2>; }; }; + + - | + #include + usb-hub { + /* I2C is not connected */ + compatible = "microchip,usb2512b"; + reset-gpios = <&porta 8 GPIO_ACTIVE_LOW>; + }; From 22fbedf9d9c2d219339620a95a0611c9ddfc397b Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Tue, 26 Aug 2025 07:45:08 +0800 Subject: [PATCH 038/132] usb: usb251xb: use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused. Signed-off-by: Jisheng Zhang Reviewed-by: Richard Leitner Link: https://lore.kernel.org/r/20250825234509.1041-3-jszhang@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usb251xb.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c index 4fb453ca5450..cb2f946de42c 100644 --- a/drivers/usb/misc/usb251xb.c +++ b/drivers/usb/misc/usb251xb.c @@ -698,7 +698,7 @@ static int usb251xb_i2c_probe(struct i2c_client *i2c) return usb251xb_probe(hub); } -static int __maybe_unused usb251xb_suspend(struct device *dev) +static int usb251xb_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct usb251xb *hub = i2c_get_clientdata(client); @@ -706,7 +706,7 @@ static int __maybe_unused usb251xb_suspend(struct device *dev) return regulator_disable(hub->vdd); } -static int __maybe_unused usb251xb_resume(struct device *dev) +static int usb251xb_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct usb251xb *hub = i2c_get_clientdata(client); @@ -719,7 +719,7 @@ static int __maybe_unused usb251xb_resume(struct device *dev) return usb251xb_connect(hub); } -static SIMPLE_DEV_PM_OPS(usb251xb_pm_ops, usb251xb_suspend, usb251xb_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_pm_ops, usb251xb_suspend, usb251xb_resume); static const struct i2c_device_id usb251xb_id[] = { { "usb2422" }, @@ -739,7 +739,7 @@ static struct i2c_driver usb251xb_i2c_driver = { .driver = { .name = DRIVER_NAME, .of_match_table = usb251xb_of_match, - .pm = &usb251xb_pm_ops, + .pm = pm_sleep_ptr(&usb251xb_pm_ops), }, .probe = usb251xb_i2c_probe, .id_table = usb251xb_id, From 2cf8ecd0ae650c1f0e551326bb7f060abf1560d3 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Tue, 26 Aug 2025 07:45:09 +0800 Subject: [PATCH 039/132] usb: usb251xb: support usage case without I2C control Currently, the usb251xb assumes i2c control. But from HW point of view, the hub supports usage case without any i2c, we only want the gpio controls. Refactor the code so that register writes for configuration are only performed if the device has a i2c_client provided and also register as a platform driver. This allows the driver to be used to manage GPIO based control of the device. Signed-off-by: Jisheng Zhang Link: https://lore.kernel.org/r/20250825234509.1041-4-jszhang@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usb251xb.c | 108 +++++++++++++++++++++++++++++++----- 1 file changed, 95 insertions(+), 13 deletions(-) diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c index cb2f946de42c..7c0778631bea 100644 --- a/drivers/usb/misc/usb251xb.c +++ b/drivers/usb/misc/usb251xb.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -242,15 +243,19 @@ static int usb251xb_check_dev_children(struct device *dev, void *child) static int usb251x_check_gpio_chip(struct usb251xb *hub) { struct gpio_chip *gc = gpiod_to_chip(hub->gpio_reset); - struct i2c_adapter *adap = hub->i2c->adapter; + struct i2c_adapter *adap; int ret; + if (!hub->i2c) + return 0; + if (!hub->gpio_reset) return 0; if (!gc) return -EINVAL; + adap = hub->i2c->adapter; ret = usb251xb_check_dev_children(&adap->dev, gc->parent); if (ret) { dev_err(hub->dev, "Reset GPIO chip is at the same i2c-bus\n"); @@ -271,7 +276,8 @@ static void usb251xb_reset(struct usb251xb *hub) if (!hub->gpio_reset) return; - i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); + if (hub->i2c) + i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); gpiod_set_value_cansleep(hub->gpio_reset, 1); usleep_range(1, 10); /* >=1us RESET_N asserted */ @@ -280,7 +286,8 @@ static void usb251xb_reset(struct usb251xb *hub) /* wait for hub recovery/stabilization */ usleep_range(500, 750); /* >=500us after RESET_N deasserted */ - i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); + if (hub->i2c) + i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); } static int usb251xb_connect(struct usb251xb *hub) @@ -289,6 +296,12 @@ static int usb251xb_connect(struct usb251xb *hub) int err, i; char i2c_wb[USB251XB_I2C_REG_SZ]; + if (!hub->i2c) { + usb251xb_reset(hub); + dev_info(dev, "hub is put in default configuration.\n"); + return 0; + } + memset(i2c_wb, 0, USB251XB_I2C_REG_SZ); if (hub->skip_config) { @@ -698,18 +711,13 @@ static int usb251xb_i2c_probe(struct i2c_client *i2c) return usb251xb_probe(hub); } -static int usb251xb_suspend(struct device *dev) +static int usb251xb_suspend(struct usb251xb *hub) { - struct i2c_client *client = to_i2c_client(dev); - struct usb251xb *hub = i2c_get_clientdata(client); - return regulator_disable(hub->vdd); } -static int usb251xb_resume(struct device *dev) +static int usb251xb_resume(struct usb251xb *hub) { - struct i2c_client *client = to_i2c_client(dev); - struct usb251xb *hub = i2c_get_clientdata(client); int err; err = regulator_enable(hub->vdd); @@ -719,7 +727,23 @@ static int usb251xb_resume(struct device *dev) return usb251xb_connect(hub); } -static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_pm_ops, usb251xb_suspend, usb251xb_resume); +static int usb251xb_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb251xb *hub = i2c_get_clientdata(client); + + return usb251xb_suspend(hub); +} + +static int usb251xb_i2c_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb251xb *hub = i2c_get_clientdata(client); + + return usb251xb_resume(hub); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_i2c_pm_ops, usb251xb_i2c_suspend, usb251xb_i2c_resume); static const struct i2c_device_id usb251xb_id[] = { { "usb2422" }, @@ -739,13 +763,71 @@ static struct i2c_driver usb251xb_i2c_driver = { .driver = { .name = DRIVER_NAME, .of_match_table = usb251xb_of_match, - .pm = pm_sleep_ptr(&usb251xb_pm_ops), + .pm = pm_sleep_ptr(&usb251xb_i2c_pm_ops), }, .probe = usb251xb_i2c_probe, .id_table = usb251xb_id, }; -module_i2c_driver(usb251xb_i2c_driver); +static int usb251xb_plat_probe(struct platform_device *pdev) +{ + struct usb251xb *hub; + + hub = devm_kzalloc(&pdev->dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + platform_set_drvdata(pdev, hub); + hub->dev = &pdev->dev; + + return usb251xb_probe(hub); +} + +static int usb251xb_plat_suspend(struct device *dev) +{ + return usb251xb_suspend(dev_get_drvdata(dev)); +} + +static int usb251xb_plat_resume(struct device *dev) +{ + return usb251xb_resume(dev_get_drvdata(dev)); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_plat_pm_ops, usb251xb_plat_suspend, usb251xb_plat_resume); + +static struct platform_driver usb251xb_plat_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = usb251xb_of_match, + .pm = pm_sleep_ptr(&usb251xb_plat_pm_ops), + }, + .probe = usb251xb_plat_probe, +}; + +static int __init usb251xb_init(void) +{ + int err; + + err = i2c_add_driver(&usb251xb_i2c_driver); + if (err) + return err; + + err = platform_driver_register(&usb251xb_plat_driver); + if (err) { + i2c_del_driver(&usb251xb_i2c_driver); + return err; + } + + return 0; +} +module_init(usb251xb_init); + +static void __exit usb251xb_exit(void) +{ + platform_driver_unregister(&usb251xb_plat_driver); + i2c_del_driver(&usb251xb_i2c_driver); +} +module_exit(usb251xb_exit); MODULE_AUTHOR("Richard Leitner "); MODULE_DESCRIPTION("USB251x/xBi USB 2.0 Hub Controller Driver"); From 56429b4a37e710429e20a4f8dc85e96fcbb9f4b7 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Thu, 28 Aug 2025 21:55:53 -0400 Subject: [PATCH 040/132] cdns2: Remove unused tracepoints Tracepoints that are defined take up around 5K each, even if they are not used. If they are defined and not used, then they waste memory for unused code. Soon unused tracepoints will cause warnings. Remove the unused tracepoints of the cdns2 subsystem. They are: cdns2_ep0_enqueue cdns2_ep0_set_config cdns2_ep_queue cdns2_iso_out_ep_disable cdns2_lpm cdns2_mapped_request cdns2_map_request cdns2_may_wakeup Signed-off-by: Steven Rostedt (Google) Link: https://lore.kernel.org/r/20250829015649.885512884@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/cdns2/cdns2-trace.h | 69 ---------------------- 1 file changed, 69 deletions(-) diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-trace.h b/drivers/usb/gadget/udc/cdns2/cdns2-trace.h index ade1752956b1..f4df0e2ff853 100644 --- a/drivers/usb/gadget/udc/cdns2/cdns2-trace.h +++ b/drivers/usb/gadget/udc/cdns2/cdns2-trace.h @@ -47,16 +47,6 @@ DEFINE_EVENT(cdns2_log_enable_disable, cdns2_pullup, TP_ARGS(set) ); -DEFINE_EVENT(cdns2_log_enable_disable, cdns2_lpm, - TP_PROTO(int set), - TP_ARGS(set) -); - -DEFINE_EVENT(cdns2_log_enable_disable, cdns2_may_wakeup, - TP_PROTO(int set), - TP_ARGS(set) -); - DECLARE_EVENT_CLASS(cdns2_log_simple, TP_PROTO(char *msg), TP_ARGS(msg), @@ -79,11 +69,6 @@ DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_status_stage, TP_ARGS(msg) ); -DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_set_config, - TP_PROTO(char *msg), - TP_ARGS(msg) -); - DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_setup, TP_PROTO(char *msg), TP_ARGS(msg) @@ -340,11 +325,6 @@ DEFINE_EVENT(cdns2_log_request, cdns2_free_request, TP_ARGS(preq) ); -DEFINE_EVENT(cdns2_log_request, cdns2_ep_queue, - TP_PROTO(struct cdns2_request *preq), - TP_ARGS(preq) -); - DEFINE_EVENT(cdns2_log_request, cdns2_request_dequeue, TP_PROTO(struct cdns2_request *preq), TP_ARGS(preq) @@ -355,50 +335,6 @@ DEFINE_EVENT(cdns2_log_request, cdns2_request_giveback, TP_ARGS(preq) ); -TRACE_EVENT(cdns2_ep0_enqueue, - TP_PROTO(struct cdns2_device *dev_priv, struct usb_request *request), - TP_ARGS(dev_priv, request), - TP_STRUCT__entry( - __field(int, dir) - __field(int, length) - ), - TP_fast_assign( - __entry->dir = dev_priv->eps[0].dir; - __entry->length = request->length; - ), - TP_printk("Queue to ep0%s length: %u", __entry->dir ? "in" : "out", - __entry->length) -); - -DECLARE_EVENT_CLASS(cdns2_log_map_request, - TP_PROTO(struct cdns2_request *priv_req), - TP_ARGS(priv_req), - TP_STRUCT__entry( - __string(name, priv_req->pep->name) - __field(struct usb_request *, req) - __field(void *, buf) - __field(dma_addr_t, dma) - ), - TP_fast_assign( - __assign_str(name); - __entry->req = &priv_req->request; - __entry->buf = priv_req->request.buf; - __entry->dma = priv_req->request.dma; - ), - TP_printk("%s: req: %p, req buf %p, dma %p", - __get_str(name), __entry->req, __entry->buf, &__entry->dma - ) -); - -DEFINE_EVENT(cdns2_log_map_request, cdns2_map_request, - TP_PROTO(struct cdns2_request *req), - TP_ARGS(req) -); -DEFINE_EVENT(cdns2_log_map_request, cdns2_mapped_request, - TP_PROTO(struct cdns2_request *req), - TP_ARGS(req) -); - DECLARE_EVENT_CLASS(cdns2_log_trb, TP_PROTO(struct cdns2_endpoint *pep, struct cdns2_trb *trb), TP_ARGS(pep, trb), @@ -507,11 +443,6 @@ DEFINE_EVENT(cdns2_log_ep, cdns2_gadget_ep_disable, TP_ARGS(pep) ); -DEFINE_EVENT(cdns2_log_ep, cdns2_iso_out_ep_disable, - TP_PROTO(struct cdns2_endpoint *pep), - TP_ARGS(pep) -); - DEFINE_EVENT(cdns2_log_ep, cdns2_ep_busy_try_halt_again, TP_PROTO(struct cdns2_endpoint *pep), TP_ARGS(pep) From 1c15b2f88127d5980747f2f62beb6e1f6e62f883 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Thu, 28 Aug 2025 21:55:54 -0400 Subject: [PATCH 041/132] cdns3: Remove unused tracepoints Tracepoints that are defined take up around 5K each, even if they are not used. If they are defined and not used, then they waste memory for unused code. Soon unused tracepoints will cause warnings. Remove the unused tracepoints of the cdns3 subsystem. They are: cdns3_mapped_request cdns3_map_request cdns3_stream_transfer_split cdns3_stream_transfer_split_next_part Signed-off-by: Steven Rostedt (Google) Acked-by: Peter Chen Link: https://lore.kernel.org/r/20250829015650.053498355@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdns3-trace.h | 61 --------------------------------- 1 file changed, 61 deletions(-) diff --git a/drivers/usb/cdns3/cdns3-trace.h b/drivers/usb/cdns3/cdns3-trace.h index c4e542f1b9b7..21a6a2ce7a3d 100644 --- a/drivers/usb/cdns3/cdns3-trace.h +++ b/drivers/usb/cdns3/cdns3-trace.h @@ -283,39 +283,6 @@ TRACE_EVENT(cdns3_ep0_queue, __entry->length) ); -DECLARE_EVENT_CLASS(cdns3_stream_split_transfer_len, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req), - TP_STRUCT__entry( - __string(name, req->priv_ep->name) - __field(struct cdns3_request *, req) - __field(unsigned int, length) - __field(unsigned int, actual) - __field(unsigned int, stream_id) - ), - TP_fast_assign( - __assign_str(name); - __entry->req = req; - __entry->actual = req->request.length; - __entry->length = req->request.actual; - __entry->stream_id = req->request.stream_id; - ), - TP_printk("%s: req: %p,request length: %u actual length: %u SID: %u", - __get_str(name), __entry->req, __entry->length, - __entry->actual, __entry->stream_id) -); - -DEFINE_EVENT(cdns3_stream_split_transfer_len, cdns3_stream_transfer_split, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - -DEFINE_EVENT(cdns3_stream_split_transfer_len, - cdns3_stream_transfer_split_next_part, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - DECLARE_EVENT_CLASS(cdns3_log_aligned_request, TP_PROTO(struct cdns3_request *priv_req), TP_ARGS(priv_req), @@ -354,34 +321,6 @@ DEFINE_EVENT(cdns3_log_aligned_request, cdns3_prepare_aligned_request, TP_ARGS(req) ); -DECLARE_EVENT_CLASS(cdns3_log_map_request, - TP_PROTO(struct cdns3_request *priv_req), - TP_ARGS(priv_req), - TP_STRUCT__entry( - __string(name, priv_req->priv_ep->name) - __field(struct usb_request *, req) - __field(void *, buf) - __field(dma_addr_t, dma) - ), - TP_fast_assign( - __assign_str(name); - __entry->req = &priv_req->request; - __entry->buf = priv_req->request.buf; - __entry->dma = priv_req->request.dma; - ), - TP_printk("%s: req: %p, req buf %p, dma %p", - __get_str(name), __entry->req, __entry->buf, &__entry->dma - ) -); -DEFINE_EVENT(cdns3_log_map_request, cdns3_map_request, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); -DEFINE_EVENT(cdns3_log_map_request, cdns3_mapped_request, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - DECLARE_EVENT_CLASS(cdns3_log_trb, TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb), TP_ARGS(priv_ep, trb), From 9d4552da0ae8f056313cc290c0f493f59c6f7eef Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Thu, 28 Aug 2025 21:55:55 -0400 Subject: [PATCH 042/132] cdnsp: Remove unused tracepoints Tracepoints that are defined take up around 5K each, even if they are not used. If they are defined and not used, then they waste memory for unused code. Soon unused tracepoints will cause warnings. Remove the unused tracepoints of the cdnsp subsystem. They are: cdnsp_defered_event cdnsp_ep0_halted cdnsp_free_priv_device cdnsp_handle_cmd_flush_ep Signed-off-by: Steven Rostedt (Google) Acked-by: Peter Chen Link: https://lore.kernel.org/r/20250829015650.224063821@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdnsp-trace.h | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/drivers/usb/cdns3/cdnsp-trace.h b/drivers/usb/cdns3/cdnsp-trace.h index f2bcf77a5d0a..9b33a736c3de 100644 --- a/drivers/usb/cdns3/cdnsp-trace.h +++ b/drivers/usb/cdns3/cdnsp-trace.h @@ -178,11 +178,6 @@ DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_set_config, TP_ARGS(msg) ); -DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_halted, - TP_PROTO(char *msg), - TP_ARGS(msg) -); - DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep_halt, TP_PROTO(char *msg), TP_ARGS(msg) @@ -399,11 +394,6 @@ DEFINE_EVENT(cdnsp_log_trb, cdnsp_cmd_timeout, TP_ARGS(ring, trb) ); -DEFINE_EVENT(cdnsp_log_trb, cdnsp_defered_event, - TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), - TP_ARGS(ring, trb) -); - DECLARE_EVENT_CLASS(cdnsp_log_pdev, TP_PROTO(struct cdnsp_device *pdev), TP_ARGS(pdev), @@ -433,16 +423,6 @@ DEFINE_EVENT(cdnsp_log_pdev, cdnsp_alloc_priv_device, TP_ARGS(vdev) ); -DEFINE_EVENT(cdnsp_log_pdev, cdnsp_free_priv_device, - TP_PROTO(struct cdnsp_device *vdev), - TP_ARGS(vdev) -); - -DEFINE_EVENT(cdnsp_log_pdev, cdnsp_setup_device, - TP_PROTO(struct cdnsp_device *vdev), - TP_ARGS(vdev) -); - DEFINE_EVENT(cdnsp_log_pdev, cdnsp_setup_addressable_priv_device, TP_PROTO(struct cdnsp_device *vdev), TP_ARGS(vdev) @@ -575,11 +555,6 @@ DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_stop_ep, TP_ARGS(ctx) ); -DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_flush_ep, - TP_PROTO(struct cdnsp_ep_ctx *ctx), - TP_ARGS(ctx) -); - DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_set_deq_ep, TP_PROTO(struct cdnsp_ep_ctx *ctx), TP_ARGS(ctx) From 1c208fd306f296a663d1edcced57bd8ef113eb74 Mon Sep 17 00:00:00 2001 From: Liao Yuanhong Date: Tue, 2 Sep 2025 21:27:19 +0800 Subject: [PATCH 043/132] usb: host: xhci-tegra: Remove redundant ternary operators For ternary operators in the form of "a ? true : false", if 'a' itself returns a boolean result, the ternary operator can be omitted. Remove redundant ternary operators to clean up the code. Signed-off-by: Liao Yuanhong Link: https://lore.kernel.org/r/20250902132720.85504-1-liaoyuanhong@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-tegra.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 0c7af44d4dae..460effa0e11e 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -1482,7 +1482,7 @@ static int tegra_xhci_id_notify(struct notifier_block *nb, tegra->otg_usb2_port = tegra_xusb_get_usb2_port(tegra, usbphy); - tegra->host_mode = (usbphy->last_event == USB_EVENT_ID) ? true : false; + tegra->host_mode = usbphy->last_event == USB_EVENT_ID; schedule_work(&tegra->id_work); From e9c206324eeb213957a567a9d066bdeb355c7491 Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Wed, 3 Sep 2025 22:16:13 +0800 Subject: [PATCH 044/132] usb: cdns3: cdnsp-pci: remove redundant pci_disable_device() call The cdnsp-pci driver uses pcim_enable_device() to enable a PCI device, which means the device will be automatically disabled on driver detach through the managed device framework. The manual pci_disable_device() call in the error path is therefore redundant. Found via static anlaysis and this is similar to commit 99ca0b57e49f ("thermal: intel: int340x: processor: Fix warning during module unload"). Fixes: 3d82904559f4 ("usb: cdnsp: cdns3 Add main part of Cadence USBSSP DRD Driver") Cc: stable@vger.kernel.org Signed-off-by: Miaoqian Lin Link: https://lore.kernel.org/r/20250903141613.2535472-1-linmq006@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdnsp-pci.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/usb/cdns3/cdnsp-pci.c b/drivers/usb/cdns3/cdnsp-pci.c index 8c361b8394e9..5e7b88ca8b96 100644 --- a/drivers/usb/cdns3/cdnsp-pci.c +++ b/drivers/usb/cdns3/cdnsp-pci.c @@ -85,7 +85,7 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, cdnsp = kzalloc(sizeof(*cdnsp), GFP_KERNEL); if (!cdnsp) { ret = -ENOMEM; - goto disable_pci; + goto put_pci; } } @@ -168,9 +168,6 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, if (!pci_is_enabled(func)) kfree(cdnsp); -disable_pci: - pci_disable_device(pdev); - put_pci: pci_dev_put(func); From 87c5ff5615dc0a37167e8faf3adeeddc6f1344a3 Mon Sep 17 00:00:00 2001 From: Chen Yufeng Date: Fri, 5 Sep 2025 17:48:42 +0800 Subject: [PATCH 045/132] usb: cdns3: gadget: Use-after-free during failed initialization and exit of cdnsp gadget In the __cdnsp_gadget_init() and cdnsp_gadget_exit() functions, the gadget structure (pdev->gadget) was freed before its endpoints. The endpoints are linked via the ep_list in the gadget structure. Freeing the gadget first leaves dangling pointers in the endpoint list. When the endpoints are subsequently freed, this results in a use-after-free. Fix: By separating the usb_del_gadget_udc() operation into distinct "del" and "put" steps, cdnsp_gadget_free_endpoints() can be executed prior to the final release of the gadget structure with usb_put_gadget(). A patch similar to bb9c74a5bd14("usb: dwc3: gadget: Free gadget structure only after freeing endpoints"). Signed-off-by: Chen Yufeng Link: https://lore.kernel.org/r/20250905094842.1232-1-chenyufeng@iie.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdnsp-gadget.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index 55f95f41b3b4..0252560cbc80 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -1976,7 +1976,10 @@ static int __cdnsp_gadget_init(struct cdns *cdns) return 0; del_gadget: - usb_del_gadget_udc(&pdev->gadget); + usb_del_gadget(&pdev->gadget); + cdnsp_gadget_free_endpoints(pdev); + usb_put_gadget(&pdev->gadget); + goto halt_pdev; free_endpoints: cdnsp_gadget_free_endpoints(pdev); halt_pdev: @@ -1998,8 +2001,9 @@ static void cdnsp_gadget_exit(struct cdns *cdns) devm_free_irq(pdev->dev, cdns->dev_irq, pdev); pm_runtime_mark_last_busy(cdns->dev); pm_runtime_put_autosuspend(cdns->dev); - usb_del_gadget_udc(&pdev->gadget); + usb_del_gadget(&pdev->gadget); cdnsp_gadget_free_endpoints(pdev); + usb_put_gadget(&pdev->gadget); cdnsp_mem_cleanup(pdev); kfree(pdev); cdns->gadget_dev = NULL; From 1bc28f015a19a10b7a305fc561c1143c34a1e04a Mon Sep 17 00:00:00 2001 From: Venkat Jayaraman Date: Fri, 5 Sep 2025 11:44:01 -0700 Subject: [PATCH 046/132] usb: typec: ucsi: Add check for UCSI version "Power Reading" bit is introduced in UCSI v2.1 and so limit the check for that bit only if version supported is 2.1 or above. Fixes: c851b71fd6cd ("usb: typec: ucsi: Add support for READ_POWER_LEVEL command") Signed-off-by: Venkat Jayaraman Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20250905184401.3222530-1-venkat.jayaraman@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 0d6b0cf5a7cd..3f568f790f39 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1293,7 +1293,8 @@ static void ucsi_handle_connector_change(struct work_struct *work) if (change & UCSI_CONSTAT_BC_CHANGE) ucsi_port_psy_changed(con); - if (UCSI_CONSTAT(con, PWR_READING_READY_V2_1)) { + if (con->ucsi->version >= UCSI_VERSION_2_1 && + UCSI_CONSTAT(con, PWR_READING_READY_V2_1)) { curr_scale = UCSI_CONSTAT(con, CURRENT_SCALE_V2_1); volt_scale = UCSI_CONSTAT(con, VOLTAGE_SCALE_V2_1); From 970076537efd847c6f74ba9ed888b9cdbf71206e Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 20 Aug 2025 17:38:16 +0300 Subject: [PATCH 047/132] usb: core: Use le16_to_cpu() to read __le16 value in usb_parse_endpoint() wMaxPacketSize field in struct usb_endpoint_descriptor is an __le16, use le16_to_cpu() to read it. Signed-off-by: Sakari Ailus Acked-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250820143824.551777-2-sakari.ailus@linux.intel.com --- drivers/usb/core/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 42468bbeffd2..cda595b4014f 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -507,7 +507,7 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, } /* Parse a possible eUSB2 periodic endpoint companion descriptor */ - if (bcdUSB == 0x0220 && d->wMaxPacketSize == 0 && + if (bcdUSB == 0x0220 && !le16_to_cpu(d->wMaxPacketSize) && (usb_endpoint_xfer_isoc(d) || usb_endpoint_xfer_int(d))) usb_parse_eusb2_isoc_endpoint_companion(ddev, cfgno, inum, asnum, endpoint, buffer, size); From 030ab58b075c04b5286d2787860373dcc30020c6 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 20 Aug 2025 17:38:17 +0300 Subject: [PATCH 048/132] usb: core: Parse eUSB2 companion descriptors for high speed devices only Check that a device is a high-speed one before proceeding to parse the eUSB2 isochronous endpoint companion descriptors. Signed-off-by: Sakari Ailus Acked-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250820143824.551777-3-sakari.ailus@linux.intel.com --- drivers/usb/core/config.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index cda595b4014f..29fcbd6de482 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -507,7 +507,8 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, } /* Parse a possible eUSB2 periodic endpoint companion descriptor */ - if (bcdUSB == 0x0220 && !le16_to_cpu(d->wMaxPacketSize) && + if (udev->speed == USB_SPEED_HIGH && bcdUSB == 0x0220 && + !le16_to_cpu(d->wMaxPacketSize) && (usb_endpoint_xfer_isoc(d) || usb_endpoint_xfer_int(d))) usb_parse_eusb2_isoc_endpoint_companion(ddev, cfgno, inum, asnum, endpoint, buffer, size); From 53d76c6866a0362320b837dd292db4befcac31ba Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 20 Aug 2025 17:38:18 +0300 Subject: [PATCH 049/132] usb: core: eUSB2 companion descriptor is for isoc IN endpoints only The eUSB2 isochronous double bandwidth endpoint descriptor is allowed for the isochronous IN endpoints, according to the ECN. Do not parse these descriptors if they are found in interrupt or OUT endpoints. Signed-off-by: Sakari Ailus Acked-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250820143824.551777-4-sakari.ailus@linux.intel.com --- drivers/usb/core/config.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 29fcbd6de482..baf5bc844b6f 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -508,8 +508,7 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, /* Parse a possible eUSB2 periodic endpoint companion descriptor */ if (udev->speed == USB_SPEED_HIGH && bcdUSB == 0x0220 && - !le16_to_cpu(d->wMaxPacketSize) && - (usb_endpoint_xfer_isoc(d) || usb_endpoint_xfer_int(d))) + !le16_to_cpu(d->wMaxPacketSize) && usb_endpoint_is_isoc_in(d)) usb_parse_eusb2_isoc_endpoint_companion(ddev, cfgno, inum, asnum, endpoint, buffer, size); From 20f988320d2718ef28b1f0635acc88c12a216d29 Mon Sep 17 00:00:00 2001 From: "Rai, Amardeep" Date: Wed, 20 Aug 2025 17:38:19 +0300 Subject: [PATCH 050/132] usb: core: Add a function to get USB version independent periodic payload Add usb_endpoint_max_periodic_payload() to obtain maximum payload bytes in a service interval for isochronous and interrupt endpoints in a USB version independent way. Signed-off-by: Rai, Amardeep Signed-off-by: Mathias Nyman Co-developed-by: Sakari Ailus Signed-off-by: Sakari Ailus Reviewed-by: Hans de Goede Acked-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250820143824.551777-5-sakari.ailus@linux.intel.com --- drivers/usb/core/usb.c | 29 +++++++++++++++++++++++++++++ include/linux/usb.h | 3 +++ 2 files changed, 32 insertions(+) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index fca7735fc660..ca9ff6ad8e73 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1110,6 +1110,35 @@ void usb_free_noncoherent(struct usb_device *dev, size_t size, } EXPORT_SYMBOL_GPL(usb_free_noncoherent); +/** + * usb_endpoint_max_periodic_payload - Get maximum payload bytes per service + * interval + * @udev: The USB device + * @ep: The endpoint + * + * Returns: the maximum number of bytes isochronous or interrupt endpoint @ep + * can transfer during a service interval, or 0 for other endpoints. + */ +u32 usb_endpoint_max_periodic_payload(struct usb_device *udev, + const struct usb_host_endpoint *ep) +{ + if (!usb_endpoint_xfer_isoc(&ep->desc) && + !usb_endpoint_xfer_int(&ep->desc)) + return 0; + + switch (udev->speed) { + case USB_SPEED_SUPER_PLUS: + if (USB_SS_SSP_ISOC_COMP(ep->ss_ep_comp.bmAttributes)) + return le32_to_cpu(ep->ssp_isoc_ep_comp.dwBytesPerInterval); + fallthrough; + case USB_SPEED_SUPER: + return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval); + default: + return usb_endpoint_maxp(&ep->desc) * usb_endpoint_maxp_mult(&ep->desc); + } +} +EXPORT_SYMBOL_GPL(usb_endpoint_max_periodic_payload); + /* * Notifications of device and interface registration */ diff --git a/include/linux/usb.h b/include/linux/usb.h index 9d662c6abb4d..e9cf2786d8bd 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -2039,6 +2039,9 @@ static inline u16 usb_maxpacket(struct usb_device *udev, int pipe) return usb_endpoint_maxp(&ep->desc); } +u32 usb_endpoint_max_periodic_payload(struct usb_device *udev, + const struct usb_host_endpoint *ep); + /* translate USB error codes to codes user space understands */ static inline int usb_translate_errors(int error_code) { From 24b8762e05ed8203dc0b76b62c802aadd487e9aa Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 20 Aug 2025 17:38:20 +0300 Subject: [PATCH 051/132] usb: xhci: Use usb_endpoint_max_periodic_payload() Use the newly added usb_endpoint_max_periodic_payload() to obtain the maximum number of bytes to transfer during a service interval for isochronous and interrupt endpoints. This will replace the xhci-specific xhci_get_max_esit_payload() which is removed as redundant. Signed-off-by: Sakari Ailus Reviewed-by: Mathias Nyman Acked-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250820143824.551777-6-sakari.ailus@linux.intel.com --- drivers/usb/host/xhci-mem.c | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 07289333a1e8..d2c04811191f 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1378,36 +1378,6 @@ static u32 xhci_get_endpoint_type(struct usb_host_endpoint *ep) return 0; } -/* Return the maximum endpoint service interval time (ESIT) payload. - * Basically, this is the maxpacket size, multiplied by the burst size - * and mult size. - */ -static u32 xhci_get_max_esit_payload(struct usb_device *udev, - struct usb_host_endpoint *ep) -{ - int max_burst; - int max_packet; - - /* Only applies for interrupt or isochronous endpoints */ - if (usb_endpoint_xfer_control(&ep->desc) || - usb_endpoint_xfer_bulk(&ep->desc)) - return 0; - - /* SuperSpeedPlus Isoc ep sending over 48k per esit */ - if ((udev->speed >= USB_SPEED_SUPER_PLUS) && - USB_SS_SSP_ISOC_COMP(ep->ss_ep_comp.bmAttributes)) - return le32_to_cpu(ep->ssp_isoc_ep_comp.dwBytesPerInterval); - - /* SuperSpeed or SuperSpeedPlus Isoc ep with less than 48k per esit */ - if (udev->speed >= USB_SPEED_SUPER) - return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval); - - max_packet = usb_endpoint_maxp(&ep->desc); - max_burst = usb_endpoint_maxp_mult(&ep->desc); - /* A 0 in max burst means 1 transfer per ESIT */ - return max_packet * max_burst; -} - /* Set up an endpoint with one ring segment. Do not allocate stream rings. * Drivers will have to call usb_alloc_streams() to do that. */ @@ -1445,7 +1415,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, * have no clue on scatter gather list entry size. For Isoc and Int, * set it to max available. See xHCI 1.1 spec 4.14.1.1 for details. */ - max_esit_payload = xhci_get_max_esit_payload(udev, ep); + max_esit_payload = usb_endpoint_max_periodic_payload(udev, ep); interval = xhci_get_endpoint_interval(udev, ep); /* Periodic endpoint bInterval limit quirk */ From d6725169a9bbcb5bd1dd14b2891b874614c59f52 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 20 Aug 2025 17:38:21 +0300 Subject: [PATCH 052/132] usb: core: Introduce usb_endpoint_is_hs_isoc_double() Introduce usb_endpoint_is_hs_isoc_double() tell whether an endpoint conforms to USB 2.0 Isochronous Double IN Bandwidth ECN. Signed-off-by: Sakari Ailus Acked-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250820143824.551777-7-sakari.ailus@linux.intel.com --- drivers/usb/core/usb.c | 19 +++++++++++++++++++ include/linux/usb.h | 3 +++ 2 files changed, 22 insertions(+) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index ca9ff6ad8e73..939dc4aafb89 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1139,6 +1139,25 @@ u32 usb_endpoint_max_periodic_payload(struct usb_device *udev, } EXPORT_SYMBOL_GPL(usb_endpoint_max_periodic_payload); +/** + * usb_endpoint_is_hs_isoc_double - Tell whether an endpoint uses USB 2 + * Isochronous Double IN Bandwidth + * @udev: The USB device + * @ep: The endpoint + * + * Returns: true if an endpoint @ep conforms to USB 2 Isochronous Double IN + * Bandwidth ECN, false otherwise. + */ +bool usb_endpoint_is_hs_isoc_double(struct usb_device *udev, + const struct usb_host_endpoint *ep) +{ + return ep->eusb2_isoc_ep_comp.bDescriptorType && + le16_to_cpu(udev->descriptor.bcdUSB) == 0x220 && + usb_endpoint_is_isoc_in(&ep->desc) && + !le16_to_cpu(ep->desc.wMaxPacketSize); +} +EXPORT_SYMBOL_GPL(usb_endpoint_is_hs_isoc_double); + /* * Notifications of device and interface registration */ diff --git a/include/linux/usb.h b/include/linux/usb.h index e9cf2786d8bd..70ef00c42d22 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -2042,6 +2042,9 @@ static inline u16 usb_maxpacket(struct usb_device *udev, int pipe) u32 usb_endpoint_max_periodic_payload(struct usb_device *udev, const struct usb_host_endpoint *ep); +bool usb_endpoint_is_hs_isoc_double(struct usb_device *udev, + const struct usb_host_endpoint *ep); + /* translate USB error codes to codes user space understands */ static inline int usb_translate_errors(int error_code) { From 0c670dc882d31f0423f18899c0e594547b55b76d Mon Sep 17 00:00:00 2001 From: "Rai, Amardeep" Date: Wed, 20 Aug 2025 17:38:22 +0300 Subject: [PATCH 053/132] usb: xhci: Add host support for eUSB2 double isochronous bandwidth devices Detect eUSB2 double isoc bw capable hosts and devices, and set the proper xhci endpoint context values such as 'Mult', 'Max Burst Size', and 'Max ESIT Payload' to enable the double isochronous bandwidth endpoints. Intel xHC uses the endpoint context 'Mult' field for eUSB2 isoc endpoints even if hosts supporting Large ESIT Payload Capability should normally ignore the mult field. Signed-off-by: Rai, Amardeep Co-developed-by: Kannappan R Signed-off-by: Kannappan R Reviewed-by: Sakari Ailus Co-developed-by: Mathias Nyman Signed-off-by: Mathias Nyman Co-developed-by: Sakari Ailus Signed-off-by: Sakari Ailus Acked-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250820143824.551777-8-sakari.ailus@linux.intel.com --- drivers/usb/host/xhci-caps.h | 2 ++ drivers/usb/host/xhci-mem.c | 53 +++++++++++++++++++++++++++--------- drivers/usb/host/xhci-ring.c | 6 ++-- drivers/usb/host/xhci.c | 16 ++++++++++- drivers/usb/host/xhci.h | 2 ++ 5 files changed, 62 insertions(+), 17 deletions(-) diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h index 4b8ff4815644..89bc83e4f1eb 100644 --- a/drivers/usb/host/xhci-caps.h +++ b/drivers/usb/host/xhci-caps.h @@ -89,3 +89,5 @@ #define HCC2_GSC(p) ((p) & (1 << 8)) /* true: HC support Virtualization Based Trusted I/O Capability */ #define HCC2_VTC(p) ((p) & (1 << 9)) +/* true: HC support Double BW on a eUSB2 HS ISOC EP */ +#define HCC2_EUSB2_DIC(p) ((p) & (1 << 11)) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index d2c04811191f..78ddcc6761ac 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1330,18 +1330,33 @@ static unsigned int xhci_get_endpoint_interval(struct usb_device *udev, return interval; } -/* The "Mult" field in the endpoint context is only set for SuperSpeed isoc eps. +/* + * xHCs without LEC use the "Mult" field in the endpoint context for SuperSpeed + * isoc eps, and High speed isoc eps that support bandwidth doubling. Standard * High speed endpoint descriptors can define "the number of additional * transaction opportunities per microframe", but that goes in the Max Burst * endpoint context field. */ -static u32 xhci_get_endpoint_mult(struct usb_device *udev, - struct usb_host_endpoint *ep) +static u32 xhci_get_endpoint_mult(struct xhci_hcd *xhci, + struct usb_device *udev, + struct usb_host_endpoint *ep) { - if (udev->speed < USB_SPEED_SUPER || - !usb_endpoint_xfer_isoc(&ep->desc)) - return 0; - return ep->ss_ep_comp.bmAttributes; + bool lec; + + /* xHCI 1.1 with LEC set does not use mult field, except intel eUSB2 */ + lec = xhci->hci_version > 0x100 && HCC2_LEC(xhci->hcc_params2); + + /* eUSB2 double isoc bw devices are the only USB2 devices using mult */ + if (usb_endpoint_is_hs_isoc_double(udev, ep) && + (!lec || xhci->quirks & XHCI_INTEL_HOST)) + return 1; + + /* SuperSpeed isoc transfers on hosts without LEC uses mult field */ + if (udev->speed >= USB_SPEED_SUPER && + usb_endpoint_xfer_isoc(&ep->desc) && !lec) + return ep->ss_ep_comp.bmAttributes; + + return 0; } static u32 xhci_get_endpoint_max_burst(struct usb_device *udev, @@ -1353,8 +1368,16 @@ static u32 xhci_get_endpoint_max_burst(struct usb_device *udev, if (udev->speed == USB_SPEED_HIGH && (usb_endpoint_xfer_isoc(&ep->desc) || - usb_endpoint_xfer_int(&ep->desc))) + usb_endpoint_xfer_int(&ep->desc))) { + /* + * USB 2 Isochronous Double IN Bandwidth ECN uses fixed burst + * size and max packets bits 12:11 are invalid. + */ + if (usb_endpoint_is_hs_isoc_double(udev, ep)) + return 2; + return usb_endpoint_maxp_mult(&ep->desc) - 1; + } return 0; } @@ -1409,6 +1432,13 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, ring_type = usb_endpoint_type(&ep->desc); + /* Ensure host supports double isoc bandwidth for eUSB2 devices */ + if (usb_endpoint_is_hs_isoc_double(udev, ep) && + !HCC2_EUSB2_DIC(xhci->hcc_params2)) { + dev_dbg(&udev->dev, "Double Isoc Bandwidth not supported by xhci\n"); + return -EINVAL; + } + /* * Get values to fill the endpoint context, mostly from ep descriptor. * The average TRB buffer lengt for bulk endpoints is unclear as we @@ -1432,8 +1462,8 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, } } - mult = xhci_get_endpoint_mult(udev, ep); - max_packet = usb_endpoint_maxp(&ep->desc); + mult = xhci_get_endpoint_mult(xhci, udev, ep); + max_packet = xhci_usb_endpoint_maxp(udev, ep); max_burst = xhci_get_endpoint_max_burst(udev, ep); avg_trb_len = max_esit_payload; @@ -1454,9 +1484,6 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, /* xHCI 1.0 and 1.1 indicates that ctrl ep avg TRB Length should be 8 */ if (usb_endpoint_xfer_control(&ep->desc) && xhci->hci_version >= 0x100) avg_trb_len = 8; - /* xhci 1.1 with LEC support doesn't use mult field, use RsvdZ */ - if ((xhci->hci_version > 0x100) && HCC2_LEC(xhci->hcc_params2)) - mult = 0; /* Set up the endpoint ring */ virt_dev->eps[ep_index].new_ring = diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index ecd757d482c5..ed2fa2c15535 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3545,7 +3545,7 @@ static u32 xhci_td_remainder(struct xhci_hcd *xhci, int transferred, if ((xhci->quirks & XHCI_MTK_HOST) && (xhci->hci_version < 0x100)) trb_buff_len = 0; - maxp = usb_endpoint_maxp(&urb->ep->desc); + maxp = xhci_usb_endpoint_maxp(urb->dev, urb->ep); total_packet_count = DIV_ROUND_UP(td_total_len, maxp); /* Queueing functions don't count the current TRB into transferred */ @@ -3562,7 +3562,7 @@ static int xhci_align_td(struct xhci_hcd *xhci, struct urb *urb, u32 enqd_len, u32 new_buff_len; size_t len; - max_pkt = usb_endpoint_maxp(&urb->ep->desc); + max_pkt = xhci_usb_endpoint_maxp(urb->dev, urb->ep); unalign = (enqd_len + *trb_buff_len) % max_pkt; /* we got lucky, last normal TRB data on segment is packet aligned */ @@ -4133,7 +4133,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, addr = start_addr + urb->iso_frame_desc[i].offset; td_len = urb->iso_frame_desc[i].length; td_remain_len = td_len; - max_pkt = usb_endpoint_maxp(&urb->ep->desc); + max_pkt = xhci_usb_endpoint_maxp(urb->dev, urb->ep); total_pkt_count = DIV_ROUND_UP(td_len, max_pkt); /* A zero-length transfer still involves at least one packet. */ diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 47151ca527bf..e193d4dc414c 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1334,7 +1334,7 @@ static bool xhci_urb_temp_buffer_required(struct usb_hcd *hcd, struct scatterlist *tail_sg; tail_sg = urb->sg; - max_pkt = usb_endpoint_maxp(&urb->ep->desc); + max_pkt = xhci_usb_endpoint_maxp(urb->dev, urb->ep); if (!urb->num_sgs) return ret; @@ -2922,6 +2922,20 @@ int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, int } EXPORT_SYMBOL_GPL(xhci_stop_endpoint_sync); +/* + * xhci_usb_endpoint_maxp - get endpoint max packet size + * @host_ep: USB host endpoint to be checked + * + * Returns max packet from the correct descriptor + */ +int xhci_usb_endpoint_maxp(struct usb_device *udev, + struct usb_host_endpoint *host_ep) +{ + if (usb_endpoint_is_hs_isoc_double(udev, host_ep)) + return le16_to_cpu(host_ep->eusb2_isoc_ep_comp.wMaxPacketSize); + return usb_endpoint_maxp(&host_ep->desc); +} + /* Issue a configure endpoint command or evaluate context command * and wait for it to finish. */ diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index a20f4e7cd43a..2721f700c022 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1957,6 +1957,8 @@ void xhci_update_erst_dequeue(struct xhci_hcd *xhci, struct xhci_interrupter *ir, bool clear_ehb); void xhci_add_interrupter(struct xhci_hcd *xhci, unsigned int intr_num); +int xhci_usb_endpoint_maxp(struct usb_device *udev, + struct usb_host_endpoint *host_ep); /* xHCI roothub code */ void xhci_set_link_state(struct xhci_hcd *xhci, struct xhci_port *port, From 0666a012d25051d6d2b4e6b2c893e2f074b87d91 Mon Sep 17 00:00:00 2001 From: "Rai, Amardeep" Date: Wed, 20 Aug 2025 17:38:23 +0300 Subject: [PATCH 054/132] usb: core: support eUSB2 double bandwidth large isoc URB frames eUSB2 double isochronous in bandwidth devices support up to 6 transactions per microframe, and thus doubles the total bytes possible to receive per microframe. Support larger URB isoc frame sizes for eUSB2 double isoc in endpoints. Also usb_endpoint_maxp() returns a natural number so there's no need to assume it could be < 0. Signed-off-by: Rai, Amardeep Reviewed-by: Sakari Ailus Signed-off-by: Mathias Nyman Co-developed-by: Sakari Ailus Signed-off-by: Sakari Ailus Acked-by: Alan Stern Acked-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250820143824.551777-9-sakari.ailus@linux.intel.com --- drivers/usb/core/urb.c | 14 ++++++++++---- drivers/usb/core/usb.c | 2 ++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index 7a76d5a62db1..ff8df16cca35 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -372,6 +372,7 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags) struct usb_host_endpoint *ep; int is_out; unsigned int allowed; + bool is_eusb2_isoch_double; if (!urb || !urb->complete) return -EINVAL; @@ -434,7 +435,8 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags) return -ENODEV; max = usb_endpoint_maxp(&ep->desc); - if (max <= 0) { + is_eusb2_isoch_double = usb_endpoint_is_hs_isoc_double(dev, ep); + if (!max && !is_eusb2_isoch_double) { dev_dbg(&dev->dev, "bogus endpoint ep%d%s in %s (bad maxpacket %d)\n", usb_endpoint_num(&ep->desc), is_out ? "out" : "in", @@ -467,9 +469,13 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags) max = le32_to_cpu(isoc_ep_comp->dwBytesPerInterval); } - /* "high bandwidth" mode, 1-3 packets/uframe? */ - if (dev->speed == USB_SPEED_HIGH) - max *= usb_endpoint_maxp_mult(&ep->desc); + /* High speed, 1-3 packets/uframe, max 6 for eUSB2 double bw */ + if (dev->speed == USB_SPEED_HIGH) { + if (is_eusb2_isoch_double) + max = le32_to_cpu(ep->eusb2_isoc_ep_comp.dwBytesPerInterval); + else + max *= usb_endpoint_maxp_mult(&ep->desc); + } if (urb->number_of_packets <= 0) return -EINVAL; diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 939dc4aafb89..b88b6271cb30 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1134,6 +1134,8 @@ u32 usb_endpoint_max_periodic_payload(struct usb_device *udev, case USB_SPEED_SUPER: return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval); default: + if (usb_endpoint_is_hs_isoc_double(udev, ep)) + return le32_to_cpu(ep->eusb2_isoc_ep_comp.dwBytesPerInterval); return usb_endpoint_maxp(&ep->desc) * usb_endpoint_maxp_mult(&ep->desc); } } From 0aa0b0326cc5642e97af968d21cbd8836368b707 Mon Sep 17 00:00:00 2001 From: Tao Q Tao Date: Wed, 20 Aug 2025 17:38:24 +0300 Subject: [PATCH 055/132] media: uvcvideo: eUSB2 double isochronous bandwidth support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use usb_endpoint_max_esit_payload() from the USB framework to find the maximum bytes per interval for the endpoint. Consequently this adds eUSB2 isochronous mode and SuperSpeedPlus Isochronous Endpoint Companion support where larger payloads within a service interval are possible. Co-developed-by: Amardeep Rai Signed-off-by: Amardeep Rai Signed-off-by: Tao Q Tao Signed-off-by: Mathias Nyman Co-developed-by: Sakari Ailus Signed-off-by: Sakari Ailus Reviewed-by: Ilpo Järvinen Reviewed-by: Laurent Pinchart Reviewed-by: Ricardo Ribalda Acked-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250820143824.551777-10-sakari.ailus@linux.intel.com --- drivers/media/usb/uvc/uvc_driver.c | 4 ++-- drivers/media/usb/uvc/uvc_video.c | 24 +++--------------------- drivers/media/usb/uvc/uvcvideo.h | 4 +--- 3 files changed, 6 insertions(+), 26 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 775bede0d93d..d06ca79ae2d9 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -539,7 +539,7 @@ static int uvc_parse_streaming(struct uvc_device *dev, unsigned int nformats = 0, nframes = 0, nintervals = 0; unsigned int size, i, n, p; u32 *interval; - u16 psize; + u32 psize; int ret = -EINVAL; if (intf->cur_altsetting->desc.bInterfaceSubClass @@ -775,7 +775,7 @@ static int uvc_parse_streaming(struct uvc_device *dev, streaming->header.bEndpointAddress); if (ep == NULL) continue; - psize = uvc_endpoint_max_bpi(dev->udev, ep); + psize = usb_endpoint_max_periodic_payload(dev->udev, ep); if (psize > streaming->maxpsize) streaming->maxpsize = psize; } diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index a5013a7fbca4..1a0cc937de9b 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -1869,24 +1869,6 @@ static void uvc_video_stop_transfer(struct uvc_streaming *stream, uvc_free_urb_buffers(stream); } -/* - * Compute the maximum number of bytes per interval for an endpoint. - */ -u16 uvc_endpoint_max_bpi(struct usb_device *dev, struct usb_host_endpoint *ep) -{ - u16 psize; - - switch (dev->speed) { - case USB_SPEED_SUPER: - case USB_SPEED_SUPER_PLUS: - return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval); - default: - psize = usb_endpoint_maxp(&ep->desc); - psize *= usb_endpoint_maxp_mult(&ep->desc); - return psize; - } -} - /* * Initialize isochronous URBs and allocate transfer buffers. The packet size * is given by the endpoint. @@ -1897,10 +1879,10 @@ static int uvc_init_video_isoc(struct uvc_streaming *stream, struct urb *urb; struct uvc_urb *uvc_urb; unsigned int npackets, i; - u16 psize; + u32 psize; u32 size; - psize = uvc_endpoint_max_bpi(stream->dev->udev, ep); + psize = usb_endpoint_max_periodic_payload(stream->dev->udev, ep); size = stream->ctrl.dwMaxVideoFrameSize; npackets = uvc_alloc_urb_buffers(stream, size, psize, gfp_flags); @@ -2043,7 +2025,7 @@ static int uvc_video_start_transfer(struct uvc_streaming *stream, continue; /* Check if the bandwidth is high enough. */ - psize = uvc_endpoint_max_bpi(stream->dev->udev, ep); + psize = usb_endpoint_max_periodic_payload(stream->dev->udev, ep); if (psize >= bandwidth && psize < best_psize) { altsetting = alts->desc.bAlternateSetting; best_psize = psize; diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 757254fc4fe9..ac19ca721f29 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -456,7 +456,7 @@ struct uvc_streaming { struct usb_interface *intf; int intfnum; - u16 maxpsize; + u32 maxpsize; struct uvc_streaming_header header; enum v4l2_buf_type type; @@ -798,8 +798,6 @@ void uvc_ctrl_cleanup_fh(struct uvc_fh *handle); /* Utility functions */ struct usb_host_endpoint *uvc_find_endpoint(struct usb_host_interface *alts, u8 epaddr); -u16 uvc_endpoint_max_bpi(struct usb_device *dev, struct usb_host_endpoint *ep); - /* Quirks support */ void uvc_video_decode_isight(struct uvc_urb *uvc_urb, struct uvc_buffer *buf, From 6f9871b3e8c31953978db552c730a7e017e51d19 Mon Sep 17 00:00:00 2001 From: Liao Yuanhong Date: Tue, 2 Sep 2025 21:26:08 +0800 Subject: [PATCH 056/132] usb: gadget: tegra-xudc: Remove redundant ternary operators For ternary operators in the form of "a ? true : false", if 'a' itself returns a boolean result, the ternary operator can be omitted. Remove redundant ternary operators to clean up the code. Signed-off-by: Liao Yuanhong Link: https://lore.kernel.org/r/20250902132613.84876-1-liaoyuanhong@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/tegra-xudc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 1d3085cc9d22..0c38fc37b6e6 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -812,8 +812,7 @@ static void tegra_xudc_update_data_role(struct tegra_xudc *xudc, return; } - xudc->device_mode = (usbphy->last_event == USB_EVENT_VBUS) ? true : - false; + xudc->device_mode = usbphy->last_event == USB_EVENT_VBUS; phy_index = tegra_xudc_get_phy_index(xudc, usbphy); dev_dbg(xudc->dev, "%s(): current phy index is %d\n", __func__, From 00f2bf97544cdc7e5b166d19f896f5eaa2bb02ae Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 29 Aug 2025 11:15:45 +0300 Subject: [PATCH 057/132] MAINTAINERS: Update Michael Jamet's maintainer entries Michael Jamet is about to continue his professional journey outside of Intel and is stepping down as maintainer of Thunderbolt/USB4 subsystem, and the networking driver. Move him from MAINTAINERS to CREDITS. Thank you, Michael! Cc: Michael Jamet Signed-off-by: Mika Westerberg --- CREDITS | 5 +++++ MAINTAINERS | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CREDITS b/CREDITS index a357f9cbb05d..31ef5c80f0ce 100644 --- a/CREDITS +++ b/CREDITS @@ -1890,6 +1890,11 @@ S: Reading S: RG6 2NU S: United Kingdom +N: Michael Jamet +E: michael.jamet@intel.com +D: Thunderbolt/USB4 driver maintainer +D: Thunderbolt/USB4 networking driver maintainer + N: Dave Jeffery E: dhjeffery@gmail.com D: SCSI hacks and IBM ServeRAID RAID driver maintenance diff --git a/MAINTAINERS b/MAINTAINERS index fe168477caa4..bf0ef08ac6b9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25091,7 +25091,6 @@ F: drivers/thunderbolt/dma_test.c THUNDERBOLT DRIVER M: Andreas Noever -M: Michael Jamet M: Mika Westerberg M: Yehezkel Bernat L: linux-usb@vger.kernel.org @@ -25102,7 +25101,6 @@ F: drivers/thunderbolt/ F: include/linux/thunderbolt.h THUNDERBOLT NETWORK DRIVER -M: Michael Jamet M: Mika Westerberg M: Yehezkel Bernat L: netdev@vger.kernel.org From a5d2edb2c9fa48382aec1673c709c2919899527c Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Wed, 10 Sep 2025 18:50:42 +0800 Subject: [PATCH 058/132] usb: typec: tcpci: add wakeup support Add wakeup support for tcpci. If the user enables the wakeup file, call enable_irq_wake() during system suspend and disable_irq_wake() during system resume. Since this driver supports shared interrupts, mask the chip interrupt by default when wakeup is disabled to avoid affecting other IRQ users. Reviewed-by: Frank Li Reviewed-by: Badhri Jagan Sridharan Signed-off-by: Xu Yang Link: https://lore.kernel.org/r/20250910105042.695146-1-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index a56e31b20c21..2a951c585e92 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -949,6 +949,8 @@ static int tcpci_probe(struct i2c_client *client) if (err < 0) goto unregister_port; + device_set_wakeup_capable(chip->tcpci->dev, true); + return 0; unregister_port: @@ -969,6 +971,36 @@ static void tcpci_remove(struct i2c_client *client) tcpci_unregister_port(chip->tcpci); } +static int tcpci_suspend(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct tcpci_chip *chip = i2c_get_clientdata(i2c); + int ret; + + if (device_may_wakeup(dev)) + ret = enable_irq_wake(i2c->irq); + else + ret = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, 0); + + return ret; +} + +static int tcpci_resume(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct tcpci_chip *chip = i2c_get_clientdata(i2c); + int ret; + + if (device_may_wakeup(dev)) + ret = disable_irq_wake(i2c->irq); + else + ret = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, chip->tcpci->alert_mask); + + return ret; +} + +DEFINE_SIMPLE_DEV_PM_OPS(tcpci_pm_ops, tcpci_suspend, tcpci_resume); + static const struct i2c_device_id tcpci_id[] = { { "tcpci" }, { } @@ -987,6 +1019,7 @@ MODULE_DEVICE_TABLE(of, tcpci_of_match); static struct i2c_driver tcpci_i2c_driver = { .driver = { .name = "tcpci", + .pm = pm_sleep_ptr(&tcpci_pm_ops), .of_match_table = of_match_ptr(tcpci_of_match), }, .probe = tcpci_probe, From e77ee1d2a8fa0f3179d4ac7b7d35b555da6a8cea Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Wed, 10 Sep 2025 18:07:30 +0200 Subject: [PATCH 059/132] usb: dwc2: Add support for 'maximum-speed' property The DWC2 IP can be properly integrated in a SoC to work at high-speed USB speed but some board issues, EMC constraints or any other reasons can lead to the need to limit this USB speed at board level. The device-tree 'maximum-speed' property already exists for this purpose but is not handled by the DWC2 driver. Fill this lack adding support for 'maximum-speed' property and so allow to limit the USB speed in device-tree (board description). Signed-off-by: Herve Codina Link: https://lore.kernel.org/r/20250910160730.585303-1-herve.codina@bootlin.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/params.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index ea6bd537e337..091bfcfef753 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -1029,11 +1029,33 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) return 0; } +static int dwc2_limit_speed(struct dwc2_hsotg *hsotg) +{ + enum usb_device_speed usb_speed; + + usb_speed = usb_get_maximum_speed(hsotg->dev); + switch (usb_speed) { + case USB_SPEED_LOW: + dev_err(hsotg->dev, "Maximum speed cannot be forced to low-speed\n"); + return -EINVAL; + case USB_SPEED_FULL: + if (hsotg->params.speed == DWC2_SPEED_PARAM_LOW) + break; + hsotg->params.speed = DWC2_SPEED_PARAM_FULL; + break; + default: + break; + } + + return 0; +} + typedef void (*set_params_cb)(struct dwc2_hsotg *data); int dwc2_init_params(struct dwc2_hsotg *hsotg) { set_params_cb set_params; + int ret; dwc2_set_default_params(hsotg); dwc2_get_device_properties(hsotg); @@ -1051,6 +1073,10 @@ int dwc2_init_params(struct dwc2_hsotg *hsotg) } } + ret = dwc2_limit_speed(hsotg); + if (ret) + return ret; + dwc2_check_params(hsotg); return 0; From 7bf1158514e410310aec975e630cec99d4e4092f Mon Sep 17 00:00:00 2001 From: Kuen-Han Tsai Date: Mon, 18 Aug 2025 16:27:19 +0800 Subject: [PATCH 060/132] usb: udc: Add trace event for usb_gadget_set_state While the userspace program can be notified of gadget state changes, timing issue can lead to missed transitions when reading the state value. Introduce a trace event for usb_gadget_set_state to reliably track state transitions. Signed-off-by: Kuen-Han Tsai Link: https://lore.kernel.org/r/20250818082722.2952867-1-khtsai@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 1 + drivers/usb/gadget/udc/trace.h | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index d709e24c1fd4..e28fea614496 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1125,6 +1125,7 @@ void usb_gadget_set_state(struct usb_gadget *gadget, { gadget->state = state; schedule_work(&gadget->work); + trace_usb_gadget_set_state(gadget, 0); } EXPORT_SYMBOL_GPL(usb_gadget_set_state); diff --git a/drivers/usb/gadget/udc/trace.h b/drivers/usb/gadget/udc/trace.h index 4e334298b0e8..fa3e6ddf0a12 100644 --- a/drivers/usb/gadget/udc/trace.h +++ b/drivers/usb/gadget/udc/trace.h @@ -81,6 +81,11 @@ DECLARE_EVENT_CLASS(udc_log_gadget, __entry->ret) ); +DEFINE_EVENT(udc_log_gadget, usb_gadget_set_state, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + DEFINE_EVENT(udc_log_gadget, usb_gadget_frame_number, TP_PROTO(struct usb_gadget *g, int ret), TP_ARGS(g, ret) From 5df186e2ef11dca3fb6f0f332dc09c4ac0bed870 Mon Sep 17 00:00:00 2001 From: Haotien Hsu Date: Mon, 11 Aug 2025 15:45:58 +0800 Subject: [PATCH 061/132] usb: xhci: tegra: Support USB wakeup function for Tegra234 When the system is suspended, USB hot-plugging/unplugging can trigger wake events of the Tegra USB host controller. Enable support for USB wake-up events by parsing device-tree to see if the interrupts for the wake-up events are present and if so configure those interrupts. Note that if wake-up events are not present, still allow the USB host controller to probe as normal. Signed-off-by: Haotien Hsu Link: https://lore.kernel.org/r/20250811074558.1062048-5-haotienh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-tegra.c | 82 ++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 460effa0e11e..5255b1002893 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -155,6 +155,8 @@ #define FW_IOCTL_TYPE_SHIFT 24 #define FW_IOCTL_CFGTBL_READ 17 +#define WAKE_IRQ_START_INDEX 2 + struct tegra_xusb_fw_header { __le32 boot_loadaddr_in_imem; __le32 boot_codedfi_offset; @@ -228,6 +230,7 @@ struct tegra_xusb_soc { unsigned int num_supplies; const struct tegra_xusb_phy_type *phy_types; unsigned int num_types; + unsigned int max_num_wakes; const struct tegra_xusb_context_soc *context; struct { @@ -263,6 +266,7 @@ struct tegra_xusb { int xhci_irq; int mbox_irq; int padctl_irq; + int *wake_irqs; void __iomem *ipfs_base; void __iomem *fpci_base; @@ -313,6 +317,7 @@ struct tegra_xusb { bool suspended; struct tegra_xusb_context context; u8 lp0_utmi_pad_mask; + int num_wakes; }; static struct hc_driver __read_mostly tegra_xhci_hc_driver; @@ -1537,6 +1542,58 @@ static void tegra_xusb_deinit_usb_phy(struct tegra_xusb *tegra) otg_set_host(tegra->usbphy[i]->otg, NULL); } +static int tegra_xusb_setup_wakeup(struct platform_device *pdev, struct tegra_xusb *tegra) +{ + unsigned int i; + + if (tegra->soc->max_num_wakes == 0) + return 0; + + tegra->wake_irqs = devm_kcalloc(tegra->dev, + tegra->soc->max_num_wakes, + sizeof(*tegra->wake_irqs), GFP_KERNEL); + if (!tegra->wake_irqs) + return -ENOMEM; + + /* + * USB wake events are independent of each other, so it is not necessary for a platform + * to utilize all wake-up events supported for a given device. The USB host can operate + * even if wake-up events are not defined or fail to be configured. Therefore, we only + * return critical errors, such as -ENOMEM. + */ + for (i = 0; i < tegra->soc->max_num_wakes; i++) { + struct irq_data *data; + + tegra->wake_irqs[i] = platform_get_irq(pdev, i + WAKE_IRQ_START_INDEX); + if (tegra->wake_irqs[i] < 0) + break; + + data = irq_get_irq_data(tegra->wake_irqs[i]); + if (!data) { + dev_warn(tegra->dev, "get wake event %d irq data fail\n", i); + irq_dispose_mapping(tegra->wake_irqs[i]); + break; + } + + irq_set_irq_type(tegra->wake_irqs[i], irqd_get_trigger_type(data)); + } + + tegra->num_wakes = i; + dev_dbg(tegra->dev, "setup %d wake events\n", tegra->num_wakes); + + return 0; +} + +static void tegra_xusb_dispose_wake(struct tegra_xusb *tegra) +{ + unsigned int i; + + for (i = 0; i < tegra->num_wakes; i++) + irq_dispose_mapping(tegra->wake_irqs[i]); + + tegra->num_wakes = 0; +} + static int tegra_xusb_probe(struct platform_device *pdev) { struct tegra_xusb *tegra; @@ -1587,9 +1644,15 @@ static int tegra_xusb_probe(struct platform_device *pdev) if (tegra->mbox_irq < 0) return tegra->mbox_irq; + err = tegra_xusb_setup_wakeup(pdev, tegra); + if (err) + return err; + tegra->padctl = tegra_xusb_padctl_get(&pdev->dev); - if (IS_ERR(tegra->padctl)) - return PTR_ERR(tegra->padctl); + if (IS_ERR(tegra->padctl)) { + err = PTR_ERR(tegra->padctl); + goto dispose_wake; + } np = of_parse_phandle(pdev->dev.of_node, "nvidia,xusb-padctl", 0); if (!np) { @@ -1913,6 +1976,8 @@ static int tegra_xusb_probe(struct platform_device *pdev) put_padctl: of_node_put(np); tegra_xusb_padctl_put(tegra->padctl); +dispose_wake: + tegra_xusb_dispose_wake(tegra); return err; } @@ -1945,6 +2010,8 @@ static void tegra_xusb_remove(struct platform_device *pdev) if (tegra->padctl_irq) pm_runtime_disable(&pdev->dev); + tegra_xusb_dispose_wake(tegra); + pm_runtime_put(&pdev->dev); tegra_xusb_disable(tegra); @@ -2355,8 +2422,13 @@ static __maybe_unused int tegra_xusb_suspend(struct device *dev) pm_runtime_disable(dev); if (device_may_wakeup(dev)) { + unsigned int i; + if (enable_irq_wake(tegra->padctl_irq)) dev_err(dev, "failed to enable padctl wakes\n"); + + for (i = 0; i < tegra->num_wakes; i++) + enable_irq_wake(tegra->wake_irqs[i]); } } @@ -2384,8 +2456,13 @@ static __maybe_unused int tegra_xusb_resume(struct device *dev) } if (device_may_wakeup(dev)) { + unsigned int i; + if (disable_irq_wake(tegra->padctl_irq)) dev_err(dev, "failed to disable padctl wakes\n"); + + for (i = 0; i < tegra->num_wakes; i++) + disable_irq_wake(tegra->wake_irqs[i]); } tegra->suspended = false; mutex_unlock(&tegra->lock); @@ -2636,6 +2713,7 @@ static const struct tegra_xusb_soc tegra234_soc = { .num_supplies = ARRAY_SIZE(tegra194_supply_names), .phy_types = tegra194_phy_types, .num_types = ARRAY_SIZE(tegra194_phy_types), + .max_num_wakes = 7, .context = &tegra186_xusb_context, .ports = { .usb3 = { .offset = 0, .count = 4, }, From 643df901f7ead35a71552a61927ad665a15aa87f Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Sun, 7 Sep 2025 23:44:11 +0530 Subject: [PATCH 062/132] usb: dwc3: core: Introduce glue callbacks for flattened implementations In certain situations like role switching, the glue layers need to be informed of these events, so that they can take any necessary action. But in non-flattened implementations, the glue drivers have no data on when the core driver probe was successful post invoking of_platform_ populate. Now that the core driver supports flattened implementations as well, introduce vendor callbacks that can be passed on from glue to core before invoking dwc3_core_probe. Introduce callbacks to notify glue layer of role_switch and run_stop changes. These can be used by flattened implementation of Qualcomm glue layer to generate connect/disconnect events in controller during cable connect and run stop modifications by udc in device mode. Acked-by: Thinh Nguyen Signed-off-by: Krishna Kurapati Link: https://lore.kernel.org/r/20250907181412.2174616-2-krishna.kurapati@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 1 + drivers/usb/dwc3/core.h | 26 ++++++++++++++++++++++++++ drivers/usb/dwc3/drd.c | 1 + drivers/usb/dwc3/gadget.c | 1 + 4 files changed, 29 insertions(+) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 370fc524a468..ae140c356295 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -2352,6 +2352,7 @@ static int dwc3_probe(struct platform_device *pdev) return -ENOMEM; dwc->dev = &pdev->dev; + dwc->glue_ops = NULL; probe_data.dwc = dwc; probe_data.res = res; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index d5b985fa12f4..a5fc92c4ffa3 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -992,6 +992,17 @@ struct dwc3_scratchpad_array { __le64 dma_adr[DWC3_MAX_HIBER_SCRATCHBUFS]; }; +/** + * struct dwc3_glue_ops - The ops indicate the notifications that + * need to be passed on to glue layer + * @pre_set_role: Notify glue of role switch notifications + * @pre_run_stop: Notify run stop enable/disable information to glue + */ +struct dwc3_glue_ops { + void (*pre_set_role)(struct dwc3 *dwc, enum usb_role role); + void (*pre_run_stop)(struct dwc3 *dwc, bool is_on); +}; + /** * struct dwc3 - representation of our controller * @drd_work: workqueue used for role swapping @@ -1012,6 +1023,7 @@ struct dwc3_scratchpad_array { * @eps: endpoint array * @gadget: device side representation of the peripheral controller * @gadget_driver: pointer to the gadget driver + * @glue_ops: Vendor callbacks for flattened device implementations. * @bus_clk: clock for accessing the registers * @ref_clk: reference clock * @susp_clk: clock used when the SS phy is in low power (S3) state @@ -1197,6 +1209,8 @@ struct dwc3 { struct usb_gadget *gadget; struct usb_gadget_driver *gadget_driver; + const struct dwc3_glue_ops *glue_ops; + struct clk *bus_clk; struct clk *ref_clk; struct clk *susp_clk; @@ -1614,6 +1628,18 @@ void dwc3_event_buffers_cleanup(struct dwc3 *dwc); int dwc3_core_soft_reset(struct dwc3 *dwc); void dwc3_enable_susphy(struct dwc3 *dwc, bool enable); +static inline void dwc3_pre_set_role(struct dwc3 *dwc, enum usb_role role) +{ + if (dwc->glue_ops && dwc->glue_ops->pre_set_role) + dwc->glue_ops->pre_set_role(dwc, role); +} + +static inline void dwc3_pre_run_stop(struct dwc3 *dwc, bool is_on) +{ + if (dwc->glue_ops && dwc->glue_ops->pre_run_stop) + dwc->glue_ops->pre_run_stop(dwc, is_on); +} + #if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) int dwc3_host_init(struct dwc3 *dwc); void dwc3_host_exit(struct dwc3 *dwc); diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index 7977860932b1..4c91240eb429 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -464,6 +464,7 @@ static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, break; } + dwc3_pre_set_role(dwc, role); dwc3_set_mode(dwc, mode); return 0; } diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 554f997eb8c4..6f18b4840a25 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2662,6 +2662,7 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on) dwc->pullups_connected = false; } + dwc3_pre_run_stop(dwc, is_on); dwc3_gadget_dctl_write_safe(dwc, reg); do { From 21188e8d6d75900f35c2acc8baa5a6ed4aec5af7 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Sun, 7 Sep 2025 23:44:12 +0530 Subject: [PATCH 063/132] usb: dwc3: qcom: Implement glue callbacks to facilitate runtime suspend On Qualcomm DWC3 dual-role controllers, the conndone/disconnect events in device mode are generated by controller when software writes to QSCRATCH registers in Qualcomm Glue layer rather than the vbus line being routed to dwc3 core IP for it to recognize and generate these events. UTMI_OTG_VBUS_VALID bit of QSCRATCH_HS_PHY_CTRL register needs to be set to generate a connection done event and to be cleared for the controller to generate a disconnect event during cable removal. When the disconnect is not generated upon cable removal, the "connected" flag of dwc3 is left marked as "true" and it blocks suspend routines and for that to happen upon cable removal, the cable disconnect notification coming in via set_role call need to be provided to the Qualcomm glue layer as well. Currently, the way DWC3 core and Qualcomm legacy glue driver are designed, there is no mechanism through which the DWC3 core can notify the Qualcomm glue layer of any role changes which it receives via role switch. To register these glue callbacks at probe time, for enabling core to notify glue layer, the legacy Qualcomm driver has no way to find out when the child driver probe was successful since it does not check for the same during of_platform_populate. Hence implement the following glue callbacks for flattened Qualcomm glue driver: 1. set_role: To pass role switching information from drd layer to glue. This information is needed to identify NONE/DEVICE mode switch and modify QSCRATCH to generate connect-done event on device mode entry and disconnect event on cable removal in device mode. 2. run_stop: When booting up in device mode, if autouspend is enabled and userspace doesn't write UDC on boot, controller enters autosuspend. After this, if the userspace writes to UDC in the future, run_stop notifier is required to enable UTMI_OTG_VBUS_VALID of QSCRATCH so that connect done event is generated after run_stop(1) is done to finish enumeration. Acked-by: Thinh Nguyen Signed-off-by: Krishna Kurapati Link: https://lore.kernel.org/r/20250907181412.2174616-3-krishna.kurapati@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 78 +++++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 8a9018ca650c..ded2ca86670c 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -83,6 +83,8 @@ struct dwc3_qcom { bool pm_suspended; struct icc_path *icc_path_ddr; struct icc_path *icc_path_apps; + + enum usb_role current_role; }; #define to_dwc3_qcom(d) container_of((d), struct dwc3_qcom, dwc) @@ -111,10 +113,6 @@ static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val) readl(base + offset); } -/* - * TODO: Make the in-core role switching code invoke dwc3_qcom_vbus_override_enable(), - * validate that the in-core extcon support is functional - */ static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable) { if (enable) { @@ -560,6 +558,55 @@ static int dwc3_qcom_setup_irq(struct dwc3_qcom *qcom, struct platform_device *p return 0; } +static void dwc3_qcom_set_role_notifier(struct dwc3 *dwc, enum usb_role next_role) +{ + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + + if (qcom->current_role == next_role) + return; + + if (pm_runtime_resume_and_get(qcom->dev)) { + dev_dbg(qcom->dev, "Failed to resume device\n"); + return; + } + + if (qcom->current_role == USB_ROLE_DEVICE) + dwc3_qcom_vbus_override_enable(qcom, false); + else if (qcom->current_role != USB_ROLE_DEVICE) + dwc3_qcom_vbus_override_enable(qcom, true); + + pm_runtime_mark_last_busy(qcom->dev); + pm_runtime_put_sync(qcom->dev); + + /* + * Current role changes via usb_role_switch_set_role callback protected + * internally by mutex lock. + */ + qcom->current_role = next_role; +} + +static void dwc3_qcom_run_stop_notifier(struct dwc3 *dwc, bool is_on) +{ + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + + /* + * When autosuspend is enabled and controller goes to suspend + * after removing UDC from userspace, the next UDC write needs + * setting of QSCRATCH VBUS_VALID to "1" to generate a connect + * done event. + */ + if (!is_on) + return; + + dwc3_qcom_vbus_override_enable(qcom, true); + pm_runtime_mark_last_busy(qcom->dev); +} + +struct dwc3_glue_ops dwc3_qcom_glue_ops = { + .pre_set_role = dwc3_qcom_set_role_notifier, + .pre_run_stop = dwc3_qcom_run_stop_notifier, +}; + static int dwc3_qcom_probe(struct platform_device *pdev) { struct dwc3_probe_data probe_data = {}; @@ -636,6 +683,23 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (ignore_pipe_clk) dwc3_qcom_select_utmi_clk(qcom); + qcom->mode = usb_get_dr_mode(dev); + + if (qcom->mode == USB_DR_MODE_HOST) { + qcom->current_role = USB_ROLE_HOST; + } else if (qcom->mode == USB_DR_MODE_PERIPHERAL) { + qcom->current_role = USB_ROLE_DEVICE; + dwc3_qcom_vbus_override_enable(qcom, true); + } else { + if ((device_property_read_bool(dev, "usb-role-switch")) && + (usb_get_role_switch_default_mode(dev) == USB_DR_MODE_HOST)) + qcom->current_role = USB_ROLE_HOST; + else + qcom->current_role = USB_ROLE_DEVICE; + } + + qcom->dwc.glue_ops = &dwc3_qcom_glue_ops; + qcom->dwc.dev = dev; probe_data.dwc = &qcom->dwc; probe_data.res = &res; @@ -650,12 +714,6 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (ret) goto remove_core; - qcom->mode = usb_get_dr_mode(dev); - - /* enable vbus override for device mode */ - if (qcom->mode != USB_DR_MODE_HOST) - dwc3_qcom_vbus_override_enable(qcom, true); - wakeup_source = of_property_read_bool(dev->of_node, "wakeup-source"); device_init_wakeup(&pdev->dev, wakeup_source); From 121a0f839dbb397af5fabb701cea3e9983223e50 Mon Sep 17 00:00:00 2001 From: Israel Cepeda Date: Thu, 11 Sep 2025 20:13:41 +0200 Subject: [PATCH 064/132] usb: misc: Add Intel USBIO bridge driver Add a driver for the Intel USBIO USB IO-expander used by the MIPI cameras on various new (Meteor Lake and later) Intel laptops. This is an USB bridge driver which adds auxbus child devices for the GPIO, I2C and SPI functions of the USBIO chip and which exports IO-functions for the drivers for the auxbus child devices to communicate with the USBIO device's firmware. Co-developed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Israel Cepeda Link: https://lore.kernel.org/r/20250911181343.77398-2-hansg@kernel.org Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 8 + drivers/usb/misc/Kconfig | 14 + drivers/usb/misc/Makefile | 1 + drivers/usb/misc/usbio.c | 749 ++++++++++++++++++++++++++++++++++++++ include/linux/usb/usbio.h | 177 +++++++++ 5 files changed, 949 insertions(+) create mode 100644 drivers/usb/misc/usbio.c create mode 100644 include/linux/usb/usbio.h diff --git a/MAINTAINERS b/MAINTAINERS index fed6cd812d79..434ac1593332 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12688,6 +12688,14 @@ S: Maintained F: Documentation/admin-guide/pm/intel_uncore_frequency_scaling.rst F: drivers/platform/x86/intel/uncore-frequency/ +INTEL USBIO USB I/O EXPANDER DRIVERS +M: Israel Cepeda +M: Hans de Goede +R: Sakari Ailus +S: Maintained +F: drivers/usb/misc/usbio.c +F: include/linux/usb/usbio.h + INTEL VENDOR SPECIFIC EXTENDED CAPABILITIES DRIVER M: David E. Box S: Supported diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 26eaae32f738..09ac6f1c985f 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -179,6 +179,20 @@ config USB_LJCA This driver can also be built as a module. If so, the module will be called usb-ljca. +config USB_USBIO + tristate "Intel USBIO Bridge support" + depends on USB && ACPI + select AUXILIARY_BUS + help + This adds support for Intel USBIO drivers. + This enables the USBIO bridge driver module in charge to talk + to the USB device. Additional drivers such as GPIO_USBIO and + I2C_USBIO must be enabled in order to use the device's full + functionality. + + This driver can also be built as a module. If so, the module + will be called usbio. + source "drivers/usb/misc/sisusbvga/Kconfig" config USB_LD diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 0cd5bc8f52fe..494ab0377f35 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_USB_EMI62) += emi62.o obj-$(CONFIG_USB_EZUSB_FX2) += ezusb.o obj-$(CONFIG_APPLE_MFI_FASTCHARGE) += apple-mfi-fastcharge.o obj-$(CONFIG_USB_LJCA) += usb-ljca.o +obj-$(CONFIG_USB_USBIO) += usbio.o obj-$(CONFIG_USB_IDMOUSE) += idmouse.o obj-$(CONFIG_USB_IOWARRIOR) += iowarrior.o obj-$(CONFIG_USB_ISIGHTFW) += isight_firmware.o diff --git a/drivers/usb/misc/usbio.c b/drivers/usb/misc/usbio.c new file mode 100644 index 000000000000..37644dddf157 --- /dev/null +++ b/drivers/usb/misc/usbio.c @@ -0,0 +1,749 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel USBIO Bridge driver + * + * Copyright (c) 2025 Intel Corporation. + * Copyright (c) 2025 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/************************************* + * USBIO Bridge Protocol Definitions * + *************************************/ + +/* USBIO Control Commands */ +#define USBIO_CTRLCMD_PROTVER 0 +#define USBIO_CTRLCMD_FWVER 1 +#define USBIO_CTRLCMD_HS 2 +#define USBIO_CTRLCMD_ENUMGPIO 16 +#define USBIO_CTRLCMD_ENUMI2C 17 + +/* USBIO Packet Flags */ +#define USBIO_PKTFLAG_ACK BIT(0) +#define USBIO_PKTFLAG_RSP BIT(1) +#define USBIO_PKTFLAG_CMP BIT(2) +#define USBIO_PKTFLAG_ERR BIT(3) + +#define USBIO_PKTFLAGS_REQRESP (USBIO_PKTFLAG_CMP | USBIO_PKTFLAG_ACK) + +#define USBIO_CTRLXFER_TIMEOUT 0 +#define USBIO_BULKXFER_TIMEOUT 100 + +struct usbio_protver { + u8 ver; +} __packed; + +struct usbio_fwver { + u8 major; + u8 minor; + __le16 patch; + __le16 build; +} __packed; + +/*********************************** + * USBIO Bridge Device Definitions * + ***********************************/ + +/** + * struct usbio_device - the usb device exposing IOs + * + * @dev: the device in the usb interface + * @udev: the detected usb device + * @intf: the usb interface + * @quirks: quirks + * @ctrl_mutex: protects ctrl_buf + * @ctrl_pipe: the control transfer pipe + * @ctrlbuf_len: the size of the control transfer pipe + * @ctrlbuf: the buffer used for control transfers + * @bulk_mutex: protects tx_buf, rx_buf and split bulk-transfers getting interrupted + * @tx_pipe: the bulk out pipe + * @txbuf_len: the size of the bulk out pipe + * @txbuf: the buffer used for bulk out transfers + * @rx_pipe: the bulk in pipe + * @rxbuf_len: the size of the bulk in pipe + * @rxdat_len: the data length at rx buffer + * @rxbuf: the buffer used for bulk in transfers + * @urb: the urb to read bulk pipe + * @done: completion object as request is done + * @cli_list: device's client list + * @nr_gpio_banks: Number of GPIO banks + * @gpios: GPIO bank descriptors + * @nr_gpio_banks: Number of I2C busses + * @gpios: I2C bank descriptors + */ +struct usbio_device { + struct device *dev; + struct usb_device *udev; + struct usb_interface *intf; + unsigned long quirks; + + struct mutex ctrl_mutex; + unsigned int ctrl_pipe; + u16 ctrlbuf_len; + void *ctrlbuf; + + struct mutex bulk_mutex; + unsigned int tx_pipe; + u16 txbuf_len; + void *txbuf; + + unsigned int rx_pipe; + u16 rxbuf_len; + u16 rxdat_len; + void *rxbuf; + struct urb *urb; + + struct completion done; + + struct list_head cli_list; + + unsigned int nr_gpio_banks; + struct usbio_gpio_bank_desc gpios[USBIO_MAX_GPIOBANKS]; + + unsigned int nr_i2c_buses; + struct usbio_i2c_bus_desc i2cs[USBIO_MAX_I2CBUSES]; +}; + +/** + * struct usbio_client - represents a usbio client + * + * @auxdev: auxiliary device object + * @mutex: protects @bridge + * @bridge: usbio bridge who service the client + * @link: usbio bridge clients list member + */ +struct usbio_client { + struct auxiliary_device auxdev; + struct mutex mutex; + struct usbio_device *bridge; + struct list_head link; +}; + +#define adev_to_client(adev) container_of_const(adev, struct usbio_client, auxdev) + +static int usbio_ctrl_msg(struct usbio_device *usbio, u8 type, u8 cmd, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len) +{ + u8 request = USB_TYPE_VENDOR | USB_RECIP_DEVICE; + struct usbio_ctrl_packet *cpkt; + unsigned int pipe; + u16 cpkt_len; + int ret; + + lockdep_assert_held(&usbio->ctrl_mutex); + + if ((obuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt))) || + (ibuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt)))) + return -EMSGSIZE; + + /* Prepare Control Packet Header */ + cpkt = usbio->ctrlbuf; + cpkt->header.type = type; + cpkt->header.cmd = cmd; + if (type == USBIO_PKTTYPE_CTRL || ibuf_len) + cpkt->header.flags = USBIO_PKTFLAGS_REQRESP; + else + cpkt->header.flags = USBIO_PKTFLAG_CMP; + cpkt->len = obuf_len; + + /* Copy the data */ + memcpy(cpkt->data, obuf, obuf_len); + + pipe = usb_sndctrlpipe(usbio->udev, usbio->ctrl_pipe); + cpkt_len = sizeof(*cpkt) + obuf_len; + ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_OUT, 0, 0, + cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT); + dev_dbg(usbio->dev, "control out %d hdr %*phN data %*phN\n", ret, + (int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data); + + if (ret != cpkt_len) { + dev_err(usbio->dev, "USB control out failed: %d\n", ret); + return (ret < 0) ? ret : -EPROTO; + } + + if (!(cpkt->header.flags & USBIO_PKTFLAG_ACK)) + return 0; + + pipe = usb_rcvctrlpipe(usbio->udev, usbio->ctrl_pipe); + cpkt_len = sizeof(*cpkt) + ibuf_len; + ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_IN, 0, 0, + cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT); + dev_dbg(usbio->dev, "control in %d hdr %*phN data %*phN\n", ret, + (int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data); + + if (ret < sizeof(*cpkt)) { + dev_err(usbio->dev, "USB control in failed: %d\n", ret); + return (ret < 0) ? ret : -EPROTO; + } + + if (cpkt->header.type != type || cpkt->header.cmd != cmd || + !(cpkt->header.flags & USBIO_PKTFLAG_RSP)) { + dev_err(usbio->dev, "Unexpected reply type: %u, cmd: %u, flags: %u\n", + cpkt->header.type, cpkt->header.cmd, cpkt->header.flags); + return -EPROTO; + } + + if (cpkt->header.flags & USBIO_PKTFLAG_ERR) + return -EREMOTEIO; + + if (ibuf_len < cpkt->len) + return -ENOSPC; + + memcpy(ibuf, cpkt->data, cpkt->len); + + return cpkt->len; +} + +int usbio_control_msg(struct auxiliary_device *adev, u8 type, u8 cmd, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + int ret; + + guard(mutex)(&client->mutex); + + usbio = client->bridge; + if (!usbio) + return -ENODEV; /* Disconnected */ + + ret = usb_autopm_get_interface(usbio->intf); + if (ret) + return ret; + + mutex_lock(&usbio->ctrl_mutex); + + ret = usbio_ctrl_msg(client->bridge, type, cmd, obuf, obuf_len, ibuf, ibuf_len); + + mutex_unlock(&usbio->ctrl_mutex); + usb_autopm_put_interface(usbio->intf); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(usbio_control_msg, "USBIO"); + +static void usbio_bulk_recv(struct urb *urb) +{ + struct usbio_bulk_packet *bpkt = urb->transfer_buffer; + struct usbio_device *usbio = urb->context; + + if (!urb->status) { + if (bpkt->header.flags & USBIO_PKTFLAG_RSP) { + usbio->rxdat_len = urb->actual_length; + complete(&usbio->done); + } + } else if (urb->status != -ENOENT) { + dev_err(usbio->dev, "Bulk in error %d\n", urb->status); + } + + usb_submit_urb(usbio->urb, GFP_ATOMIC); +} + +int usbio_bulk_msg(struct auxiliary_device *adev, u8 type, u8 cmd, bool last, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio = client->bridge; + struct usbio_bulk_packet *bpkt; + int ret, act = 0; + u16 bpkt_len; + + lockdep_assert_held(&client->mutex); + lockdep_assert_held(&usbio->bulk_mutex); + + if ((obuf_len > (usbio->txbuf_len - sizeof(*bpkt))) || + (ibuf_len > (usbio->txbuf_len - sizeof(*bpkt)))) + return -EMSGSIZE; + + if (ibuf_len) + reinit_completion(&usbio->done); + + /* If no data to send, skip to read */ + if (!obuf_len) + goto read; + + /* Prepare Bulk Packet Header */ + bpkt = usbio->txbuf; + bpkt->header.type = type; + bpkt->header.cmd = cmd; + if (!last) + bpkt->header.flags = 0; + else if (ibuf_len) + bpkt->header.flags = USBIO_PKTFLAGS_REQRESP; + else + bpkt->header.flags = USBIO_PKTFLAG_CMP; + bpkt->len = cpu_to_le16(obuf_len); + + /* Copy the data */ + memcpy(bpkt->data, obuf, obuf_len); + + bpkt_len = sizeof(*bpkt) + obuf_len; + ret = usb_bulk_msg(usbio->udev, usbio->tx_pipe, bpkt, bpkt_len, &act, + USBIO_BULKXFER_TIMEOUT); + dev_dbg(usbio->dev, "bulk out %d hdr %*phN data %*phN\n", act, + (int)sizeof(*bpkt), bpkt, obuf_len, bpkt->data); + + if (ret || act != bpkt_len) { + dev_err(usbio->dev, "Bulk out failed: %d\n", ret); + return ret ?: -EPROTO; + } + + if (!(bpkt->header.flags & USBIO_PKTFLAG_ACK)) + return obuf_len; + +read: + ret = wait_for_completion_timeout(&usbio->done, USBIO_BULKXFER_TIMEOUT); + if (ret <= 0) { + dev_err(usbio->dev, "Bulk in wait failed: %d\n", ret); + return ret ?: -ETIMEDOUT; + } + + act = usbio->rxdat_len; + bpkt = usbio->rxbuf; + bpkt_len = le16_to_cpu(bpkt->len); + dev_dbg(usbio->dev, "bulk in %d hdr %*phN data %*phN\n", act, + (int)sizeof(*bpkt), bpkt, bpkt_len, bpkt->data); + + /* + * Unsupported bulk commands get only an usbio_packet_header with + * the error flag set as reply. Return -EPIPE for this case. + */ + if (act == sizeof(struct usbio_packet_header) && + (bpkt->header.flags & USBIO_PKTFLAG_ERR)) + return -EPIPE; + + if (act < sizeof(*bpkt)) { + dev_err(usbio->dev, "Bulk in short read: %d\n", act); + return -EPROTO; + } + + if (bpkt->header.type != type || bpkt->header.cmd != cmd || + !(bpkt->header.flags & USBIO_PKTFLAG_RSP)) { + dev_err(usbio->dev, + "Unexpected bulk in type 0x%02x cmd 0x%02x flags 0x%02x\n", + bpkt->header.type, bpkt->header.cmd, bpkt->header.flags); + return -EPROTO; + } + + if (bpkt->header.flags & USBIO_PKTFLAG_ERR) + return -EREMOTEIO; + + if (ibuf_len < bpkt_len) + return -ENOSPC; + + memcpy(ibuf, bpkt->data, bpkt_len); + + return bpkt_len; +} +EXPORT_SYMBOL_NS_GPL(usbio_bulk_msg, "USBIO"); + +int usbio_acquire(struct auxiliary_device *adev) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + int ret; + + mutex_lock(&client->mutex); + + usbio = client->bridge; + if (!usbio) { + ret = -ENODEV; /* Disconnected */ + goto err_unlock; + } + + ret = usb_autopm_get_interface(usbio->intf); + if (ret) + goto err_unlock; + + mutex_lock(&usbio->bulk_mutex); + + /* Leave client locked until release to avoid abba deadlock issues */ + return 0; + +err_unlock: + mutex_unlock(&client->mutex); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(usbio_acquire, "USBIO"); + +void usbio_release(struct auxiliary_device *adev) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio = client->bridge; + + lockdep_assert_held(&client->mutex); + + mutex_unlock(&usbio->bulk_mutex); + usb_autopm_put_interface(usbio->intf); + mutex_unlock(&client->mutex); +} +EXPORT_SYMBOL_NS_GPL(usbio_release, "USBIO"); + +void usbio_get_txrxbuf_len(struct auxiliary_device *adev, u16 *txbuf_len, u16 *rxbuf_len) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + + guard(mutex)(&client->mutex); + + usbio = client->bridge; + if (!usbio) + return; /* Disconnected */ + + *txbuf_len = usbio->txbuf_len; + *rxbuf_len = usbio->rxbuf_len; +} +EXPORT_SYMBOL_NS_GPL(usbio_get_txrxbuf_len, "USBIO"); + +unsigned long usbio_get_quirks(struct auxiliary_device *adev) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + + guard(mutex)(&client->mutex); + + usbio = client->bridge; + if (!usbio) + return 0; /* Disconnected */ + + return usbio->quirks; +} +EXPORT_SYMBOL_NS_GPL(usbio_get_quirks, "USBIO"); + +static void usbio_auxdev_release(struct device *dev) +{ + struct auxiliary_device *adev = to_auxiliary_dev(dev); + struct usbio_client *client = adev_to_client(adev); + + mutex_destroy(&client->mutex); + kfree(client); +} + +static int usbio_add_client(struct usbio_device *usbio, char *name, u8 id, void *data) +{ + struct usbio_client *client; + struct auxiliary_device *adev; + int ret; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return -ENOMEM; + + mutex_init(&client->mutex); + client->bridge = usbio; + adev = &client->auxdev; + adev->name = name; + adev->id = id; + + adev->dev.parent = usbio->dev; + adev->dev.platform_data = data; + adev->dev.release = usbio_auxdev_release; + + ret = auxiliary_device_init(adev); + if (ret) { + usbio_auxdev_release(&adev->dev); + return ret; + } + + ret = auxiliary_device_add(adev); + if (ret) { + auxiliary_device_uninit(adev); + return ret; + } + + list_add_tail(&client->link, &usbio->cli_list); + + return 0; +} + +static int usbio_enum_gpios(struct usbio_device *usbio) +{ + struct usbio_gpio_bank_desc *gpio = usbio->gpios; + + dev_dbg(usbio->dev, "GPIO Banks: %d\n", usbio->nr_gpio_banks); + + for (unsigned int i = 0; i < usbio->nr_gpio_banks; i++) + dev_dbg(usbio->dev, "\tBank%d[%d] map: %#08x\n", + gpio[i].id, gpio[i].pins, gpio[i].bmap); + + usbio_add_client(usbio, USBIO_GPIO_CLIENT, 0, gpio); + + return 0; +} + +static int usbio_enum_i2cs(struct usbio_device *usbio) +{ + struct usbio_i2c_bus_desc *i2c = usbio->i2cs; + + dev_dbg(usbio->dev, "I2C Busses: %d\n", usbio->nr_i2c_buses); + + for (unsigned int i = 0; i < usbio->nr_i2c_buses; i++) { + dev_dbg(usbio->dev, "\tBus%d caps: %#02x\n", i2c[i].id, i2c[i].caps); + usbio_add_client(usbio, USBIO_I2C_CLIENT, i, &i2c[i]); + } + + return 0; +} + +static int usbio_suspend(struct usb_interface *intf, pm_message_t msg) +{ + struct usbio_device *usbio = usb_get_intfdata(intf); + + usb_kill_urb(usbio->urb); + + return 0; +} + +static int usbio_resume(struct usb_interface *intf) +{ + struct usbio_device *usbio = usb_get_intfdata(intf); + + return usb_submit_urb(usbio->urb, GFP_KERNEL); +} + +static void usbio_disconnect(struct usb_interface *intf) +{ + struct usbio_device *usbio = usb_get_intfdata(intf); + struct usbio_client *client; + + /* Wakeup any clients waiting for a reply */ + usbio->rxdat_len = 0; + complete(&usbio->done); + + /* Let clients know the bridge is gone */ + list_for_each_entry(client, &usbio->cli_list, link) { + mutex_lock(&client->mutex); + client->bridge = NULL; + mutex_unlock(&client->mutex); + } + + /* From here on clients will no longer touch struct usbio_device */ + usb_kill_urb(usbio->urb); + usb_free_urb(usbio->urb); + + list_for_each_entry_reverse(client, &usbio->cli_list, link) { + auxiliary_device_delete(&client->auxdev); + auxiliary_device_uninit(&client->auxdev); + } +} + +static int usbio_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *ep_in, *ep_out; + struct device *dev = &intf->dev; + struct usbio_protver protver; + struct usbio_device *usbio; + struct usbio_fwver fwver; + int ret; + + usbio = devm_kzalloc(dev, sizeof(*usbio), GFP_KERNEL); + if (!usbio) + return -ENOMEM; + + ret = devm_mutex_init(dev, &usbio->ctrl_mutex); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &usbio->bulk_mutex); + if (ret) + return ret; + + usbio->dev = dev; + usbio->udev = udev; + usbio->intf = intf; + usbio->quirks = id ? id->driver_info : 0; + init_completion(&usbio->done); + INIT_LIST_HEAD(&usbio->cli_list); + usb_set_intfdata(intf, usbio); + + usbio->ctrl_pipe = usb_endpoint_num(&udev->ep0.desc); + usbio->ctrlbuf_len = usb_maxpacket(udev, usbio->ctrl_pipe); + usbio->ctrlbuf = devm_kzalloc(dev, usbio->ctrlbuf_len, GFP_KERNEL); + if (!usbio->ctrlbuf) + return -ENOMEM; + + /* Find the first bulk-in and bulk-out endpoints */ + ret = usb_find_common_endpoints(intf->cur_altsetting, &ep_in, &ep_out, + NULL, NULL); + if (ret) { + dev_err(dev, "Cannot find bulk endpoints: %d\n", ret); + return ret; + } + + usbio->tx_pipe = usb_sndbulkpipe(udev, usb_endpoint_num(ep_out)); + + if (usbio->quirks & USBIO_QUIRK_BULK_MAXP_63) + usbio->txbuf_len = 63; + else + usbio->txbuf_len = usb_endpoint_maxp(ep_out); + + usbio->txbuf = devm_kzalloc(dev, usbio->txbuf_len, GFP_KERNEL); + if (!usbio->txbuf) + return -ENOMEM; + + usbio->rx_pipe = usb_rcvbulkpipe(udev, usb_endpoint_num(ep_in)); + + if (usbio->quirks & USBIO_QUIRK_BULK_MAXP_63) + usbio->rxbuf_len = 63; + else + usbio->rxbuf_len = usb_endpoint_maxp(ep_in); + + usbio->rxbuf = devm_kzalloc(dev, usbio->rxbuf_len, GFP_KERNEL); + if (!usbio->rxbuf) + return -ENOMEM; + + usbio->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!usbio->urb) + return -ENOMEM; + + usb_fill_bulk_urb(usbio->urb, udev, usbio->rx_pipe, usbio->rxbuf, + usbio->rxbuf_len, usbio_bulk_recv, usbio); + ret = usb_submit_urb(usbio->urb, GFP_KERNEL); + if (ret) + return dev_err_probe(dev, ret, "Submitting usb urb\n"); + + mutex_lock(&usbio->ctrl_mutex); + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_HS, NULL, 0, NULL, 0); + if (ret < 0) + goto err_unlock; + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_PROTVER, NULL, 0, + &protver, sizeof(protver)); + if (ret < 0) + goto err_unlock; + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_FWVER, NULL, 0, + &fwver, sizeof(fwver)); + if (ret < 0) + goto err_unlock; + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_ENUMGPIO, NULL, 0, + usbio->gpios, sizeof(usbio->gpios)); + if (ret < 0 || ret % sizeof(struct usbio_gpio_bank_desc)) { + ret = (ret < 0) ? ret : -EPROTO; + goto err_unlock; + } + usbio->nr_gpio_banks = ret / sizeof(struct usbio_gpio_bank_desc); + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_ENUMI2C, NULL, 0, + usbio->i2cs, sizeof(usbio->i2cs)); + if (ret < 0 || ret % sizeof(struct usbio_i2c_bus_desc)) { + ret = (ret < 0) ? ret : -EPROTO; + goto err_unlock; + } + usbio->nr_i2c_buses = ret / sizeof(struct usbio_i2c_bus_desc); + + mutex_unlock(&usbio->ctrl_mutex); + + dev_dbg(dev, "ProtVer(BCD): %02x FwVer: %d.%d.%d.%d\n", + protver.ver, fwver.major, fwver.minor, + le16_to_cpu(fwver.patch), le16_to_cpu(fwver.build)); + + usbio_enum_gpios(usbio); + usbio_enum_i2cs(usbio); + + return 0; + +err_unlock: + mutex_unlock(&usbio->ctrl_mutex); + usb_kill_urb(usbio->urb); + usb_free_urb(usbio->urb); + + return ret; +} + +static const struct usb_device_id usbio_table[] = { + { USB_DEVICE(0x2ac1, 0x20c1), /* Lattice NX40 */ + .driver_info = USBIO_QUIRK_I2C_MAX_RW_LEN_52 }, + { USB_DEVICE(0x2ac1, 0x20c9), /* Lattice NX33 */ + .driver_info = USBIO_QUIRK_I2C_NO_INIT_ACK | USBIO_QUIRK_I2C_MAX_RW_LEN_52 | + USBIO_QUIRK_I2C_ALLOW_400KHZ }, + { USB_DEVICE(0x2ac1, 0x20cb) }, /* Lattice NX33U */ + { USB_DEVICE(0x06cb, 0x0701), /* Synaptics Sabre */ + .driver_info = USBIO_QUIRK_BULK_MAXP_63 | USBIO_QUIRK_I2C_USE_CHUNK_LEN }, + { } +}; +MODULE_DEVICE_TABLE(usb, usbio_table); + +static struct usb_driver usbio_driver = { + .name = "usbio-bridge", + .probe = usbio_probe, + .disconnect = usbio_disconnect, + .suspend = usbio_suspend, + .resume = usbio_resume, + .id_table = usbio_table, + .supports_autosuspend = 1, +}; +module_usb_driver(usbio_driver); + +struct usbio_match_ids_walk_data { + struct acpi_device *adev; + const struct acpi_device_id *hids; + unsigned int id; +}; + +static int usbio_match_device_ids(struct acpi_device *adev, void *data) +{ + struct usbio_match_ids_walk_data *wd = data; + unsigned int id = 0; + char *uid; + + if (acpi_match_device_ids(adev, wd->hids)) + return 0; + + uid = acpi_device_uid(adev); + if (uid) { + for (int i = 0; i < strlen(uid); i++) { + if (!kstrtouint(&uid[i], 10, &id)) + break; + } + } + + if (!uid || wd->id == id) { + wd->adev = adev; + return 1; + } + + return 0; +} + +void usbio_acpi_bind(struct auxiliary_device *adev, const struct acpi_device_id *hids) +{ + struct device *dev = &adev->dev; + struct acpi_device *parent; + struct usbio_match_ids_walk_data wd = { + .adev = NULL, + .hids = hids, + .id = adev->id, + }; + + parent = ACPI_COMPANION(dev->parent); + if (!parent) + return; + + acpi_dev_for_each_child(parent, usbio_match_device_ids, &wd); + if (wd.adev) + ACPI_COMPANION_SET(dev, wd.adev); +} +EXPORT_SYMBOL_NS_GPL(usbio_acpi_bind, "USBIO"); + +MODULE_DESCRIPTION("Intel USBIO Bridge driver"); +MODULE_AUTHOR("Israel Cepeda "); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/usb/usbio.h b/include/linux/usb/usbio.h new file mode 100644 index 000000000000..6c4e7c246d58 --- /dev/null +++ b/include/linux/usb/usbio.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2025 Intel Corporation. + * + */ + +#ifndef _LINUX_USBIO_H_ +#define _LINUX_USBIO_H_ + +#include +#include +#include +#include + +/*********************** + * USBIO Clients Names * + ***********************/ +#define USBIO_GPIO_CLIENT "usbio-gpio" +#define USBIO_I2C_CLIENT "usbio-i2c" + +/**************** + * USBIO quirks * + ****************/ +#define USBIO_QUIRK_BULK_MAXP_63 BIT(0) /* Force bulk endpoint maxp to 63 */ +#define USBIO_QUIRK_I2C_NO_INIT_ACK BIT(8) /* Do not ask for ack on I2C init */ +#define USBIO_QUIRK_I2C_MAX_RW_LEN_52 BIT(9) /* Set i2c-adapter max r/w len to 52 */ +#define USBIO_QUIRK_I2C_USE_CHUNK_LEN BIT(10) /* Send chunk-len for split xfers */ +#define USBIO_QUIRK_I2C_ALLOW_400KHZ BIT(11) /* Override desc, allowing 400 KHz */ + +/************************** + * USBIO Type Definitions * + **************************/ + +/* USBIO Packet Type */ +#define USBIO_PKTTYPE_CTRL 1 +#define USBIO_PKTTYPE_DBG 2 +#define USBIO_PKTTYPE_GPIO 3 +#define USBIO_PKTTYPE_I2C 4 + +/* USBIO Packet Header */ +struct usbio_packet_header { + u8 type; + u8 cmd; + u8 flags; +} __packed; + +/* USBIO Control Transfer Packet */ +struct usbio_ctrl_packet { + struct usbio_packet_header header; + u8 len; + u8 data[] __counted_by(len); +} __packed; + +/* USBIO Bulk Transfer Packet */ +struct usbio_bulk_packet { + struct usbio_packet_header header; + __le16 len; + u8 data[] __counted_by(len); +} __packed; + +/* USBIO GPIO commands */ +enum usbio_gpio_cmd { + USBIO_GPIOCMD_DEINIT, + USBIO_GPIOCMD_INIT, + USBIO_GPIOCMD_READ, + USBIO_GPIOCMD_WRITE, + USBIO_GPIOCMD_END +}; + +/* USBIO GPIO config */ +enum usbio_gpio_pincfg { + USBIO_GPIO_PINCFG_DEFAULT, + USBIO_GPIO_PINCFG_PULLUP, + USBIO_GPIO_PINCFG_PULLDOWN, + USBIO_GPIO_PINCFG_PUSHPULL +}; + +#define USBIO_GPIO_PINCFG_SHIFT 2 +#define USBIO_GPIO_PINCFG_MASK (0x3 << USBIO_GPIO_PINCFG_SHIFT) +#define USBIO_GPIO_SET_PINCFG(pincfg) \ + (((pincfg) << USBIO_GPIO_PINCFG_SHIFT) & USBIO_GPIO_PINCFG_MASK) + +enum usbio_gpio_pinmode { + USBIO_GPIO_PINMOD_INVAL, + USBIO_GPIO_PINMOD_INPUT, + USBIO_GPIO_PINMOD_OUTPUT, + USBIO_GPIO_PINMOD_MAXVAL +}; + +#define USBIO_GPIO_PINMOD_MASK 0x3 +#define USBIO_GPIO_SET_PINMOD(pin) (pin & USBIO_GPIO_PINMOD_MASK) + +/************************* + * USBIO GPIO Controller * + *************************/ + +#define USBIO_MAX_GPIOBANKS 5 +#define USBIO_GPIOSPERBANK 32 + +struct usbio_gpio_bank_desc { + u8 id; + u8 pins; + __le32 bmap; +} __packed; + +struct usbio_gpio_init { + u8 bankid; + u8 config; + u8 pincount; + u8 pin; +} __packed; + +struct usbio_gpio_rw { + u8 bankid; + u8 pincount; + u8 pin; + __le32 value; +} __packed; + +/* USBIO I2C commands */ +enum usbio_i2c_cmd { + USBIO_I2CCMD_UNINIT, + USBIO_I2CCMD_INIT, + USBIO_I2CCMD_READ, + USBIO_I2CCMD_WRITE, + USBIO_I2CCMD_END +}; + +/************************ + * USBIO I2C Controller * + ************************/ + +#define USBIO_MAX_I2CBUSES 5 + +#define USBIO_I2C_BUS_ADDR_CAP_10B BIT(3) /* 10bit address support */ +#define USBIO_I2C_BUS_MODE_CAP_MASK 0x3 +#define USBIO_I2C_BUS_MODE_CAP_SM 0 /* Standard Mode */ +#define USBIO_I2C_BUS_MODE_CAP_FM 1 /* Fast Mode */ +#define USBIO_I2C_BUS_MODE_CAP_FMP 2 /* Fast Mode+ */ +#define USBIO_I2C_BUS_MODE_CAP_HSM 3 /* High-Speed Mode */ + +struct usbio_i2c_bus_desc { + u8 id; + u8 caps; +} __packed; + +struct usbio_i2c_uninit { + u8 busid; + __le16 config; +} __packed; + +struct usbio_i2c_init { + u8 busid; + __le16 config; + __le32 speed; +} __packed; + +struct usbio_i2c_rw { + u8 busid; + __le16 config; + __le16 size; + u8 data[] __counted_by(size); +} __packed; + +int usbio_control_msg(struct auxiliary_device *adev, u8 type, u8 cmd, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len); + +int usbio_bulk_msg(struct auxiliary_device *adev, u8 type, u8 cmd, bool last, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len); + +int usbio_acquire(struct auxiliary_device *adev); +void usbio_release(struct auxiliary_device *adev); +void usbio_get_txrxbuf_len(struct auxiliary_device *adev, u16 *txbuf_len, u16 *rxbuf_len); +unsigned long usbio_get_quirks(struct auxiliary_device *adev); +void usbio_acpi_bind(struct auxiliary_device *adev, const struct acpi_device_id *hids); + +#endif From c122451ce04e6991316ed37bac41d45646942027 Mon Sep 17 00:00:00 2001 From: Israel Cepeda Date: Thu, 11 Sep 2025 20:13:42 +0200 Subject: [PATCH 065/132] gpio: Add Intel USBIO GPIO driver Add a a driver for the GPIO auxbus child device of the Intel USBIO USB IO-expander used by the MIPI cameras on various new (Meteor Lake and later) Intel laptops. Reviewed-by: Linus Walleij Acked-by: Bartosz Golaszewski Co-developed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Israel Cepeda Link: https://lore.kernel.org/r/20250911181343.77398-3-hansg@kernel.org Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + drivers/gpio/Kconfig | 11 ++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-usbio.c | 247 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 260 insertions(+) create mode 100644 drivers/gpio/gpio-usbio.c diff --git a/MAINTAINERS b/MAINTAINERS index 434ac1593332..7fef488118a4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12693,6 +12693,7 @@ M: Israel Cepeda M: Hans de Goede R: Sakari Ailus S: Maintained +F: drivers/gpio/gpio-usbio.c F: drivers/usb/misc/usbio.c F: include/linux/usb/usbio.h diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index e43abb322fa6..9390ef4e5079 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1923,6 +1923,17 @@ config GPIO_MPSSE GPIO driver for FTDI's MPSSE interface. These can do input and output. Each MPSSE provides 16 IO pins. +config GPIO_USBIO + tristate "Intel USBIO GPIO support" + depends on USB_USBIO + default USB_USBIO + help + Select this option to enable GPIO driver for the INTEL + USBIO driver stack. + + This driver can also be built as a module. If so, the module + will be called gpio_usbio. + endmenu menu "Virtual GPIO drivers" diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 379f55e9ed1e..b1593ce92ebe 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -192,6 +192,7 @@ obj-$(CONFIG_GPIO_TS5500) += gpio-ts5500.o obj-$(CONFIG_GPIO_TWL4030) += gpio-twl4030.o obj-$(CONFIG_GPIO_TWL6040) += gpio-twl6040.o obj-$(CONFIG_GPIO_UNIPHIER) += gpio-uniphier.o +obj-$(CONFIG_GPIO_USBIO) += gpio-usbio.o obj-$(CONFIG_GPIO_VF610) += gpio-vf610.o obj-$(CONFIG_GPIO_VIPERBOARD) += gpio-viperboard.o obj-$(CONFIG_GPIO_VIRTUSER) += gpio-virtuser.o diff --git a/drivers/gpio/gpio-usbio.c b/drivers/gpio/gpio-usbio.c new file mode 100644 index 000000000000..e13c120824e3 --- /dev/null +++ b/drivers/gpio/gpio-usbio.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025 Intel Corporation. + * Copyright (c) 2025 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct usbio_gpio_bank { + u8 config[USBIO_GPIOSPERBANK]; + u32 bitmap; +}; + +struct usbio_gpio { + struct mutex config_mutex; /* Protects banks[x].config */ + struct usbio_gpio_bank banks[USBIO_MAX_GPIOBANKS]; + struct gpio_chip gc; + struct auxiliary_device *adev; +}; + +static const struct acpi_device_id usbio_gpio_acpi_hids[] = { + { "INTC1007" }, /* MTL */ + { "INTC10B2" }, /* ARL */ + { "INTC10B5" }, /* LNL */ + { "INTC10E2" }, /* PTL */ + { } +}; + +static void usbio_gpio_get_bank_and_pin(struct gpio_chip *gc, unsigned int offset, + struct usbio_gpio_bank **bank_ret, + unsigned int *pin_ret) +{ + struct usbio_gpio *gpio = gpiochip_get_data(gc); + struct device *dev = &gpio->adev->dev; + struct usbio_gpio_bank *bank; + unsigned int pin; + + bank = &gpio->banks[offset / USBIO_GPIOSPERBANK]; + pin = offset % USBIO_GPIOSPERBANK; + if (~bank->bitmap & BIT(pin)) { + /* The FW bitmap sometimes is invalid, warn and continue */ + dev_warn_once(dev, FW_BUG "GPIO %u is not in FW pins bitmap\n", offset); + } + + *bank_ret = bank; + *pin_ret = pin; +} + +static int usbio_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct usbio_gpio_bank *bank; + unsigned int pin; + u8 cfg; + + usbio_gpio_get_bank_and_pin(gc, offset, &bank, &pin); + + cfg = bank->config[pin] & USBIO_GPIO_PINMOD_MASK; + + return (cfg == USBIO_GPIO_PINMOD_OUTPUT) ? + GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; +} + +static int usbio_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct usbio_gpio *gpio = gpiochip_get_data(gc); + struct usbio_gpio_bank *bank; + struct usbio_gpio_rw gbuf; + unsigned int pin; + int ret; + + usbio_gpio_get_bank_and_pin(gc, offset, &bank, &pin); + + gbuf.bankid = offset / USBIO_GPIOSPERBANK; + gbuf.pincount = 1; + gbuf.pin = pin; + + ret = usbio_control_msg(gpio->adev, USBIO_PKTTYPE_GPIO, USBIO_GPIOCMD_READ, + &gbuf, sizeof(gbuf) - sizeof(gbuf.value), + &gbuf, sizeof(gbuf)); + if (ret != sizeof(gbuf)) + return (ret < 0) ? ret : -EPROTO; + + return (le32_to_cpu(gbuf.value) >> pin) & 1; +} + +static int usbio_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct usbio_gpio *gpio = gpiochip_get_data(gc); + struct usbio_gpio_bank *bank; + struct usbio_gpio_rw gbuf; + unsigned int pin; + + usbio_gpio_get_bank_and_pin(gc, offset, &bank, &pin); + + gbuf.bankid = offset / USBIO_GPIOSPERBANK; + gbuf.pincount = 1; + gbuf.pin = pin; + gbuf.value = cpu_to_le32(value << pin); + + return usbio_control_msg(gpio->adev, USBIO_PKTTYPE_GPIO, USBIO_GPIOCMD_WRITE, + &gbuf, sizeof(gbuf), NULL, 0); +} + +static int usbio_gpio_update_config(struct gpio_chip *gc, unsigned int offset, + u8 mask, u8 value) +{ + struct usbio_gpio *gpio = gpiochip_get_data(gc); + struct usbio_gpio_bank *bank; + struct usbio_gpio_init gbuf; + unsigned int pin; + + usbio_gpio_get_bank_and_pin(gc, offset, &bank, &pin); + + guard(mutex)(&gpio->config_mutex); + + bank->config[pin] &= ~mask; + bank->config[pin] |= value; + + gbuf.bankid = offset / USBIO_GPIOSPERBANK; + gbuf.config = bank->config[pin]; + gbuf.pincount = 1; + gbuf.pin = pin; + + return usbio_control_msg(gpio->adev, USBIO_PKTTYPE_GPIO, USBIO_GPIOCMD_INIT, + &gbuf, sizeof(gbuf), NULL, 0); +} + +static int usbio_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + return usbio_gpio_update_config(gc, offset, USBIO_GPIO_PINMOD_MASK, + USBIO_GPIO_SET_PINMOD(USBIO_GPIO_PINMOD_INPUT)); +} + +static int usbio_gpio_direction_output(struct gpio_chip *gc, + unsigned int offset, int value) +{ + int ret; + + ret = usbio_gpio_update_config(gc, offset, USBIO_GPIO_PINMOD_MASK, + USBIO_GPIO_SET_PINMOD(USBIO_GPIO_PINMOD_OUTPUT)); + if (ret) + return ret; + + return usbio_gpio_set(gc, offset, value); +} + +static int usbio_gpio_set_config(struct gpio_chip *gc, unsigned int offset, + unsigned long config) +{ + u8 value; + + switch (pinconf_to_config_param(config)) { + case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT: + value = USBIO_GPIO_SET_PINCFG(USBIO_GPIO_PINCFG_DEFAULT); + break; + case PIN_CONFIG_BIAS_PULL_UP: + value = USBIO_GPIO_SET_PINCFG(USBIO_GPIO_PINCFG_PULLUP); + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + value = USBIO_GPIO_SET_PINCFG(USBIO_GPIO_PINCFG_PULLDOWN); + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + value = USBIO_GPIO_SET_PINCFG(USBIO_GPIO_PINCFG_PUSHPULL); + break; + default: + return -ENOTSUPP; + } + + return usbio_gpio_update_config(gc, offset, USBIO_GPIO_PINCFG_MASK, value); +} + +static int usbio_gpio_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *adev_id) +{ + struct usbio_gpio_bank_desc *bank_desc; + struct device *dev = &adev->dev; + struct usbio_gpio *gpio; + int bank, ret; + + bank_desc = dev_get_platdata(dev); + if (!bank_desc) + return -EINVAL; + + gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + ret = devm_mutex_init(dev, &gpio->config_mutex); + if (ret) + return ret; + + gpio->adev = adev; + + usbio_acpi_bind(gpio->adev, usbio_gpio_acpi_hids); + + for (bank = 0; bank < USBIO_MAX_GPIOBANKS && bank_desc[bank].bmap; bank++) + gpio->banks[bank].bitmap = le32_to_cpu(bank_desc[bank].bmap); + + gpio->gc.label = ACPI_COMPANION(dev) ? + acpi_dev_name(ACPI_COMPANION(dev)) : dev_name(dev); + gpio->gc.parent = dev; + gpio->gc.owner = THIS_MODULE; + gpio->gc.get_direction = usbio_gpio_get_direction; + gpio->gc.direction_input = usbio_gpio_direction_input; + gpio->gc.direction_output = usbio_gpio_direction_output; + gpio->gc.get = usbio_gpio_get; + gpio->gc.set = usbio_gpio_set; + gpio->gc.set_config = usbio_gpio_set_config; + gpio->gc.base = -1; + gpio->gc.ngpio = bank * USBIO_GPIOSPERBANK; + gpio->gc.can_sleep = true; + + ret = devm_gpiochip_add_data(dev, &gpio->gc, gpio); + if (ret) + return ret; + + if (has_acpi_companion(dev)) + acpi_dev_clear_dependencies(ACPI_COMPANION(dev)); + + return 0; +} + +static const struct auxiliary_device_id usbio_gpio_id_table[] = { + { "usbio.usbio-gpio" }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, usbio_gpio_id_table); + +static struct auxiliary_driver usbio_gpio_driver = { + .name = USBIO_GPIO_CLIENT, + .probe = usbio_gpio_probe, + .id_table = usbio_gpio_id_table +}; +module_auxiliary_driver(usbio_gpio_driver); + +MODULE_DESCRIPTION("Intel USBIO GPIO driver"); +MODULE_AUTHOR("Israel Cepeda "); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USBIO"); From daf161343a3904f6699febbfb1e18d532371ba00 Mon Sep 17 00:00:00 2001 From: Israel Cepeda Date: Thu, 11 Sep 2025 20:13:43 +0200 Subject: [PATCH 066/132] i2c: Add Intel USBIO I2C driver Add a a driver for the I2C auxbus child device of the Intel USBIO USB IO-expander used by the MIPI cameras on various new (Meteor Lake and later) Intel laptops. Co-developed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Israel Cepeda Reviewed-by: Wolfram Sang Link: https://lore.kernel.org/r/20250911181343.77398-4-hansg@kernel.org Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + drivers/i2c/busses/Kconfig | 11 ++ drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-usbio.c | 320 +++++++++++++++++++++++++++++++++ 4 files changed, 333 insertions(+) create mode 100644 drivers/i2c/busses/i2c-usbio.c diff --git a/MAINTAINERS b/MAINTAINERS index 7fef488118a4..c65f2f71db0c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12694,6 +12694,7 @@ M: Hans de Goede R: Sakari Ailus S: Maintained F: drivers/gpio/gpio-usbio.c +F: drivers/i2c/busses/i2c-usbio.c F: drivers/usb/misc/usbio.c F: include/linux/usb/usbio.h diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 070d014fdc5d..06b1b702fd7a 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -1357,6 +1357,17 @@ config I2C_LJCA This driver can also be built as a module. If so, the module will be called i2c-ljca. +config I2C_USBIO + tristate "Intel USBIO I2C Adapter support" + depends on USB_USBIO + default USB_USBIO + help + Select this option to enable I2C driver for the INTEL + USBIO driver stack. + + This driver can also be built as a module. If so, the module + will be called i2c_usbio. + config I2C_CP2615 tristate "Silicon Labs CP2615 USB sound card and I2C adapter" depends on USB diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 04db855fdfd6..401a79c9767e 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -135,6 +135,7 @@ obj-$(CONFIG_I2C_GXP) += i2c-gxp.o obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o obj-$(CONFIG_I2C_DLN2) += i2c-dln2.o obj-$(CONFIG_I2C_LJCA) += i2c-ljca.o +obj-$(CONFIG_I2C_USBIO) += i2c-usbio.o obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o obj-$(CONFIG_I2C_PCI1XXXX) += i2c-mchp-pci1xxxx.o diff --git a/drivers/i2c/busses/i2c-usbio.c b/drivers/i2c/busses/i2c-usbio.c new file mode 100644 index 000000000000..d42f9ab6e9a5 --- /dev/null +++ b/drivers/i2c/busses/i2c-usbio.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025 Intel Corporation. + * Copyright (c) 2025 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#define I2C_RW_OVERHEAD (sizeof(struct usbio_bulk_packet) + sizeof(struct usbio_i2c_rw)) + +struct usbio_i2c { + struct i2c_adapter adap; + struct auxiliary_device *adev; + struct usbio_i2c_rw *rwbuf; + unsigned long quirks; + u32 speed; + u16 txbuf_len; + u16 rxbuf_len; +}; + +static const struct acpi_device_id usbio_i2c_acpi_hids[] = { + { "INTC1008" }, /* MTL */ + { "INTC10B3" }, /* ARL */ + { "INTC10B6" }, /* LNL */ + { "INTC10E3" }, /* PTL */ + { } +}; + +static const u32 usbio_i2c_speeds[] = { + I2C_MAX_STANDARD_MODE_FREQ, + I2C_MAX_FAST_MODE_FREQ, + I2C_MAX_FAST_MODE_PLUS_FREQ, + I2C_MAX_HIGH_SPEED_MODE_FREQ +}; + +static void usbio_i2c_uninit(struct i2c_adapter *adap, struct i2c_msg *msg) +{ + struct usbio_i2c *i2c = i2c_get_adapdata(adap); + struct usbio_i2c_uninit ubuf; + + ubuf.busid = i2c->adev->id; + ubuf.config = cpu_to_le16(msg->addr); + + usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, USBIO_I2CCMD_UNINIT, true, + &ubuf, sizeof(ubuf), NULL, 0); +} + +static int usbio_i2c_init(struct i2c_adapter *adap, struct i2c_msg *msg) +{ + struct usbio_i2c *i2c = i2c_get_adapdata(adap); + struct usbio_i2c_init ibuf; + void *reply_buf; + u16 reply_len; + int ret; + + ibuf.busid = i2c->adev->id; + ibuf.config = cpu_to_le16(msg->addr); + ibuf.speed = cpu_to_le32(i2c->speed); + + if (i2c->quirks & USBIO_QUIRK_I2C_NO_INIT_ACK) { + reply_buf = NULL; + reply_len = 0; + } else { + reply_buf = &ibuf; + reply_len = sizeof(ibuf); + } + + ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, USBIO_I2CCMD_INIT, true, + &ibuf, sizeof(ibuf), reply_buf, reply_len); + if (ret != sizeof(ibuf)) + return (ret < 0) ? ret : -EIO; + + return 0; +} + +static int usbio_i2c_read(struct i2c_adapter *adap, struct i2c_msg *msg) +{ + struct usbio_i2c *i2c = i2c_get_adapdata(adap); + u16 rxchunk = i2c->rxbuf_len - I2C_RW_OVERHEAD; + struct usbio_i2c_rw *rbuf = i2c->rwbuf; + int ret; + + rbuf->busid = i2c->adev->id; + rbuf->config = cpu_to_le16(msg->addr); + rbuf->size = cpu_to_le16(msg->len); + + if (msg->len > rxchunk) { + /* Need to split the input buffer */ + u16 len = 0; + + do { + if (msg->len - len < rxchunk) + rxchunk = msg->len - len; + + ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, + USBIO_I2CCMD_READ, true, + rbuf, len == 0 ? sizeof(*rbuf) : 0, + rbuf, sizeof(*rbuf) + rxchunk); + if (ret < 0) + return ret; + + memcpy(&msg->buf[len], rbuf->data, rxchunk); + len += rxchunk; + } while (msg->len > len); + + return 0; + } + + ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, USBIO_I2CCMD_READ, true, + rbuf, sizeof(*rbuf), rbuf, sizeof(*rbuf) + msg->len); + if (ret != sizeof(*rbuf) + msg->len) + return (ret < 0) ? ret : -EIO; + + memcpy(msg->buf, rbuf->data, msg->len); + + return 0; +} + +static int usbio_i2c_write(struct i2c_adapter *adap, struct i2c_msg *msg) +{ + struct usbio_i2c *i2c = i2c_get_adapdata(adap); + u16 txchunk = i2c->txbuf_len - I2C_RW_OVERHEAD; + struct usbio_i2c_rw *wbuf = i2c->rwbuf; + int ret; + + if (msg->len > txchunk) { + /* Need to split the output buffer */ + u16 len = 0; + + do { + wbuf->busid = i2c->adev->id; + wbuf->config = cpu_to_le16(msg->addr); + + if (i2c->quirks & USBIO_QUIRK_I2C_USE_CHUNK_LEN) + wbuf->size = cpu_to_le16(txchunk); + else + wbuf->size = cpu_to_le16(msg->len); + + memcpy(wbuf->data, &msg->buf[len], txchunk); + len += txchunk; + + ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, + USBIO_I2CCMD_WRITE, msg->len == len, + wbuf, sizeof(*wbuf) + txchunk, + wbuf, sizeof(*wbuf)); + if (ret < 0) + return ret; + + if (msg->len - len < txchunk) + txchunk = msg->len - len; + } while (msg->len > len); + + return 0; + } + + wbuf->busid = i2c->adev->id; + wbuf->config = cpu_to_le16(msg->addr); + wbuf->size = cpu_to_le16(msg->len); + memcpy(wbuf->data, msg->buf, msg->len); + + ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, USBIO_I2CCMD_WRITE, true, + wbuf, sizeof(*wbuf) + msg->len, wbuf, sizeof(*wbuf)); + if (ret != sizeof(*wbuf) || le16_to_cpu(wbuf->size) != msg->len) + return (ret < 0) ? ret : -EIO; + + return 0; +} + +static int usbio_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct usbio_i2c *i2c = i2c_get_adapdata(adap); + int ret; + + usbio_acquire(i2c->adev); + + ret = usbio_i2c_init(adap, msgs); + if (ret) + goto out_release; + + for (int i = 0; i < num; ret = ++i) { + if (msgs[i].flags & I2C_M_RD) + ret = usbio_i2c_read(adap, &msgs[i]); + else + ret = usbio_i2c_write(adap, &msgs[i]); + + if (ret) + break; + } + + usbio_i2c_uninit(adap, msgs); + +out_release: + usbio_release(i2c->adev); + + return ret; +} + +static u32 usbio_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_adapter_quirks usbio_i2c_quirks = { + .flags = I2C_AQ_NO_ZERO_LEN | I2C_AQ_NO_REP_START, + .max_read_len = SZ_4K, + .max_write_len = SZ_4K, +}; + +static const struct i2c_adapter_quirks usbio_i2c_quirks_max_rw_len52 = { + .flags = I2C_AQ_NO_ZERO_LEN | I2C_AQ_NO_REP_START, + .max_read_len = 52, + .max_write_len = 52, +}; + +static const struct i2c_algorithm usbio_i2c_algo = { + .master_xfer = usbio_i2c_xfer, + .functionality = usbio_i2c_func, +}; + +static int usbio_i2c_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *adev_id) +{ + struct usbio_i2c_bus_desc *i2c_desc; + struct device *dev = &adev->dev; + struct usbio_i2c *i2c; + u32 max_speed; + int ret; + + i2c_desc = dev_get_platdata(dev); + if (!i2c_desc) + return -EINVAL; + + i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + i2c->adev = adev; + + usbio_acpi_bind(i2c->adev, usbio_i2c_acpi_hids); + usbio_get_txrxbuf_len(i2c->adev, &i2c->txbuf_len, &i2c->rxbuf_len); + + i2c->rwbuf = devm_kzalloc(dev, max(i2c->txbuf_len, i2c->rxbuf_len), GFP_KERNEL); + if (!i2c->rwbuf) + return -ENOMEM; + + i2c->quirks = usbio_get_quirks(i2c->adev); + + max_speed = usbio_i2c_speeds[i2c_desc->caps & USBIO_I2C_BUS_MODE_CAP_MASK]; + if (max_speed < I2C_MAX_FAST_MODE_FREQ && + (i2c->quirks & USBIO_QUIRK_I2C_ALLOW_400KHZ)) + max_speed = I2C_MAX_FAST_MODE_FREQ; + + i2c->speed = i2c_acpi_find_bus_speed(dev); + if (!i2c->speed) + i2c->speed = I2C_MAX_STANDARD_MODE_FREQ; + else if (i2c->speed > max_speed) { + dev_warn(dev, "Invalid speed %u adjusting to bus max %u\n", + i2c->speed, max_speed); + i2c->speed = max_speed; + } + + i2c->adap.owner = THIS_MODULE; + i2c->adap.class = I2C_CLASS_HWMON; + i2c->adap.dev.parent = dev; + i2c->adap.algo = &usbio_i2c_algo; + + if (i2c->quirks & USBIO_QUIRK_I2C_MAX_RW_LEN_52) + i2c->adap.quirks = &usbio_i2c_quirks_max_rw_len52; + else + i2c->adap.quirks = &usbio_i2c_quirks; + + snprintf(i2c->adap.name, sizeof(i2c->adap.name), "%s.%d", + USBIO_I2C_CLIENT, i2c->adev->id); + + device_set_node(&i2c->adap.dev, dev_fwnode(&adev->dev)); + + auxiliary_set_drvdata(adev, i2c); + i2c_set_adapdata(&i2c->adap, i2c); + + ret = i2c_add_adapter(&i2c->adap); + if (ret) + return ret; + + if (has_acpi_companion(&i2c->adap.dev)) + acpi_dev_clear_dependencies(ACPI_COMPANION(&i2c->adap.dev)); + + return 0; +} + +static void usbio_i2c_remove(struct auxiliary_device *adev) +{ + struct usbio_i2c *i2c = auxiliary_get_drvdata(adev); + + i2c_del_adapter(&i2c->adap); +} + +static const struct auxiliary_device_id usbio_i2c_id_table[] = { + { "usbio.usbio-i2c" }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, usbio_i2c_id_table); + +static struct auxiliary_driver usbio_i2c_driver = { + .name = USBIO_I2C_CLIENT, + .probe = usbio_i2c_probe, + .remove = usbio_i2c_remove, + .id_table = usbio_i2c_id_table +}; +module_auxiliary_driver(usbio_i2c_driver); + +MODULE_DESCRIPTION("Intel USBIO I2C driver"); +MODULE_AUTHOR("Israel Cepeda "); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USBIO"); From ddb473a51b4bf33d040d827ae9a8724ee5ef1a36 Mon Sep 17 00:00:00 2001 From: Guan-Yu Lin Date: Thu, 11 Sep 2025 14:20:13 +0000 Subject: [PATCH 067/132] usb: xhci-plat: separate dev_pm_ops for each pm_event Separate dev_pm_ops for different power events such as suspend, thaw, and hibernation. This is crucial when xhci-plat driver needs to adapt its behavior based on different power state changes. Signed-off-by: Guan-Yu Lin Link: https://lore.kernel.org/r/20250911142051.90822-2-guanyulin@google.com Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250911142051.90822-2-guanyulin@google.com --- drivers/usb/host/xhci-plat.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 5eb51797de32..df2e942ad5f7 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -454,7 +454,7 @@ void xhci_plat_remove(struct platform_device *dev) } EXPORT_SYMBOL_GPL(xhci_plat_remove); -static int xhci_plat_suspend(struct device *dev) +static int xhci_plat_suspend_common(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); struct xhci_hcd *xhci = hcd_to_xhci(hcd); @@ -482,6 +482,16 @@ static int xhci_plat_suspend(struct device *dev) return 0; } +static int xhci_plat_suspend(struct device *dev) +{ + return xhci_plat_suspend_common(dev); +} + +static int xhci_plat_freeze(struct device *dev) +{ + return xhci_plat_suspend_common(dev); +} + static int xhci_plat_resume_common(struct device *dev, bool power_lost) { struct usb_hcd *hcd = dev_get_drvdata(dev); @@ -529,6 +539,11 @@ static int xhci_plat_resume(struct device *dev) return xhci_plat_resume_common(dev, false); } +static int xhci_plat_thaw(struct device *dev) +{ + return xhci_plat_resume_common(dev, false); +} + static int xhci_plat_restore(struct device *dev) { return xhci_plat_resume_common(dev, true); @@ -558,9 +573,9 @@ static int __maybe_unused xhci_plat_runtime_resume(struct device *dev) const struct dev_pm_ops xhci_plat_pm_ops = { .suspend = pm_sleep_ptr(xhci_plat_suspend), .resume = pm_sleep_ptr(xhci_plat_resume), - .freeze = pm_sleep_ptr(xhci_plat_suspend), - .thaw = pm_sleep_ptr(xhci_plat_resume), - .poweroff = pm_sleep_ptr(xhci_plat_suspend), + .freeze = pm_sleep_ptr(xhci_plat_freeze), + .thaw = pm_sleep_ptr(xhci_plat_thaw), + .poweroff = pm_sleep_ptr(xhci_plat_freeze), .restore = pm_sleep_ptr(xhci_plat_restore), SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend, From 7f70b89b2be66c03ddc76d3ad8aebeeec4a9c505 Mon Sep 17 00:00:00 2001 From: Guan-Yu Lin Date: Thu, 11 Sep 2025 14:20:14 +0000 Subject: [PATCH 068/132] usb: offload: add apis for offload usage tracking Introduce offload_usage and corresponding apis to track offload usage on each USB device. Offload denotes that there is another co-processor accessing the USB device via the same USB host controller. To optimize power usage, it's essential to monitor whether the USB device is actively used by other co-processor. This information is vital when determining if a USB device can be safely suspended during system power state transitions. Signed-off-by: Guan-Yu Lin Link: https://lore.kernel.org/r/20250911142051.90822-3-guanyulin@google.com Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250911142051.90822-3-guanyulin@google.com --- drivers/usb/core/Makefile | 1 + drivers/usb/core/offload.c | 136 +++++++++++++++++++++++++++++++++++++ drivers/usb/core/usb.c | 1 + include/linux/usb.h | 18 +++++ 4 files changed, 156 insertions(+) create mode 100644 drivers/usb/core/offload.c diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile index ac006abd13b3..766000b4939e 100644 --- a/drivers/usb/core/Makefile +++ b/drivers/usb/core/Makefile @@ -9,6 +9,7 @@ usbcore-y += devio.o notify.o generic.o quirks.o devices.o usbcore-y += phy.o port.o usbcore-$(CONFIG_OF) += of.o +usbcore-$(CONFIG_USB_XHCI_SIDEBAND) += offload.o usbcore-$(CONFIG_USB_PCI) += hcd-pci.o usbcore-$(CONFIG_ACPI) += usb-acpi.o diff --git a/drivers/usb/core/offload.c b/drivers/usb/core/offload.c new file mode 100644 index 000000000000..7c699f1b8d2b --- /dev/null +++ b/drivers/usb/core/offload.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * offload.c - USB offload related functions + * + * Copyright (c) 2025, Google LLC. + * + * Author: Guan-Yu Lin + */ + +#include + +#include "usb.h" + +/** + * usb_offload_get - increment the offload_usage of a USB device + * @udev: the USB device to increment its offload_usage + * + * Incrementing the offload_usage of a usb_device indicates that offload is + * enabled on this usb_device; that is, another entity is actively handling USB + * transfers. This information allows the USB driver to adjust its power + * management policy based on offload activity. + * + * Return: 0 on success. A negative error code otherwise. + */ +int usb_offload_get(struct usb_device *udev) +{ + int ret; + + usb_lock_device(udev); + if (udev->state == USB_STATE_NOTATTACHED) { + usb_unlock_device(udev); + return -ENODEV; + } + + if (udev->state == USB_STATE_SUSPENDED || + udev->offload_at_suspend) { + usb_unlock_device(udev); + return -EBUSY; + } + + /* + * offload_usage could only be modified when the device is active, since + * it will alter the suspend flow of the device. + */ + ret = usb_autoresume_device(udev); + if (ret < 0) { + usb_unlock_device(udev); + return ret; + } + + udev->offload_usage++; + usb_autosuspend_device(udev); + usb_unlock_device(udev); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_offload_get); + +/** + * usb_offload_put - drop the offload_usage of a USB device + * @udev: the USB device to drop its offload_usage + * + * The inverse operation of usb_offload_get, which drops the offload_usage of + * a USB device. This information allows the USB driver to adjust its power + * management policy based on offload activity. + * + * Return: 0 on success. A negative error code otherwise. + */ +int usb_offload_put(struct usb_device *udev) +{ + int ret; + + usb_lock_device(udev); + if (udev->state == USB_STATE_NOTATTACHED) { + usb_unlock_device(udev); + return -ENODEV; + } + + if (udev->state == USB_STATE_SUSPENDED || + udev->offload_at_suspend) { + usb_unlock_device(udev); + return -EBUSY; + } + + /* + * offload_usage could only be modified when the device is active, since + * it will alter the suspend flow of the device. + */ + ret = usb_autoresume_device(udev); + if (ret < 0) { + usb_unlock_device(udev); + return ret; + } + + /* Drop the count when it wasn't 0, ignore the operation otherwise. */ + if (udev->offload_usage) + udev->offload_usage--; + usb_autosuspend_device(udev); + usb_unlock_device(udev); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_offload_put); + +/** + * usb_offload_check - check offload activities on a USB device + * @udev: the USB device to check its offload activity. + * + * Check if there are any offload activity on the USB device right now. This + * information could be used for power management or other forms of resource + * management. + * + * The caller must hold @udev's device lock. In addition, the caller should + * ensure downstream usb devices are all either suspended or marked as + * "offload_at_suspend" to ensure the correctness of the return value. + * + * Returns true on any offload activity, false otherwise. + */ +bool usb_offload_check(struct usb_device *udev) __must_hold(&udev->dev->mutex) +{ + struct usb_device *child; + bool active; + int port1; + + usb_hub_for_each_child(udev, port1, child) { + usb_lock_device(child); + active = usb_offload_check(child); + usb_unlock_device(child); + if (active) + return true; + } + + return !!udev->offload_usage; +} +EXPORT_SYMBOL_GPL(usb_offload_check); diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index b88b6271cb30..b6b0b8489523 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -670,6 +670,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, set_dev_node(&dev->dev, dev_to_node(bus->sysdev)); dev->state = USB_STATE_ATTACHED; dev->lpm_disable_count = 1; + dev->offload_usage = 0; atomic_set(&dev->urbnum, 0); INIT_LIST_HEAD(&dev->ep0.urb_list); diff --git a/include/linux/usb.h b/include/linux/usb.h index 70ef00c42d22..e85105939af8 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -636,6 +636,8 @@ struct usb3_lpm_parameters { * @do_remote_wakeup: remote wakeup should be enabled * @reset_resume: needs reset instead of resume * @port_is_suspended: the upstream port is suspended (L2 or U3) + * @offload_at_suspend: offload activities during suspend is enabled. + * @offload_usage: number of offload activities happening on this usb device. * @slot_id: Slot ID assigned by xHCI * @l1_params: best effor service latency for USB2 L1 LPM state, and L1 timeout. * @u1_params: exit latencies for USB3 U1 LPM state, and hub-initiated timeout. @@ -724,6 +726,8 @@ struct usb_device { unsigned do_remote_wakeup:1; unsigned reset_resume:1; unsigned port_is_suspended:1; + unsigned offload_at_suspend:1; + int offload_usage; enum usb_link_tunnel_mode tunnel_mode; struct device_link *usb4_link; @@ -841,6 +845,20 @@ static inline void usb_mark_last_busy(struct usb_device *udev) { } #endif +#if IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND) +int usb_offload_get(struct usb_device *udev); +int usb_offload_put(struct usb_device *udev); +bool usb_offload_check(struct usb_device *udev); +#else + +static inline int usb_offload_get(struct usb_device *udev) +{ return 0; } +static inline int usb_offload_put(struct usb_device *udev) +{ return 0; } +static inline bool usb_offload_check(struct usb_device *udev) +{ return false; } +#endif + extern int usb_disable_lpm(struct usb_device *udev); extern void usb_enable_lpm(struct usb_device *udev); /* Same as above, but these functions lock/unlock the bandwidth_mutex. */ From ef82a4803aabaf623bfcae07981406f1386eabf9 Mon Sep 17 00:00:00 2001 From: Guan-Yu Lin Date: Thu, 11 Sep 2025 14:20:15 +0000 Subject: [PATCH 069/132] xhci: sideband: add api to trace sideband usage The existing sideband driver only registers sidebands without tracking their active usage. To address this, sideband will now record its active usage when it creates/removes interrupters. In addition, a new api is introduced to provide a means for other dirvers to fetch sideband activity information on a USB host controller. Signed-off-by: Guan-Yu Lin Link: https://lore.kernel.org/r/20250911142051.90822-4-guanyulin@google.com Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250911142051.90822-4-guanyulin@google.com --- drivers/usb/host/xhci-sideband.c | 36 +++++++++++++++++++++++++++++++ include/linux/usb/xhci-sideband.h | 9 ++++++++ 2 files changed, 45 insertions(+) diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c index d49f9886dd84..e771a476fef2 100644 --- a/drivers/usb/host/xhci-sideband.c +++ b/drivers/usb/host/xhci-sideband.c @@ -266,6 +266,31 @@ xhci_sideband_get_event_buffer(struct xhci_sideband *sb) } EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer); +/** + * xhci_sideband_check - check the existence of active sidebands + * @hcd: the host controller driver associated with the target host controller + * + * Allow other drivers, such as usb controller driver, to check if there are + * any sideband activity on the host controller. This information could be used + * for power management or other forms of resource management. The caller should + * ensure downstream usb devices are all either suspended or marked as + * "offload_at_suspend" to ensure the correctness of the return value. + * + * Returns true on any active sideband existence, false otherwise. + */ +bool xhci_sideband_check(struct usb_hcd *hcd) +{ + struct usb_device *udev = hcd->self.root_hub; + bool active; + + usb_lock_device(udev); + active = usb_offload_check(udev); + usb_unlock_device(udev); + + return active; +} +EXPORT_SYMBOL_GPL(xhci_sideband_check); + /** * xhci_sideband_create_interrupter - creates a new interrupter for this sideband * @sb: sideband instance for this usb device @@ -286,6 +311,7 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, bool ip_autoclear, u32 imod_interval, int intr_num) { int ret = 0; + struct usb_device *udev; if (!sb || !sb->xhci) return -ENODEV; @@ -304,6 +330,9 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, goto out; } + udev = sb->vdev->udev; + ret = usb_offload_get(udev); + sb->ir->ip_autoclear = ip_autoclear; out: @@ -323,6 +352,8 @@ EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter); void xhci_sideband_remove_interrupter(struct xhci_sideband *sb) { + struct usb_device *udev; + if (!sb || !sb->ir) return; @@ -330,6 +361,11 @@ xhci_sideband_remove_interrupter(struct xhci_sideband *sb) xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir); sb->ir = NULL; + udev = sb->vdev->udev; + + if (udev->state != USB_STATE_NOTATTACHED) + usb_offload_put(udev); + mutex_unlock(&sb->mutex); } EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter); diff --git a/include/linux/usb/xhci-sideband.h b/include/linux/usb/xhci-sideband.h index 45288c392f6e..005257085dcb 100644 --- a/include/linux/usb/xhci-sideband.h +++ b/include/linux/usb/xhci-sideband.h @@ -11,6 +11,7 @@ #include #include +#include #define EP_CTX_PER_DEV 31 /* FIXME defined twice, from xhci.h */ @@ -83,6 +84,14 @@ xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb, struct usb_host_endpoint *host_ep); struct sg_table * xhci_sideband_get_event_buffer(struct xhci_sideband *sb); + +#if IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND) +bool xhci_sideband_check(struct usb_hcd *hcd); +#else +static inline bool xhci_sideband_check(struct usb_hcd *hcd) +{ return false; } +#endif /* IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND) */ + int xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, bool ip_autoclear, u32 imod_interval, int intr_num); From 38d627bc8522a6a2fd6f9454c8b3b5d3848f5f03 Mon Sep 17 00:00:00 2001 From: Guan-Yu Lin Date: Thu, 11 Sep 2025 14:20:16 +0000 Subject: [PATCH 070/132] usb: host: enable USB offload during system sleep Sharing a USB controller with another entity via xhci-sideband driver creates power management complexities. To prevent the USB controller from being inadvertently deactivated while in use by the other entity, a usage-count based mechanism is implemented. This allows the system to manage power effectively, ensuring the controller remains available whenever needed. In order to maintain full functionality of an offloaded USB devices, several changes are made within the suspend flow of such devices: - skip usb_suspend_device() so that the port/hub are still active for USB transfers via offloaded path. - not suspending the endpoints which are used by USB interfaces marked with needs_remote_wakeup. Namely, skip usb_suspend_interface() and usb_hcd_flush_endpoint() on associated USB interfaces. This reserves a pending interrupt urb during system suspend for handling the interrupt transfer, which is necessary since remote wakeup doesn't apply in the offloaded USB devices when controller is still active. - not flushing the endpoints of actively offloaded USB devices. Given that the USB devices is used by another entity, unilaterally flush the endpoint might lead to unexpected behavior on another entity. - not suspending the xhci controller. This is done by skipping the suspend/resume callbacks in the xhci platform driver. Signed-off-by: Guan-Yu Lin Link: https://lore.kernel.org/r/20250911142051.90822-5-guanyulin@google.com Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250911142051.90822-5-guanyulin@google.com --- drivers/usb/core/driver.c | 50 +++++++++++++++++++++++++++++++----- drivers/usb/host/xhci-plat.c | 19 ++++++++++++++ drivers/usb/host/xhci-plat.h | 1 + 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 25358cf3e324..6418cfb0fb37 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1420,11 +1420,28 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) udev->state == USB_STATE_SUSPENDED) goto done; + if (msg.event == PM_EVENT_SUSPEND && usb_offload_check(udev)) { + dev_dbg(&udev->dev, "device offloaded, skip suspend.\n"); + udev->offload_at_suspend = 1; + } + /* Suspend all the interfaces and then udev itself */ if (udev->actconfig) { n = udev->actconfig->desc.bNumInterfaces; for (i = n - 1; i >= 0; --i) { intf = udev->actconfig->interface[i]; + /* + * Don't suspend interfaces with remote wakeup while + * the controller is active. This preserves pending + * interrupt urbs, allowing interrupt events to be + * handled during system suspend. + */ + if (udev->offload_at_suspend && + intf->needs_remote_wakeup) { + dev_dbg(&intf->dev, + "device offloaded, skip suspend.\n"); + continue; + } status = usb_suspend_interface(udev, intf, msg); /* Ignore errors during system sleep transitions */ @@ -1435,7 +1452,8 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) } } if (status == 0) { - status = usb_suspend_device(udev, msg); + if (!udev->offload_at_suspend) + status = usb_suspend_device(udev, msg); /* * Ignore errors from non-root-hub devices during @@ -1480,9 +1498,11 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) */ } else { udev->can_submit = 0; - for (i = 0; i < 16; ++i) { - usb_hcd_flush_endpoint(udev, udev->ep_out[i]); - usb_hcd_flush_endpoint(udev, udev->ep_in[i]); + if (!udev->offload_at_suspend) { + for (i = 0; i < 16; ++i) { + usb_hcd_flush_endpoint(udev, udev->ep_out[i]); + usb_hcd_flush_endpoint(udev, udev->ep_in[i]); + } } } @@ -1524,17 +1544,35 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg) udev->can_submit = 1; /* Resume the device */ - if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) - status = usb_resume_device(udev, msg); + if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) { + if (!udev->offload_at_suspend) + status = usb_resume_device(udev, msg); + else + dev_dbg(&udev->dev, + "device offloaded, skip resume.\n"); + } /* Resume the interfaces */ if (status == 0 && udev->actconfig) { for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { intf = udev->actconfig->interface[i]; + /* + * Interfaces with remote wakeup aren't suspended + * while the controller is active. This preserves + * pending interrupt urbs, allowing interrupt events + * to be handled during system suspend. + */ + if (udev->offload_at_suspend && + intf->needs_remote_wakeup) { + dev_dbg(&intf->dev, + "device offloaded, skip resume.\n"); + continue; + } usb_resume_interface(udev, intf, msg, udev->reset_resume); } } + udev->offload_at_suspend = 0; usb_mark_last_busy(udev); done: diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index df2e942ad5f7..3a56d8f94519 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "xhci.h" #include "xhci-plat.h" @@ -484,6 +485,15 @@ static int xhci_plat_suspend_common(struct device *dev) static int xhci_plat_suspend(struct device *dev) { + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (xhci_sideband_check(hcd)) { + priv->sideband_at_suspend = 1; + dev_dbg(dev, "sideband instance active, skip suspend.\n"); + return 0; + } + return xhci_plat_suspend_common(dev); } @@ -536,6 +546,15 @@ static int xhci_plat_resume_common(struct device *dev, bool power_lost) static int xhci_plat_resume(struct device *dev) { + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (priv->sideband_at_suspend) { + priv->sideband_at_suspend = 0; + dev_dbg(dev, "sideband instance active, skip resume.\n"); + return 0; + } + return xhci_plat_resume_common(dev, false); } diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h index fe4f95e690fa..2b32a93d2b76 100644 --- a/drivers/usb/host/xhci-plat.h +++ b/drivers/usb/host/xhci-plat.h @@ -16,6 +16,7 @@ struct xhci_plat_priv { const char *firmware_name; unsigned long long quirks; bool power_lost; + unsigned sideband_at_suspend:1; void (*plat_start)(struct usb_hcd *); int (*init_quirk)(struct usb_hcd *); int (*suspend_quirk)(struct usb_hcd *); From be5ae730ffa6fd774a00a4705c1e11e078b08ca1 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 14 Sep 2025 12:56:06 +0000 Subject: [PATCH 071/132] usb: typec: tipd: Clear interrupts first Right now the interrupt handler first reads all updated status registers and only then clears the interrupts. It's possible that a duplicate interrupt for a changed register or plug state comes in after the interrupts have been processed but before they have been cleared: * plug is inserted, TPS_REG_INT_PLUG_EVENT is set * TPS_REG_INT_EVENT1 is read * tps6598x_handle_plug_event() has run and registered the plug * plug is removed again, TPS_REG_INT_PLUG_EVENT is set (again) * TPS_REG_INT_CLEAR1 is written, TPS_REG_INT_PLUG_EVENT is cleared We then have no plug connected and no pending interrupt but the tipd core still thinks there is a plug. It's possible to trigger this with e.g. a slightly broken Type-C to USB A converter. Fix this by first clearing the interrupts and only then reading the updated registers. Fixes: 45188f27b3d0 ("usb: typec: tipd: Add support for Apple CD321X") Fixes: 0a4c005bd171 ("usb: typec: driver for TI TPS6598x USB Power Delivery controllers") Cc: stable@kernel.org Reviewed-by: Heikki Krogerus Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Link: https://lore.kernel.org/r/20250914-apple-usb3-tipd-v1-1-4e99c8649024@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index dcf141ada078..1c80296c3b27 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -545,24 +545,23 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) if (!event) goto err_unlock; + tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event); + if (!tps6598x_read_status(tps, &status)) - goto err_clear_ints; + goto err_unlock; if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) if (!tps6598x_read_power_status(tps)) - goto err_clear_ints; + goto err_unlock; if (event & APPLE_CD_REG_INT_DATA_STATUS_UPDATE) if (!tps6598x_read_data_status(tps)) - goto err_clear_ints; + goto err_unlock; /* Handle plug insert or removal */ if (event & APPLE_CD_REG_INT_PLUG_EVENT) tps6598x_handle_plug_event(tps, status); -err_clear_ints: - tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event); - err_unlock: mutex_unlock(&tps->lock); @@ -668,25 +667,24 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) if (!(event1[0] | event1[1] | event2[0] | event2[1])) goto err_unlock; + tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event1, intev_len); + tps6598x_block_write(tps, TPS_REG_INT_CLEAR2, event2, intev_len); + if (!tps6598x_read_status(tps, &status)) - goto err_clear_ints; + goto err_unlock; if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE) if (!tps6598x_read_power_status(tps)) - goto err_clear_ints; + goto err_unlock; if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE) if (!tps6598x_read_data_status(tps)) - goto err_clear_ints; + goto err_unlock; /* Handle plug insert or removal */ if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) tps6598x_handle_plug_event(tps, status); -err_clear_ints: - tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event1, intev_len); - tps6598x_block_write(tps, TPS_REG_INT_CLEAR2, event2, intev_len); - err_unlock: mutex_unlock(&tps->lock); From b3dddff502c5e049e43e3d0c43586de342b9e1d7 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 14 Sep 2025 12:56:07 +0000 Subject: [PATCH 072/132] usb: typec: tipd: Move initial irq mask to tipd_data Since the irq mask was originally added more tipd variants have been introduced and there's now struct tipd_data. Move the initial mask in there. Reviewed-by: Heikki Krogerus Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Link: https://lore.kernel.org/r/20250914-apple-usb3-tipd-v1-2-4e99c8649024@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 1c80296c3b27..6d8bcbc9cad8 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -112,6 +112,7 @@ struct tps6598x; struct tipd_data { irq_handler_t irq_handler; + u64 irq_mask1; int (*register_port)(struct tps6598x *tps, struct fwnode_handle *node); void (*trace_power_status)(u16 status); void (*trace_status)(u32 status); @@ -1298,7 +1299,6 @@ static int tps6598x_probe(struct i2c_client *client) u32 status; u32 vid; int ret; - u64 mask1; tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL); if (!tps) @@ -1337,16 +1337,6 @@ static int tps6598x_probe(struct i2c_client *client) if (ret) return ret; - /* CD321X chips have all interrupts masked initially */ - mask1 = APPLE_CD_REG_INT_POWER_STATUS_UPDATE | - APPLE_CD_REG_INT_DATA_STATUS_UPDATE | - APPLE_CD_REG_INT_PLUG_EVENT; - - } else { - /* Enable power status, data status and plug event interrupts */ - mask1 = TPS_REG_INT_POWER_STATUS_UPDATE | - TPS_REG_INT_DATA_STATUS_UPDATE | - TPS_REG_INT_PLUG_EVENT; } tps->data = i2c_get_match_data(client); @@ -1364,7 +1354,7 @@ static int tps6598x_probe(struct i2c_client *client) return ret; } - ret = tps6598x_write64(tps, TPS_REG_INT_MASK1, mask1); + ret = tps6598x_write64(tps, TPS_REG_INT_MASK1, tps->data->irq_mask1); if (ret) goto err_reset_controller; @@ -1527,6 +1517,9 @@ static const struct dev_pm_ops tps6598x_pm_ops = { static const struct tipd_data cd321x_data = { .irq_handler = cd321x_interrupt, + .irq_mask1 = APPLE_CD_REG_INT_POWER_STATUS_UPDATE | + APPLE_CD_REG_INT_DATA_STATUS_UPDATE | + APPLE_CD_REG_INT_PLUG_EVENT, .register_port = tps6598x_register_port, .trace_power_status = trace_tps6598x_power_status, .trace_status = trace_tps6598x_status, @@ -1536,6 +1529,9 @@ static const struct tipd_data cd321x_data = { static const struct tipd_data tps6598x_data = { .irq_handler = tps6598x_interrupt, + .irq_mask1 = TPS_REG_INT_POWER_STATUS_UPDATE | + TPS_REG_INT_DATA_STATUS_UPDATE | + TPS_REG_INT_PLUG_EVENT, .register_port = tps6598x_register_port, .trace_power_status = trace_tps6598x_power_status, .trace_status = trace_tps6598x_status, @@ -1546,6 +1542,9 @@ static const struct tipd_data tps6598x_data = { static const struct tipd_data tps25750_data = { .irq_handler = tps25750_interrupt, + .irq_mask1 = TPS_REG_INT_POWER_STATUS_UPDATE | + TPS_REG_INT_DATA_STATUS_UPDATE | + TPS_REG_INT_PLUG_EVENT, .register_port = tps25750_register_port, .trace_power_status = trace_tps25750_power_status, .trace_status = trace_tps25750_status, From ff175d85888e6bb4c41101493b8e74717842fb39 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 14 Sep 2025 12:56:08 +0000 Subject: [PATCH 073/132] usb: typec: tipd: Move switch_power_state to tipd_data When support for CD321x was originally added no other hardware variant was supported and there was no need for struct tipd_data. Now that it exists move the special case in there so that we can drop the of_device_is_compatible_check entirely. Reviewed-by: Heikki Krogerus Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Link: https://lore.kernel.org/r/20250914-apple-usb3-tipd-v1-3-4e99c8649024@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 6d8bcbc9cad8..4815c5c46283 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -118,6 +118,7 @@ struct tipd_data { void (*trace_status)(u32 status); int (*apply_patch)(struct tps6598x *tps); int (*init)(struct tps6598x *tps); + int (*switch_power_state)(struct tps6598x *tps, u8 target_state); int (*reset)(struct tps6598x *tps); }; @@ -1293,7 +1294,6 @@ tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) static int tps6598x_probe(struct i2c_client *client) { - struct device_node *np = client->dev.of_node; struct tps6598x *tps; struct fwnode_handle *fwnode; u32 status; @@ -1331,18 +1331,16 @@ static int tps6598x_probe(struct i2c_client *client) if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) tps->i2c_protocol = true; - if (np && of_device_is_compatible(np, "apple,cd321x")) { - /* Switch CD321X chips to the correct system power state */ - ret = cd321x_switch_power_state(tps, TPS_SYSTEM_POWER_STATE_S0); - if (ret) - return ret; - - } - tps->data = i2c_get_match_data(client); if (!tps->data) return -EINVAL; + if (tps->data->switch_power_state) { + ret = tps->data->switch_power_state(tps, TPS_SYSTEM_POWER_STATE_S0); + if (ret) + return ret; + } + /* Make sure the controller has application firmware running */ ret = tps6598x_check_mode(tps); if (ret < 0) @@ -1525,6 +1523,7 @@ static const struct tipd_data cd321x_data = { .trace_status = trace_tps6598x_status, .init = cd321x_init, .reset = cd321x_reset, + .switch_power_state = cd321x_switch_power_state, }; static const struct tipd_data tps6598x_data = { From 60e1ff66179ca59bf98ec64a849d0325c0db0f57 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 14 Sep 2025 12:56:09 +0000 Subject: [PATCH 074/132] usb: typec: tipd: Trace data status for CD321x correctly Some bits inside the CD321x TPS_DATA_STATUS register have a different function compared to the original tipd chip. Add these and introduce a separate trace function to show them correctly. Reviewed-by: Heikki Krogerus Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Link: https://lore.kernel.org/r/20250914-apple-usb3-tipd-v1-4-4e99c8649024@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 8 ++++++- drivers/usb/typec/tipd/tps6598x.h | 5 ++++ drivers/usb/typec/tipd/trace.h | 39 +++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 4815c5c46283..19d713937870 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -114,6 +114,7 @@ struct tipd_data { irq_handler_t irq_handler; u64 irq_mask1; int (*register_port)(struct tps6598x *tps, struct fwnode_handle *node); + void (*trace_data_status)(u32 status); void (*trace_power_status)(u16 status); void (*trace_status)(u32 status); int (*apply_patch)(struct tps6598x *tps); @@ -492,7 +493,9 @@ static bool tps6598x_read_data_status(struct tps6598x *tps) dev_err(tps->dev, "failed to read data status: %d\n", ret); return false; } - trace_tps6598x_data_status(data_status); + + if (tps->data->trace_data_status) + tps->data->trace_data_status(data_status); return true; } @@ -1519,6 +1522,7 @@ static const struct tipd_data cd321x_data = { APPLE_CD_REG_INT_DATA_STATUS_UPDATE | APPLE_CD_REG_INT_PLUG_EVENT, .register_port = tps6598x_register_port, + .trace_data_status = trace_cd321x_data_status, .trace_power_status = trace_tps6598x_power_status, .trace_status = trace_tps6598x_status, .init = cd321x_init, @@ -1532,6 +1536,7 @@ static const struct tipd_data tps6598x_data = { TPS_REG_INT_DATA_STATUS_UPDATE | TPS_REG_INT_PLUG_EVENT, .register_port = tps6598x_register_port, + .trace_data_status = trace_tps6598x_data_status, .trace_power_status = trace_tps6598x_power_status, .trace_status = trace_tps6598x_status, .apply_patch = tps6598x_apply_patch, @@ -1545,6 +1550,7 @@ static const struct tipd_data tps25750_data = { TPS_REG_INT_DATA_STATUS_UPDATE | TPS_REG_INT_PLUG_EVENT, .register_port = tps25750_register_port, + .trace_data_status = trace_tps6598x_data_status, .trace_power_status = trace_tps25750_power_status, .trace_status = trace_tps25750_status, .apply_patch = tps25750_apply_patch, diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index cecb8d11d239..03edbb77bbd6 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -197,6 +197,11 @@ #define TPS_DATA_STATUS_FORCE_LSX BIT(23) #define TPS_DATA_STATUS_POWER_MISMATCH BIT(24) +/* modified TPS_REG_DATA_STATUS bits for CD321x (and likely also TPS65987DDK) */ +#define CD321X_DATA_STATUS_HPD_IRQ BIT(14) +#define CD321X_DATA_STATUS_HPD_LEVEL BIT(15) +#define CD321X_DATA_STATUS_USB4_CONNECTION BIT(23) + #define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK GENMASK(11, 10) #define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT(x) \ TPS_FIELD_GET(TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK, (x)) diff --git a/drivers/usb/typec/tipd/trace.h b/drivers/usb/typec/tipd/trace.h index bea383f2db9d..e9e40425138a 100644 --- a/drivers/usb/typec/tipd/trace.h +++ b/drivers/usb/typec/tipd/trace.h @@ -217,6 +217,26 @@ { TPS_DATA_STATUS_FORCE_LSX, "FORCE_LSX" }, \ { TPS_DATA_STATUS_POWER_MISMATCH, "POWER_MISMATCH" }) +#define show_cd321x_data_status_flags(data_status) \ + __print_flags(data_status & TPS_DATA_STATUS_FLAGS_MASK, "|", \ + { TPS_DATA_STATUS_DATA_CONNECTION, "DATA_CONNECTION" }, \ + { TPS_DATA_STATUS_UPSIDE_DOWN, "DATA_UPSIDE_DOWN" }, \ + { TPS_DATA_STATUS_ACTIVE_CABLE, "ACTIVE_CABLE" }, \ + { TPS_DATA_STATUS_USB2_CONNECTION, "USB2_CONNECTION" }, \ + { TPS_DATA_STATUS_USB3_CONNECTION, "USB3_CONNECTION" }, \ + { TPS_DATA_STATUS_USB3_GEN2, "USB3_GEN2" }, \ + { TPS_DATA_STATUS_USB_DATA_ROLE, "USB_DATA_ROLE" }, \ + { TPS_DATA_STATUS_DP_CONNECTION, "DP_CONNECTION" }, \ + { TPS_DATA_STATUS_DP_SINK, "DP_SINK" }, \ + { CD321X_DATA_STATUS_HPD_IRQ, "HPD_IRQ" }, \ + { CD321X_DATA_STATUS_HPD_LEVEL, "HPD_LEVEL" }, \ + { TPS_DATA_STATUS_TBT_CONNECTION, "TBT_CONNECTION" }, \ + { TPS_DATA_STATUS_TBT_TYPE, "TBT_TYPE" }, \ + { TPS_DATA_STATUS_OPTICAL_CABLE, "OPTICAL_CABLE" }, \ + { TPS_DATA_STATUS_ACTIVE_LINK_TRAIN, "ACTIVE_LINK_TRAIN" }, \ + { CD321X_DATA_STATUS_USB4_CONNECTION, "USB4" }, \ + { TPS_DATA_STATUS_POWER_MISMATCH, "POWER_MISMATCH" }) + #define show_data_status_dp_pin_assignment(data_status) \ __print_symbolic(TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT(data_status), \ { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_E, "E" }, \ @@ -388,6 +408,25 @@ TRACE_EVENT(tps6598x_data_status, ) ); +TRACE_EVENT(cd321x_data_status, + TP_PROTO(u32 data_status), + TP_ARGS(data_status), + + TP_STRUCT__entry( + __field(u32, data_status) + ), + + TP_fast_assign( + __entry->data_status = data_status; + ), + + TP_printk("%s%s%s", + show_cd321x_data_status_flags(__entry->data_status), + __entry->data_status & TPS_DATA_STATUS_DP_CONNECTION ? ", DP pinout " : "", + maybe_show_data_status_dp_pin_assignment(__entry->data_status) + ) +); + #endif /* _TPS6598X_TRACE_H_ */ /* This part must be outside protection */ From 9f36fdfcf36445db4c886afdeff2d1d126f16c45 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 14 Sep 2025 12:56:10 +0000 Subject: [PATCH 075/132] usb: typec: tipd: Add cd321x struct with separate size We're about to add more fields to struct tps6598x which are only relevant for Apple's CD321x and to ensure that we don't waste memory everywhere for those add a separate struct for cd321x and prepare to allocate more space inside probe. Reviewed-by: Heikki Krogerus Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Link: https://lore.kernel.org/r/20250914-apple-usb3-tipd-v1-5-4e99c8649024@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 19d713937870..51b0f3be8b66 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -113,6 +113,7 @@ struct tps6598x; struct tipd_data { irq_handler_t irq_handler; u64 irq_mask1; + size_t tps_struct_size; int (*register_port)(struct tps6598x *tps, struct fwnode_handle *node); void (*trace_data_status)(u32 status); void (*trace_power_status)(u16 status); @@ -148,6 +149,10 @@ struct tps6598x { const struct tipd_data *data; }; +struct cd321x { + struct tps6598x tps; +}; + static enum power_supply_property tps6598x_psy_props[] = { POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_ONLINE, @@ -1297,18 +1302,24 @@ tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) static int tps6598x_probe(struct i2c_client *client) { + const struct tipd_data *data; struct tps6598x *tps; struct fwnode_handle *fwnode; u32 status; u32 vid; int ret; - tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL); + data = i2c_get_match_data(client); + if (!data) + return -EINVAL; + + tps = devm_kzalloc(&client->dev, data->tps_struct_size, GFP_KERNEL); if (!tps) return -ENOMEM; mutex_init(&tps->lock); tps->dev = &client->dev; + tps->data = data; tps->reset = devm_gpiod_get_optional(tps->dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(tps->reset)) @@ -1334,10 +1345,6 @@ static int tps6598x_probe(struct i2c_client *client) if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) tps->i2c_protocol = true; - tps->data = i2c_get_match_data(client); - if (!tps->data) - return -EINVAL; - if (tps->data->switch_power_state) { ret = tps->data->switch_power_state(tps, TPS_SYSTEM_POWER_STATE_S0); if (ret) @@ -1521,6 +1528,7 @@ static const struct tipd_data cd321x_data = { .irq_mask1 = APPLE_CD_REG_INT_POWER_STATUS_UPDATE | APPLE_CD_REG_INT_DATA_STATUS_UPDATE | APPLE_CD_REG_INT_PLUG_EVENT, + .tps_struct_size = sizeof(struct cd321x), .register_port = tps6598x_register_port, .trace_data_status = trace_cd321x_data_status, .trace_power_status = trace_tps6598x_power_status, @@ -1535,6 +1543,7 @@ static const struct tipd_data tps6598x_data = { .irq_mask1 = TPS_REG_INT_POWER_STATUS_UPDATE | TPS_REG_INT_DATA_STATUS_UPDATE | TPS_REG_INT_PLUG_EVENT, + .tps_struct_size = sizeof(struct tps6598x), .register_port = tps6598x_register_port, .trace_data_status = trace_tps6598x_data_status, .trace_power_status = trace_tps6598x_power_status, @@ -1549,6 +1558,7 @@ static const struct tipd_data tps25750_data = { .irq_mask1 = TPS_REG_INT_POWER_STATUS_UPDATE | TPS_REG_INT_DATA_STATUS_UPDATE | TPS_REG_INT_PLUG_EVENT, + .tps_struct_size = sizeof(struct tps6598x), .register_port = tps25750_register_port, .trace_data_status = trace_tps6598x_data_status, .trace_power_status = trace_tps25750_power_status, From 0b31c978935fb15863c0299a4c35ec6e89b492d2 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 14 Sep 2025 12:56:11 +0000 Subject: [PATCH 076/132] usb: typec: tipd: Read USB4, Thunderbolt and DisplayPort status for cd321x CD321x supports various alternate modes and stores information once these are entered into separate status registers. Read those when they are active when reading TPS_DATA_STATUS to prepare supporting these. Reviewed-by: Heikki Krogerus Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Link: https://lore.kernel.org/r/20250914-apple-usb3-tipd-v1-6-4e99c8649024@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 80 +++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 51b0f3be8b66..afd11b3e1ae5 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -35,14 +35,18 @@ #define TPS_REG_INT_MASK2 0x17 #define TPS_REG_INT_CLEAR1 0x18 #define TPS_REG_INT_CLEAR2 0x19 -#define TPS_REG_SYSTEM_POWER_STATE 0x20 #define TPS_REG_STATUS 0x1a +#define TPS_REG_SYSTEM_POWER_STATE 0x20 +#define TPS_REG_USB4_STATUS 0x24 #define TPS_REG_SYSTEM_CONF 0x28 #define TPS_REG_CTRL_CONF 0x29 #define TPS_REG_BOOT_STATUS 0x2D #define TPS_REG_POWER_STATUS 0x3f #define TPS_REG_PD_STATUS 0x40 #define TPS_REG_RX_IDENTITY_SOP 0x48 +#define TPS_REG_CF_VID_STATUS 0x5e +#define TPS_REG_DP_SID_STATUS 0x58 +#define TPS_REG_INTEL_VID_STATUS 0x59 #define TPS_REG_DATA_STATUS 0x5f #define TPS_REG_SLEEP_CONF 0x70 @@ -85,6 +89,31 @@ struct tps6598x_rx_identity_reg { struct usb_pd_identity identity; } __packed; +/* TPS_REG_USB4_STATUS */ +struct tps6598x_usb4_status_reg { + u8 mode_status; + __le32 eudo; + __le32 unknown; +} __packed; + +/* TPS_REG_DP_SID_STATUS */ +struct tps6598x_dp_sid_status_reg { + u8 mode_status; + __le32 status_tx; + __le32 status_rx; + __le32 configure; + __le32 mode_data; +} __packed; + +/* TPS_REG_INTEL_VID_STATUS */ +struct tps6598x_intel_vid_status_reg { + u8 mode_status; + __le32 attention_vdo; + __le16 enter_vdo; + __le16 device_mode; + __le16 cable_mode; +} __packed; + /* Standard Task return codes */ #define TPS_TASK_TIMEOUT 1 #define TPS_TASK_REJECTED 3 @@ -121,6 +150,7 @@ struct tipd_data { int (*apply_patch)(struct tps6598x *tps); int (*init)(struct tps6598x *tps); int (*switch_power_state)(struct tps6598x *tps, u8 target_state); + bool (*read_data_status)(struct tps6598x *tps); int (*reset)(struct tps6598x *tps); }; @@ -151,6 +181,10 @@ struct tps6598x { struct cd321x { struct tps6598x tps; + + struct tps6598x_dp_sid_status_reg dp_sid_status; + struct tps6598x_intel_vid_status_reg intel_vid_status; + struct tps6598x_usb4_status_reg usb4_status; }; static enum power_supply_property tps6598x_psy_props[] = { @@ -505,6 +539,41 @@ static bool tps6598x_read_data_status(struct tps6598x *tps) return true; } +static bool cd321x_read_data_status(struct tps6598x *tps) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + int ret; + + ret = tps6598x_read_data_status(tps); + if (ret < 0) + return false; + + if (tps->data_status & TPS_DATA_STATUS_DP_CONNECTION) { + ret = tps6598x_block_read(tps, TPS_REG_DP_SID_STATUS, + &cd321x->dp_sid_status, sizeof(cd321x->dp_sid_status)); + if (ret) + dev_err(tps->dev, "Failed to read DP SID Status: %d\n", + ret); + } + + if (tps->data_status & TPS_DATA_STATUS_TBT_CONNECTION) { + ret = tps6598x_block_read(tps, TPS_REG_INTEL_VID_STATUS, + &cd321x->intel_vid_status, sizeof(cd321x->intel_vid_status)); + if (ret) + dev_err(tps->dev, "Failed to read Intel VID Status: %d\n", ret); + } + + if (tps->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) { + ret = tps6598x_block_read(tps, TPS_REG_USB4_STATUS, + &cd321x->usb4_status, sizeof(cd321x->usb4_status)); + if (ret) + dev_err(tps->dev, + "Failed to read USB4 Status: %d\n", ret); + } + + return true; +} + static bool tps6598x_read_power_status(struct tps6598x *tps) { u16 pwr_status; @@ -565,7 +634,7 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) goto err_unlock; if (event & APPLE_CD_REG_INT_DATA_STATUS_UPDATE) - if (!tps6598x_read_data_status(tps)) + if (!tps->data->read_data_status(tps)) goto err_unlock; /* Handle plug insert or removal */ @@ -614,7 +683,7 @@ static irqreturn_t tps25750_interrupt(int irq, void *data) goto err_clear_ints; if (event[0] & TPS_REG_INT_DATA_STATUS_UPDATE) - if (!tps6598x_read_data_status(tps)) + if (!tps->data->read_data_status(tps)) goto err_clear_ints; /* @@ -688,7 +757,7 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) goto err_unlock; if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE) - if (!tps6598x_read_data_status(tps)) + if (!tps->data->read_data_status(tps)) goto err_unlock; /* Handle plug insert or removal */ @@ -1534,6 +1603,7 @@ static const struct tipd_data cd321x_data = { .trace_power_status = trace_tps6598x_power_status, .trace_status = trace_tps6598x_status, .init = cd321x_init, + .read_data_status = cd321x_read_data_status, .reset = cd321x_reset, .switch_power_state = cd321x_switch_power_state, }; @@ -1550,6 +1620,7 @@ static const struct tipd_data tps6598x_data = { .trace_status = trace_tps6598x_status, .apply_patch = tps6598x_apply_patch, .init = tps6598x_init, + .read_data_status = tps6598x_read_data_status, .reset = tps6598x_reset, }; @@ -1565,6 +1636,7 @@ static const struct tipd_data tps25750_data = { .trace_status = trace_tps25750_status, .apply_patch = tps25750_apply_patch, .init = tps25750_init, + .read_data_status = tps6598x_read_data_status, .reset = tps25750_reset, }; From 7b1d318506839ea8b71b258a3e48b1f87f69d1bb Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 14 Sep 2025 12:56:12 +0000 Subject: [PATCH 077/132] usb: typec: tipd: Register DisplayPort and Thunderbolt altmodes for cd321x Ports equipped with a CD321x are only found on Apple Silicon machines and always support DisplayPort, Thunderbolt and USB4. Register these port modes unconditionally. Reviewed-by: Heikki Krogerus Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Link: https://lore.kernel.org/r/20250914-apple-usb3-tipd-v1-7-4e99c8649024@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 85 +++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index afd11b3e1ae5..c7cf936e5a61 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -144,6 +146,7 @@ struct tipd_data { u64 irq_mask1; size_t tps_struct_size; int (*register_port)(struct tps6598x *tps, struct fwnode_handle *node); + void (*unregister_port)(struct tps6598x *tps); void (*trace_data_status)(u32 status); void (*trace_power_status)(u16 status); void (*trace_status)(u32 status); @@ -185,6 +188,9 @@ struct cd321x { struct tps6598x_dp_sid_status_reg dp_sid_status; struct tps6598x_intel_vid_status_reg intel_vid_status; struct tps6598x_usb4_status_reg usb4_status; + + struct typec_altmode *port_altmode_dp; + struct typec_altmode *port_altmode_tbt; }; static enum power_supply_property tps6598x_psy_props[] = { @@ -964,6 +970,76 @@ tps6598x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) return 0; } +static int cd321x_register_port_altmodes(struct cd321x *cd321x) +{ + struct typec_altmode_desc desc; + struct typec_altmode *amode; + + memset(&desc, 0, sizeof(desc)); + desc.svid = USB_TYPEC_DP_SID; + desc.mode = USB_TYPEC_DP_MODE; + desc.vdo = DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D)); + desc.vdo |= DP_CAP_DFP_D; + amode = typec_port_register_altmode(cd321x->tps.port, &desc); + if (IS_ERR(amode)) + return PTR_ERR(amode); + cd321x->port_altmode_dp = amode; + + memset(&desc, 0, sizeof(desc)); + desc.svid = USB_TYPEC_TBT_SID; + desc.mode = TYPEC_ANY_MODE; + amode = typec_port_register_altmode(cd321x->tps.port, &desc); + if (IS_ERR(amode)) { + typec_unregister_altmode(cd321x->port_altmode_dp); + cd321x->port_altmode_dp = NULL; + return PTR_ERR(amode); + } + cd321x->port_altmode_tbt = amode; + + return 0; +} + +static int +cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + int ret; + + ret = tps6598x_register_port(tps, fwnode); + if (ret) + return ret; + + ret = cd321x_register_port_altmodes(cd321x); + if (ret) + goto err_unregister_port; + + typec_set_mode(tps->port, TYPEC_STATE_SAFE); + + return 0; + +err_unregister_port: + typec_unregister_port(tps->port); + return ret; +} + +static void +tps6598x_unregister_port(struct tps6598x *tps) +{ + typec_unregister_port(tps->port); +} + +static void +cd321x_unregister_port(struct tps6598x *tps) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + + typec_unregister_altmode(cd321x->port_altmode_dp); + cd321x->port_altmode_dp = NULL; + typec_unregister_altmode(cd321x->port_altmode_tbt); + cd321x->port_altmode_tbt = NULL; + typec_unregister_port(tps->port); +} + static int tps_request_firmware(struct tps6598x *tps, const struct firmware **fw, const char **firmware_name) { @@ -1505,7 +1581,7 @@ static int tps6598x_probe(struct i2c_client *client) err_disconnect: tps6598x_disconnect(tps, 0); err_unregister_port: - typec_unregister_port(tps->port); + tps->data->unregister_port(tps); err_role_put: usb_role_switch_put(tps->role_sw); err_fwnode_put: @@ -1529,7 +1605,7 @@ static void tps6598x_remove(struct i2c_client *client) devm_free_irq(tps->dev, client->irq, tps); tps6598x_disconnect(tps, 0); - typec_unregister_port(tps->port); + tps->data->unregister_port(tps); usb_role_switch_put(tps->role_sw); /* Reset PD controller to remove any applied patch */ @@ -1598,7 +1674,8 @@ static const struct tipd_data cd321x_data = { APPLE_CD_REG_INT_DATA_STATUS_UPDATE | APPLE_CD_REG_INT_PLUG_EVENT, .tps_struct_size = sizeof(struct cd321x), - .register_port = tps6598x_register_port, + .register_port = cd321x_register_port, + .unregister_port = cd321x_unregister_port, .trace_data_status = trace_cd321x_data_status, .trace_power_status = trace_tps6598x_power_status, .trace_status = trace_tps6598x_status, @@ -1615,6 +1692,7 @@ static const struct tipd_data tps6598x_data = { TPS_REG_INT_PLUG_EVENT, .tps_struct_size = sizeof(struct tps6598x), .register_port = tps6598x_register_port, + .unregister_port = tps6598x_unregister_port, .trace_data_status = trace_tps6598x_data_status, .trace_power_status = trace_tps6598x_power_status, .trace_status = trace_tps6598x_status, @@ -1631,6 +1709,7 @@ static const struct tipd_data tps25750_data = { TPS_REG_INT_PLUG_EVENT, .tps_struct_size = sizeof(struct tps6598x), .register_port = tps25750_register_port, + .unregister_port = tps6598x_unregister_port, .trace_data_status = trace_tps6598x_data_status, .trace_power_status = trace_tps25750_power_status, .trace_status = trace_tps25750_status, From 36c791c1996db13d5042960aa5895f777239845b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 14 Sep 2025 12:56:13 +0000 Subject: [PATCH 078/132] usb: typec: tipd: Update partner identity when power status was updated Whenever the power status is changed make sure to also update the partner identity to be able to detect changes once de-bouncing and mode changes are added for CD321x. Signed-off-by: Hector Martin Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Link: https://lore.kernel.org/r/20250914-apple-usb3-tipd-v1-8-4e99c8649024@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index c7cf936e5a61..e16c6c07c72a 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -635,9 +635,16 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) if (!tps6598x_read_status(tps, &status)) goto err_unlock; - if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) + if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) { if (!tps6598x_read_power_status(tps)) goto err_unlock; + if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) { + if (tps6598x_read_partner_identity(tps)) { + dev_err(tps->dev, "failed to read partner identity\n"); + tps->partner_identity = (struct usb_pd_identity) {0}; + } + } + } if (event & APPLE_CD_REG_INT_DATA_STATUS_UPDATE) if (!tps->data->read_data_status(tps)) From 77ed2f4538da7356a1813e9d4e4c13de6e3d5a2a Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 14 Sep 2025 12:56:14 +0000 Subject: [PATCH 079/132] usb: typec: tipd: Use read_power_status function in probe We need the initial power status to be able to reliably detect connector changes once we introduce de-bouncing for CD321x next. read_power_status takes care of this and also forwards the status to the trace subsystem so let's use that instead of open-coding it inside probe. Signed-off-by: Hector Martin Reviewed-by: Heikki Krogerus Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Link: https://lore.kernel.org/r/20250914-apple-usb3-tipd-v1-9-4e99c8649024@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index e16c6c07c72a..f546f4ae563a 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -1549,11 +1549,8 @@ static int tps6598x_probe(struct i2c_client *client) goto err_role_put; if (status & TPS_STATUS_PLUG_PRESENT) { - ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &tps->pwr_status); - if (ret < 0) { - dev_err(tps->dev, "failed to read power status: %d\n", ret); + if (!tps6598x_read_power_status(tps)) goto err_unregister_port; - } ret = tps6598x_connect(tps, status); if (ret) dev_err(&client->dev, "failed to register partner\n"); From 04041fd7d6ec0bc5c4277711e17b43ffb45e30ba Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 14 Sep 2025 12:56:15 +0000 Subject: [PATCH 080/132] usb: typec: tipd: Read data status in probe and cache its value Just like for power status we also need to keep track of data status to be able to detect mode changes once we introduce de-bouncing for CD321x. Read it during probe and keep a cached copy of its value. Signed-off-by: Hector Martin Reviewed-by: Heikki Krogerus Reviewed-by: Neal Gompa Signed-off-by: Sven Peter Link: https://lore.kernel.org/r/20250914-apple-usb3-tipd-v1-10-4e99c8649024@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index f546f4ae563a..f347e5bc6254 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -176,6 +176,7 @@ struct tps6598x { int wakeup; u32 status; /* status reg */ + u32 data_status; u16 pwr_status; struct delayed_work wq_poll; @@ -538,6 +539,7 @@ static bool tps6598x_read_data_status(struct tps6598x *tps) dev_err(tps->dev, "failed to read data status: %d\n", ret); return false; } + tps->data_status = data_status; if (tps->data->trace_data_status) tps->data->trace_data_status(data_status); @@ -1551,6 +1553,8 @@ static int tps6598x_probe(struct i2c_client *client) if (status & TPS_STATUS_PLUG_PRESENT) { if (!tps6598x_read_power_status(tps)) goto err_unregister_port; + if (!tps->data->read_data_status(tps)) + goto err_unregister_port; ret = tps6598x_connect(tps, status); if (ret) dev_err(&client->dev, "failed to register partner\n"); From 82432bbfb9e83b7e81d04660fe129b99a29b2ac2 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 14 Sep 2025 12:56:16 +0000 Subject: [PATCH 081/132] usb: typec: tipd: Handle mode transitions for CD321x On Apple Silicon machines there is no control over which alt mode is chosen. The CD321x' firmware negotiates the target mode on its own and only lets the main CPU know after the mode has already been chosen. Especially after plugging a new cable in this can result to quick mode changes from e.g. power only -> USB3 only -> USB3+DisplayPort in a short time. It is not possile to influence this in any way and we also do not get direct access to the PDOs or VDOs exchanged via USB PD. Additionally, mode changes must be tightly synchronized between DWC3 and the Type C PHY and most mode changes require a full reset of DWC3 to make the port work correctly. This is all done synchronously from the role switch handler inside the DWC3 glue driver on these machines to avoid tripping any failsafes or watchdogs inside the Type-C PHY that may, in the worst case, reset the entire SoC. To be able to control all this we trigger the entire process in the correct order directly from the TIPD driver and de-bounce any mode changes to avoid tearing down and re-setting DWC3 back up multiple times any time a new connection is made. Signed-off-by: Hector Martin Reviewed-by: Heikki Krogerus Reviewed-by: Neal Gompa Co-developed-by: Sven Peter Signed-off-by: Sven Peter Reviewed-by: Janne Grunau Link: https://lore.kernel.org/r/20250914-apple-usb3-tipd-v1-11-4e99c8649024@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 290 +++++++++++++++++++++++++++++++++- 1 file changed, 286 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index f347e5bc6254..2b1049c9a6f3 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,9 @@ struct tps6598x_intel_vid_status_reg { #define TPS_TASK_TIMEOUT 1 #define TPS_TASK_REJECTED 3 +/* Debounce delay for mode changes, in milliseconds */ +#define CD321X_DEBOUNCE_DELAY_MS 500 + enum { TPS_MODE_APP, TPS_MODE_BOOT, @@ -145,6 +149,7 @@ struct tipd_data { irq_handler_t irq_handler; u64 irq_mask1; size_t tps_struct_size; + void (*remove)(struct tps6598x *tps); int (*register_port)(struct tps6598x *tps, struct fwnode_handle *node); void (*unregister_port)(struct tps6598x *tps); void (*trace_data_status)(u32 status); @@ -155,6 +160,7 @@ struct tipd_data { int (*switch_power_state)(struct tps6598x *tps, u8 target_state); bool (*read_data_status)(struct tps6598x *tps); int (*reset)(struct tps6598x *tps); + int (*connect)(struct tps6598x *tps, u32 status); }; struct tps6598x { @@ -183,6 +189,17 @@ struct tps6598x { const struct tipd_data *data; }; +struct cd321x_status { + u32 status; + u32 pwr_status; + u32 data_status; + u32 status_changed; + struct usb_pd_identity partner_identity; + struct tps6598x_dp_sid_status_reg dp_sid_status; + struct tps6598x_intel_vid_status_reg intel_vid_status; + struct tps6598x_usb4_status_reg usb4_status; +}; + struct cd321x { struct tps6598x tps; @@ -192,6 +209,13 @@ struct cd321x { struct typec_altmode *port_altmode_dp; struct typec_altmode *port_altmode_tbt; + + struct typec_mux *mux; + struct typec_mux_state state; + + struct cd321x_status update_status; + struct delayed_work update_work; + struct usb_pd_identity cur_partner_identity; }; static enum power_supply_property tps6598x_psy_props[] = { @@ -613,6 +637,233 @@ static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status) } } +static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status *st) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + + if (!(st->data_status & TPS_DATA_STATUS_DATA_CONNECTION)) { + if (cd321x->state.mode == TYPEC_STATE_SAFE) + return; + cd321x->state.alt = NULL; + cd321x->state.mode = TYPEC_STATE_SAFE; + cd321x->state.data = NULL; + typec_mux_set(cd321x->mux, &cd321x->state); + } else if (st->data_status & TPS_DATA_STATUS_DP_CONNECTION) { + struct typec_displayport_data dp_data; + unsigned long mode; + + switch (TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT(st->data_status)) { + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_A: + mode = TYPEC_DP_STATE_A; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_B: + mode = TYPEC_DP_STATE_B; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_C: + mode = TYPEC_DP_STATE_C; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_D: + mode = TYPEC_DP_STATE_D; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_E: + mode = TYPEC_DP_STATE_E; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_F: + mode = TYPEC_DP_STATE_F; + break; + default: + dev_err(tps->dev, "Invalid DP pin assignment\n"); + return; + } + + if (cd321x->state.alt == cd321x->port_altmode_dp && + cd321x->state.mode == mode) { + return; + } + + dp_data.status = le32_to_cpu(st->dp_sid_status.status_rx); + dp_data.conf = le32_to_cpu(st->dp_sid_status.configure); + cd321x->state.alt = cd321x->port_altmode_dp; + cd321x->state.data = &dp_data; + cd321x->state.mode = mode; + typec_mux_set(cd321x->mux, &cd321x->state); + } else if (st->data_status & TPS_DATA_STATUS_TBT_CONNECTION) { + struct typec_thunderbolt_data tbt_data; + + if (cd321x->state.alt == cd321x->port_altmode_tbt && + cd321x->state.mode == TYPEC_TBT_MODE) + return; + + tbt_data.cable_mode = le16_to_cpu(st->intel_vid_status.cable_mode); + tbt_data.device_mode = le16_to_cpu(st->intel_vid_status.device_mode); + tbt_data.enter_vdo = le16_to_cpu(st->intel_vid_status.enter_vdo); + cd321x->state.alt = cd321x->port_altmode_tbt; + cd321x->state.mode = TYPEC_TBT_MODE; + cd321x->state.data = &tbt_data; + typec_mux_set(cd321x->mux, &cd321x->state); + } else if (st->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) { + struct enter_usb_data eusb_data; + + if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_MODE_USB4) + return; + + eusb_data.eudo = le32_to_cpu(st->usb4_status.eudo); + eusb_data.active_link_training = + !!(st->data_status & TPS_DATA_STATUS_ACTIVE_LINK_TRAIN); + + cd321x->state.alt = NULL; + cd321x->state.data = &eusb_data; + cd321x->state.mode = TYPEC_MODE_USB4; + typec_mux_set(cd321x->mux, &cd321x->state); + } else { + if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_STATE_USB) + return; + cd321x->state.alt = NULL; + cd321x->state.mode = TYPEC_STATE_USB; + cd321x->state.data = NULL; + typec_mux_set(cd321x->mux, &cd321x->state); + } + + /* Clear data since it's no longer used after typec_mux_set and points to the stack */ + cd321x->state.data = NULL; +} + +static void cd321x_update_work(struct work_struct *work) +{ + struct cd321x *cd321x = container_of(to_delayed_work(work), + struct cd321x, update_work); + struct tps6598x *tps = &cd321x->tps; + struct cd321x_status st; + + guard(mutex)(&tps->lock); + + st = cd321x->update_status; + cd321x->update_status.status_changed = 0; + + bool old_connected = !!tps->partner; + bool new_connected = st.status & TPS_STATUS_PLUG_PRESENT; + bool was_disconnected = st.status_changed & TPS_STATUS_PLUG_PRESENT; + + bool usb_connection = st.data_status & + (TPS_DATA_STATUS_USB2_CONNECTION | TPS_DATA_STATUS_USB3_CONNECTION); + + enum usb_role old_role = usb_role_switch_get_role(tps->role_sw); + enum usb_role new_role = USB_ROLE_NONE; + enum typec_pwr_opmode pwr_opmode = TYPEC_PWR_MODE_USB; + enum typec_orientation orientation = TYPEC_ORIENTATION_NONE; + + if (usb_connection) { + if (tps->data_status & TPS_DATA_STATUS_USB_DATA_ROLE) + new_role = USB_ROLE_DEVICE; + else + new_role = USB_ROLE_HOST; + } + + if (new_connected) { + pwr_opmode = TPS_POWER_STATUS_PWROPMODE(st.pwr_status); + orientation = TPS_STATUS_TO_UPSIDE_DOWN(st.status) ? + TYPEC_ORIENTATION_REVERSE : TYPEC_ORIENTATION_NORMAL; + } + + bool is_pd = pwr_opmode == TYPEC_PWR_MODE_PD; + bool partner_changed = old_connected && new_connected && + (was_disconnected || + (is_pd && memcmp(&st.partner_identity, + &cd321x->cur_partner_identity, sizeof(struct usb_pd_identity)))); + + /* If we are switching from an active role, transition to USB_ROLE_NONE first */ + if (old_role != USB_ROLE_NONE && (new_role != old_role || was_disconnected)) + usb_role_switch_set_role(tps->role_sw, USB_ROLE_NONE); + + /* Process partner disconnection or change */ + if (!new_connected || partner_changed) { + if (!IS_ERR(tps->partner)) + typec_unregister_partner(tps->partner); + tps->partner = NULL; + } + + /* If there was a disconnection, set PHY to off */ + if (!new_connected || was_disconnected) { + cd321x->state.alt = NULL; + cd321x->state.mode = TYPEC_STATE_SAFE; + cd321x->state.data = NULL; + typec_set_mode(tps->port, TYPEC_STATE_SAFE); + } + + /* Update Type-C properties */ + typec_set_pwr_opmode(tps->port, pwr_opmode); + typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(st.status)); + typec_set_vconn_role(tps->port, TPS_STATUS_TO_TYPEC_VCONN(st.status)); + typec_set_orientation(tps->port, orientation); + typec_set_data_role(tps->port, TPS_STATUS_TO_TYPEC_DATAROLE(st.status)); + power_supply_changed(tps->psy); + + /* If the plug is disconnected, we are done */ + if (!new_connected) + return; + + /* Set up partner if we were previously disconnected (or changed). */ + if (!tps->partner) { + struct typec_partner_desc desc; + + desc.usb_pd = is_pd; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ + desc.identity = NULL; + + if (desc.usb_pd) + desc.identity = &st.partner_identity; + + tps->partner = typec_register_partner(tps->port, &desc); + if (IS_ERR(tps->partner)) + dev_warn(tps->dev, "%s: failed to register partnet\n", __func__); + + if (desc.identity) { + typec_partner_set_identity(tps->partner); + cd321x->cur_partner_identity = st.partner_identity; + } + } + + /* Update the TypeC MUX/PHY state */ + cd321x_typec_update_mode(tps, &st); + + /* Launch the USB role switch */ + usb_role_switch_set_role(tps->role_sw, new_role); + + power_supply_changed(tps->psy); +} + +static void cd321x_queue_status(struct cd321x *cd321x) +{ + cd321x->update_status.status_changed |= cd321x->update_status.status ^ cd321x->tps.status; + + cd321x->update_status.status = cd321x->tps.status; + cd321x->update_status.pwr_status = cd321x->tps.pwr_status; + cd321x->update_status.data_status = cd321x->tps.data_status; + + cd321x->update_status.partner_identity = cd321x->tps.partner_identity; + cd321x->update_status.dp_sid_status = cd321x->dp_sid_status; + cd321x->update_status.intel_vid_status = cd321x->intel_vid_status; + cd321x->update_status.usb4_status = cd321x->usb4_status; +} + +static int cd321x_connect(struct tps6598x *tps, u32 status) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + + tps->status = status; + cd321x_queue_status(cd321x); + + /* + * Cancel pending work if not already running, then requeue after CD321X_DEBOUNCE_DELAY_MS + * regardless since the work function will check for any plug or altmodes changes since + * its last run anyway. + */ + cancel_delayed_work(&cd321x->update_work); + schedule_delayed_work(&cd321x->update_work, msecs_to_jiffies(CD321X_DEBOUNCE_DELAY_MS)); + + return 0; +} + static irqreturn_t cd321x_interrupt(int irq, void *data) { struct tps6598x *tps = data; @@ -652,9 +903,8 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) if (!tps->data->read_data_status(tps)) goto err_unlock; - /* Handle plug insert or removal */ - if (event & APPLE_CD_REG_INT_PLUG_EVENT) - tps6598x_handle_plug_event(tps, status); + /* Can be called uncondtionally since it will check for any changes itself */ + cd321x_connect(tps, status); err_unlock: mutex_unlock(&tps->lock); @@ -1014,6 +1264,8 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) struct cd321x *cd321x = container_of(tps, struct cd321x, tps); int ret; + INIT_DELAYED_WORK(&cd321x->update_work, cd321x_update_work); + ret = tps6598x_register_port(tps, fwnode); if (ret) return ret; @@ -1022,10 +1274,24 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) if (ret) goto err_unregister_port; + cd321x->mux = fwnode_typec_mux_get(fwnode); + if (IS_ERR(cd321x->mux)) { + ret = PTR_ERR(cd321x->mux); + goto err_unregister_altmodes; + } + + cd321x->state.alt = NULL; + cd321x->state.mode = TYPEC_STATE_SAFE; + cd321x->state.data = NULL; typec_set_mode(tps->port, TYPEC_STATE_SAFE); return 0; +err_unregister_altmodes: + typec_unregister_altmode(cd321x->port_altmode_dp); + typec_unregister_altmode(cd321x->port_altmode_tbt); + cd321x->port_altmode_dp = NULL; + cd321x->port_altmode_tbt = NULL; err_unregister_port: typec_unregister_port(tps->port); return ret; @@ -1042,6 +1308,8 @@ cd321x_unregister_port(struct tps6598x *tps) { struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + typec_mux_put(cd321x->mux); + cd321x->mux = NULL; typec_unregister_altmode(cd321x->port_altmode_dp); cd321x->port_altmode_dp = NULL; typec_unregister_altmode(cd321x->port_altmode_tbt); @@ -1454,6 +1722,13 @@ tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) return 0; } +static void cd321x_remove(struct tps6598x *tps) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + + cancel_delayed_work_sync(&cd321x->update_work); +} + static int tps6598x_probe(struct i2c_client *client) { const struct tipd_data *data; @@ -1555,7 +1830,7 @@ static int tps6598x_probe(struct i2c_client *client) goto err_unregister_port; if (!tps->data->read_data_status(tps)) goto err_unregister_port; - ret = tps6598x_connect(tps, status); + ret = tps->data->connect(tps, status); if (ret) dev_err(&client->dev, "failed to register partner\n"); } @@ -1612,6 +1887,9 @@ static void tps6598x_remove(struct i2c_client *client) else devm_free_irq(tps->dev, client->irq, tps); + if (tps->data->remove) + tps->data->remove(tps); + tps6598x_disconnect(tps, 0); tps->data->unregister_port(tps); usb_role_switch_put(tps->role_sw); @@ -1682,6 +1960,7 @@ static const struct tipd_data cd321x_data = { APPLE_CD_REG_INT_DATA_STATUS_UPDATE | APPLE_CD_REG_INT_PLUG_EVENT, .tps_struct_size = sizeof(struct cd321x), + .remove = cd321x_remove, .register_port = cd321x_register_port, .unregister_port = cd321x_unregister_port, .trace_data_status = trace_cd321x_data_status, @@ -1691,6 +1970,7 @@ static const struct tipd_data cd321x_data = { .read_data_status = cd321x_read_data_status, .reset = cd321x_reset, .switch_power_state = cd321x_switch_power_state, + .connect = cd321x_connect, }; static const struct tipd_data tps6598x_data = { @@ -1708,6 +1988,7 @@ static const struct tipd_data tps6598x_data = { .init = tps6598x_init, .read_data_status = tps6598x_read_data_status, .reset = tps6598x_reset, + .connect = tps6598x_connect, }; static const struct tipd_data tps25750_data = { @@ -1725,6 +2006,7 @@ static const struct tipd_data tps25750_data = { .init = tps25750_init, .read_data_status = tps6598x_read_data_status, .reset = tps25750_reset, + .connect = tps6598x_connect, }; static const struct of_device_id tps6598x_of_match[] = { From 1fac4d003fd6895599053f8f046139108fba68b8 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:21 +0200 Subject: [PATCH 082/132] thunderbolt: Update acpi.c function documentation Make acpi.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/acpi.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c index d2a0054217da..45d1415871b4 100644 --- a/drivers/thunderbolt/acpi.c +++ b/drivers/thunderbolt/acpi.c @@ -86,7 +86,7 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, * @nhi ACPI node. For each reference a device link is added. The link * is automatically removed by the driver core. * - * Returns %true if at least one link was created. + * Returns %true if at least one link was created, %false otherwise. */ bool tb_acpi_add_links(struct tb_nhi *nhi) { @@ -113,8 +113,10 @@ bool tb_acpi_add_links(struct tb_nhi *nhi) /** * tb_acpi_is_native() - Did the platform grant native TBT/USB4 control * - * Returns %true if the platform granted OS native control over - * TBT/USB4. In this case software based connection manager can be used, + * Return: %true if the platform granted OS native control over + * TBT/USB4, %false otherwise. + * + * When returned %true, software based connection manager can be used, * otherwise there is firmware based connection manager running. */ bool tb_acpi_is_native(void) @@ -126,8 +128,8 @@ bool tb_acpi_is_native(void) /** * tb_acpi_may_tunnel_usb3() - Is USB3 tunneling allowed by the platform * - * When software based connection manager is used, this function - * returns %true if platform allows native USB3 tunneling. + * Return: %true if software based connection manager is used and + * platform allows native USB 3.x tunneling, %false otherwise. */ bool tb_acpi_may_tunnel_usb3(void) { @@ -139,8 +141,8 @@ bool tb_acpi_may_tunnel_usb3(void) /** * tb_acpi_may_tunnel_dp() - Is DisplayPort tunneling allowed by the platform * - * When software based connection manager is used, this function - * returns %true if platform allows native DP tunneling. + * Return: %true if software based connection manager is used and + * platform allows native DP tunneling, %false otherwise. */ bool tb_acpi_may_tunnel_dp(void) { @@ -152,8 +154,8 @@ bool tb_acpi_may_tunnel_dp(void) /** * tb_acpi_may_tunnel_pcie() - Is PCIe tunneling allowed by the platform * - * When software based connection manager is used, this function - * returns %true if platform allows native PCIe tunneling. + * Return: %true if software based connection manager is used and + * platform allows native PCIe tunneling, %false otherwise. */ bool tb_acpi_may_tunnel_pcie(void) { @@ -165,8 +167,8 @@ bool tb_acpi_may_tunnel_pcie(void) /** * tb_acpi_is_xdomain_allowed() - Are XDomain connections allowed * - * When software based connection manager is used, this function - * returns %true if platform allows XDomain connections. + * Return: %true if software based connection manager is used and + * platform allows XDomain tunneling, %false otherwise. */ bool tb_acpi_is_xdomain_allowed(void) { @@ -256,7 +258,7 @@ static int tb_acpi_retimer_set_power(struct tb_port *port, bool power) * * This should only be called if the USB4/TBT link is not up. * - * Returns %0 on success. + * Return: %0 on success, negative errno otherwise. */ int tb_acpi_power_on_retimers(struct tb_port *port) { @@ -270,7 +272,7 @@ int tb_acpi_power_on_retimers(struct tb_port *port) * This is the opposite of tb_acpi_power_on_retimers(). After returning * successfully the normal operations with the @port can continue. * - * Returns %0 on success. + * Return: %0 on success, negative errno otherwise. */ int tb_acpi_power_off_retimers(struct tb_port *port) { From c0a078d7bc008d27f06cfc6bb05d311158d4b3b9 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:22 +0200 Subject: [PATCH 083/132] thunderbolt: Update cap.c function documentation Make cap.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/cap.c | 49 +++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/drivers/thunderbolt/cap.c b/drivers/thunderbolt/cap.c index 8ecd610c62d5..4ab22d5291ac 100644 --- a/drivers/thunderbolt/cap.c +++ b/drivers/thunderbolt/cap.c @@ -64,10 +64,14 @@ static void tb_port_dummy_read(struct tb_port *port) * @port: Port to find the capability for * @offset: Previous capability offset (%0 for start) * - * Returns dword offset of the next capability in port config space - * capability list and returns it. Passing %0 returns the first entry in - * the capability list. If no next capability is found returns %0. In case - * of failure returns negative errno. + * Finds dword offset of the next capability in port config space + * capability list. When passed %0 in @offset parameter, first entry + * will be returned, if it exists. + * + * Return: + * * Double word offset of the first or next capability - On success. + * * %0 - If no next capability is found. + * * Negative errno - Another error occurred. */ int tb_port_next_cap(struct tb_port *port, unsigned int offset) { @@ -112,9 +116,10 @@ static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) * @port: Port to find the capability for * @cap: Capability to look * - * Returns offset to start of capability or %-ENOENT if no such - * capability was found. Negative errno is returned if there was an - * error. + * Return: + * * Offset to the start of capability - On success. + * * %-ENOENT - If no such capability was found. + * * Negative errno - Another error occurred. */ int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) { @@ -137,10 +142,14 @@ int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) * @sw: Switch to find the capability for * @offset: Previous capability offset (%0 for start) * - * Finds dword offset of the next capability in router config space - * capability list and returns it. Passing %0 returns the first entry in - * the capability list. If no next capability is found returns %0. In case - * of failure returns negative errno. + * Finds dword offset of the next capability in port config space + * capability list. When passed %0 in @offset parameter, first entry + * will be returned, if it exists. + * + * Return: + * * Double word offset of the first or next capability - On success. + * * %0 - If no next capability is found. + * * Negative errno - Another error occurred. */ int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset) { @@ -181,9 +190,10 @@ int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset) * @sw: Switch to find the capability for * @cap: Capability to look * - * Returns offset to start of capability or %-ENOENT if no such - * capability was found. Negative errno is returned if there was an - * error. + * Return: + * * Offset to the start of capability - On success. + * * %-ENOENT - If no such capability was found. + * * Negative errno - Another error occurred. */ int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) { @@ -213,10 +223,13 @@ int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) * @sw: Switch to find the capability for * @vsec: Vendor specific capability to look * - * Functions enumerates vendor specific capabilities (VSEC) of a switch - * and returns offset when capability matching @vsec is found. If no - * such capability is found returns %-ENOENT. In case of error returns - * negative errno. + * This function enumerates vendor specific capabilities (VSEC) of a + * switch and returns offset when capability matching @vsec is found. + * + * Return: + * * Offset of capability - On success. + * * %-ENOENT - If capability was not found. + * * Negative errno - Another error occurred. */ int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) { From 66cf14cc9fdf2a9747bdaf2a629e3a510d31a3de Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:23 +0200 Subject: [PATCH 084/132] thunderbolt: Update clx.c function documentation Make clx.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/clx.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/clx.c b/drivers/thunderbolt/clx.c index 787dfd1550e5..1637e79d988a 100644 --- a/drivers/thunderbolt/clx.c +++ b/drivers/thunderbolt/clx.c @@ -167,7 +167,8 @@ static int tb_port_clx(struct tb_port *port) * @port: USB4 port to check * @clx: Mask of CL states to check * - * Returns true if any of the given CL states is enabled for @port. + * Return: %true if any of the given CL states is enabled for @port, + * %false otherwise. */ bool tb_port_clx_is_enabled(struct tb_port *port, unsigned int clx) { @@ -177,6 +178,8 @@ bool tb_port_clx_is_enabled(struct tb_port *port, unsigned int clx) /** * tb_switch_clx_is_supported() - Is CLx supported on this type of router * @sw: The router to check CLx support for + * + * Return: %true if CLx is supported, %false otherwise. */ static bool tb_switch_clx_is_supported(const struct tb_switch *sw) { @@ -203,7 +206,7 @@ static bool tb_switch_clx_is_supported(const struct tb_switch *sw) * Can be called for any router. Initializes the current CL state by * reading it from the hardware. * - * Returns %0 in case of success and negative errno in case of failure. + * Return: %0 on success, negative errno otherwise. */ int tb_switch_clx_init(struct tb_switch *sw) { @@ -313,7 +316,7 @@ static bool validate_mask(unsigned int clx) * is not inter-domain link. The complete set of conditions is described in CM * Guide 1.0 section 8.1. * - * Returns %0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ int tb_switch_clx_enable(struct tb_switch *sw, unsigned int clx) { @@ -390,8 +393,7 @@ int tb_switch_clx_enable(struct tb_switch *sw, unsigned int clx) * Disables all CL states of the given router. Can be called on any * router and if the states were not enabled already does nothing. * - * Returns the CL states that were disabled or negative errno in case of - * failure. + * Return: CL states that were disabled or negative errno otherwise. */ int tb_switch_clx_disable(struct tb_switch *sw) { From 38f33b8e2cc2d3d3bae88dd5b4546e4f38cda3be Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:24 +0200 Subject: [PATCH 085/132] thunderbolt: Update ctl.c function documentation Make ctl.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/ctl.c | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 1db2e951b53f..f92175ee3841 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -82,6 +82,8 @@ static DEFINE_MUTEX(tb_cfg_request_lock); * * This is refcounted object so when you are done with this, call * tb_cfg_request_put() to it. + * + * Return: &struct tb_cfg_request on success, %NULL otherwise. */ struct tb_cfg_request *tb_cfg_request_alloc(void) { @@ -359,7 +361,7 @@ static void tb_ctl_tx_callback(struct tb_ring *ring, struct ring_frame *frame, * * len must be a multiple of four. * - * Return: Returns 0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ static int tb_ctl_tx(struct tb_ctl *ctl, const void *data, size_t len, enum tb_cfg_pkg_type type) @@ -539,6 +541,8 @@ static void tb_cfg_request_work(struct work_struct *work) * * This queues @req on the given control channel without waiting for it * to complete. When the request completes @callback is called. + * + * Return: %0 on success, negative errno otherwise. */ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, void (*callback)(void *), void *callback_data) @@ -605,6 +609,9 @@ static void tb_cfg_request_complete(void *data) * triggers the request is canceled before function returns. Note the * caller needs to make sure only one message for given switch is active * at a time. + * + * Return: &struct tb_cfg_result with non-zero @err field if error + * has occurred. */ struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl, struct tb_cfg_request *req, @@ -641,7 +648,7 @@ struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl, * * cb will be invoked once for every hot plug event. * - * Return: Returns a pointer on success or NULL on failure. + * Return: Pointer to &struct tb_ctl, %NULL on failure. */ struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int index, int timeout_msec, event_cb cb, void *cb_data) @@ -764,8 +771,9 @@ void tb_ctl_stop(struct tb_ctl *ctl) * @route: Router that originated the event * @error: Pointer to the notification package * - * Call this as response for non-plug notification to ack it. Returns - * %0 on success or an error code on failure. + * Call this as a response for non-plug notification to ack it. + * + * Return: %0 on success, negative errno otherwise. */ int tb_cfg_ack_notification(struct tb_ctl *ctl, u64 route, const struct cfg_error_pkg *error) @@ -827,8 +835,9 @@ int tb_cfg_ack_notification(struct tb_ctl *ctl, u64 route, * @port: Port where the hot plug/unplug happened * @unplug: Ack hot plug or unplug * - * Call this as response for hot plug/unplug event to ack it. - * Returns %0 on success or an error code on failure. + * Call this as a response for hot plug/unplug event to ack it. + * + * Return: %0 on success, negative errno otherwise. */ int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug) { @@ -895,6 +904,9 @@ static bool tb_cfg_copy(struct tb_cfg_request *req, const struct ctl_pkg *pkg) * If the switch at route is incorrectly configured then we will not receive a * reply (even though the switch will reset). The caller should check for * -ETIMEDOUT and attempt to reconfigure the switch. + * + * Return: &struct tb_cfg_result with non-zero @err field if error + * has occurred. */ struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route) { @@ -937,6 +949,9 @@ struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route) * @timeout_msec: Timeout in ms how long to wait for the response * * Reads from router config space without translating the possible error. + * + * Return: &struct tb_cfg_result with non-zero @err field if error + * has occurred. */ struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer, u64 route, u32 port, enum tb_cfg_space space, @@ -1008,6 +1023,9 @@ struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer, * @timeout_msec: Timeout in ms how long to wait for the response * * Writes to router config space without translating the possible error. + * + * Return: &struct tb_cfg_result with non-zero @err field if error + * has occurred. */ struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port, enum tb_cfg_space space, @@ -1150,8 +1168,7 @@ int tb_cfg_write(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port, * Reads the first dword from the switches TB_CFG_SWITCH config area and * returns the port number from which the reply originated. * - * Return: Returns the upstream port number on success or an error code on - * failure. + * Return: Upstream port number on success or negative error code on failure. */ int tb_cfg_get_upstream_port(struct tb_ctl *ctl, u64 route) { From 12cb68e48691ffb6700ef0d8dceae7b4739b8dea Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:25 +0200 Subject: [PATCH 086/132] thunderbolt: Add missing documentation in ctl.h tb_cfg_request struct Add missing @request field description in tb_cfg_request struct kernel-doc. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/ctl.h | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h index 7e08ca8f0895..db1646eb4fd0 100644 --- a/drivers/thunderbolt/ctl.h +++ b/drivers/thunderbolt/ctl.h @@ -54,6 +54,7 @@ struct ctl_pkg { * @kref: Reference count * @ctl: Pointer to the control channel structure. Only set when the * request is queued. + * @request: Request is stored here * @request_size: Size of the request packet (in bytes) * @request_type: Type of the request packet * @response: Response is stored here From a84be45d332ae69b1ed1ef82143d98936b14201d Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:26 +0200 Subject: [PATCH 087/132] thunderbolt: Update dma_port.c function documentation Make dma_port.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/dma_port.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/dma_port.c b/drivers/thunderbolt/dma_port.c index 9f20c7bbf0ce..dc8ea188a114 100644 --- a/drivers/thunderbolt/dma_port.c +++ b/drivers/thunderbolt/dma_port.c @@ -197,6 +197,8 @@ static int dma_find_port(struct tb_switch *sw) * * The DMA control port is functional also when the switch is in safe * mode. + * + * Return: &struct tb_dma_port on success, %NULL otherwise. */ struct tb_dma_port *dma_port_alloc(struct tb_switch *sw) { @@ -354,6 +356,8 @@ static int dma_port_flash_write_block(void *data, unsigned int dwaddress, * @address: Address relative to the start of active region * @buf: Buffer where the data is read * @size: Size of the buffer + * + * Return: %0 on success, negative errno otherwise. */ int dma_port_flash_read(struct tb_dma_port *dma, unsigned int address, void *buf, size_t size) @@ -372,6 +376,8 @@ int dma_port_flash_read(struct tb_dma_port *dma, unsigned int address, * Writes block of data to the non-active flash region of the switch. If * the address is given as %DMA_PORT_CSS_ADDRESS the block is written * using CSS command. + * + * Return: %0 on success, negative errno otherwise. */ int dma_port_flash_write(struct tb_dma_port *dma, unsigned int address, const void *buf, size_t size) @@ -393,6 +399,8 @@ int dma_port_flash_write(struct tb_dma_port *dma, unsigned int address, * dma_port_flash_update_auth_status() to get status of this command. * This is because if the switch in question is root switch the * thunderbolt host controller gets reset as well. + * + * Return: %0 on success, negative errno otherwise. */ int dma_port_flash_update_auth(struct tb_dma_port *dma) { @@ -410,12 +418,13 @@ int dma_port_flash_update_auth(struct tb_dma_port *dma) * @status: Status code of the operation * * The function checks if there is status available from the last update - * auth command. Returns %0 if there is no status and no further - * action is required. If there is status, %1 is returned instead and - * @status holds the failure code. + * auth command. * - * Negative return means there was an error reading status from the - * switch. + * Return: + * * %0 - If there is no status and no further action is required. + * * %1 - If there is some status. @status holds the failure code. + * * Negative errno - An error occurred when reading status from the + * switch. */ int dma_port_flash_update_auth_status(struct tb_dma_port *dma, u32 *status) { @@ -446,6 +455,8 @@ int dma_port_flash_update_auth_status(struct tb_dma_port *dma, u32 *status) * @dma: DMA control port * * Triggers power cycle to the switch. + * + * Return: %0 on success, negative errno otherwise. */ int dma_port_power_cycle(struct tb_dma_port *dma) { From 250afc7f396d456a3458cc9160cf44990474c644 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:27 +0200 Subject: [PATCH 088/132] thunderbolt: Update domain.c function documentation Make domain.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/domain.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 5272c255e046..83defc915d33 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -369,7 +369,7 @@ static bool tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type, * Call tb_domain_put() to release the domain before it has been added * to the system. * - * Return: allocated domain structure on %NULL in case of error + * Return: Pointer to &struct tb or %NULL in case of error. */ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize) { @@ -431,7 +431,7 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize * and release the domain after this function has been called, call * tb_domain_remove(). * - * Return: %0 in case of success and negative errno in case of error + * Return: %0 on success, negative errno otherwise. */ int tb_domain_add(struct tb *tb, bool reset) { @@ -519,6 +519,8 @@ void tb_domain_remove(struct tb *tb) * @tb: Domain to suspend * * Suspends all devices in the domain and stops the control channel. + * + * Return: %0 on success, negative errno otherwise. */ int tb_domain_suspend_noirq(struct tb *tb) { @@ -545,6 +547,8 @@ int tb_domain_suspend_noirq(struct tb *tb) * * Re-starts the control channel, and resumes all devices connected to * the domain. + * + * Return: %0 on success, negative errno otherwise. */ int tb_domain_resume_noirq(struct tb *tb) { @@ -644,6 +648,8 @@ int tb_domain_disapprove_switch(struct tb *tb, struct tb_switch *sw) * This will approve switch by connection manager specific means. In * case of success the connection manager will create PCIe tunnel from * parent to @sw. + * + * Return: %0 on success, negative errno otherwise. */ int tb_domain_approve_switch(struct tb *tb, struct tb_switch *sw) { @@ -742,7 +748,7 @@ int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw) * This needs to be called in preparation for NVM upgrade of the host * controller. Makes sure all PCIe paths are disconnected. * - * Return %0 on success and negative errno in case of error. + * Return: %0 on success and negative errno in case of error. */ int tb_domain_disconnect_pcie_paths(struct tb *tb) { @@ -764,9 +770,11 @@ int tb_domain_disconnect_pcie_paths(struct tb *tb) * Calls connection manager specific method to enable DMA paths to the * XDomain in question. * - * Return: 0% in case of success and negative errno otherwise. In - * particular returns %-ENOTSUPP if the connection manager - * implementation does not support XDomains. + * Return: + * * %0 - On success. + * * %-ENOTSUPP - If the connection manager implementation does not support + * XDomains. + * * Negative errno - An error occurred. */ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, @@ -791,9 +799,11 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, * Calls connection manager specific method to disconnect DMA paths to * the XDomain in question. * - * Return: 0% in case of success and negative errno otherwise. In - * particular returns %-ENOTSUPP if the connection manager - * implementation does not support XDomains. + * Return: + * * %0 - On success. + * * %-ENOTSUPP - If the connection manager implementation does not support + * XDomains. + * * Negative errno - An error occurred. */ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, From 728ab0e4a0ca78f550ca59d9267e0b2835f523fa Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:28 +0200 Subject: [PATCH 089/132] thunderbolt: Update eeprom.c function documentation Make eeprom.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/eeprom.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index e66183a72cf9..1af65fece495 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -298,6 +298,8 @@ struct tb_drom_entry_desc { * * Does not use the cached copy in sw->drom. Used during resume to check switch * identity. + * + * Return: %0 on success, negative errno otherwise. */ int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid) { @@ -709,7 +711,7 @@ static int tb_drom_device_read(struct tb_switch *sw) * populates the fields in @sw accordingly. Can be called for any router * generation. * - * Returns %0 in case of success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_drom_read(struct tb_switch *sw) { From 4815b7548cf669b402919d592583a1ad00de9305 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:29 +0200 Subject: [PATCH 090/132] thunderbolt: Update lc.c function documentation Make lc.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/lc.c | 58 +++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/drivers/thunderbolt/lc.c b/drivers/thunderbolt/lc.c index 63cb4b6afb71..0891d51ac2e9 100644 --- a/drivers/thunderbolt/lc.c +++ b/drivers/thunderbolt/lc.c @@ -14,6 +14,8 @@ * tb_lc_read_uuid() - Read switch UUID from link controller common register * @sw: Switch whose UUID is read * @uuid: UUID is placed here + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_read_uuid(struct tb_switch *sw, u32 *uuid) { @@ -52,9 +54,10 @@ static int find_port_lc_cap(struct tb_port *port) * @port: Port that is reset * * Triggers downstream port reset through link controller registers. - * Returns %0 in case of success negative errno otherwise. Only supports - * non-USB4 routers with link controller (that's Thunderbolt 2 and - * Thunderbolt 3). + * Only supports non-USB4 routers with link controller (that's + * Thunderbolt 2 and Thunderbolt 3). + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_reset_port(struct tb_port *port) { @@ -132,6 +135,8 @@ static int tb_lc_set_port_configured(struct tb_port *port, bool configured) * @port: Port that is set as configured * * Sets the port configured for power management purposes. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_configure_port(struct tb_port *port) { @@ -143,6 +148,8 @@ int tb_lc_configure_port(struct tb_port *port) * @port: Port that is set as configured * * Sets the port unconfigured for power management purposes. + * + * Return: %0 on success, negative errno otherwise. */ void tb_lc_unconfigure_port(struct tb_port *port) { @@ -184,8 +191,10 @@ static int tb_lc_set_xdomain_configured(struct tb_port *port, bool configure) * tb_lc_configure_xdomain() - Inform LC that the link is XDomain * @port: Switch downstream port connected to another host * - * Sets the lane configured for XDomain accordingly so that the LC knows - * about this. Returns %0 in success and negative errno in failure. + * Sets the lane configured for XDomain accordingly so that LC knows + * about this. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_configure_xdomain(struct tb_port *port) { @@ -211,7 +220,7 @@ void tb_lc_unconfigure_xdomain(struct tb_port *port) * sleep. Should be called for those downstream lane adapters that were * not connected (tb_lc_configure_port() was not called) before sleep. * - * Returns %0 in success and negative errno in case of failure. + * Return: %0 on success, negative errno otherwise. */ int tb_lc_start_lane_initialization(struct tb_port *port) { @@ -244,6 +253,8 @@ int tb_lc_start_lane_initialization(struct tb_port *port) * * TB_LC_LINK_ATTR_CPS bit reflects if the link supports CLx including * active cables (if connected on the link). + * + * Return: %true if CLx is supported, %false otherwise. */ bool tb_lc_is_clx_supported(struct tb_port *port) { @@ -266,7 +277,8 @@ bool tb_lc_is_clx_supported(struct tb_port *port) * tb_lc_is_usb_plugged() - Is there USB device connected to port * @port: Device router lane 0 adapter * - * Returns true if the @port has USB type-C device connected. + * Return: %true if the @port has USB Type-C device connected, %false + * otherwise. */ bool tb_lc_is_usb_plugged(struct tb_port *port) { @@ -292,7 +304,8 @@ bool tb_lc_is_usb_plugged(struct tb_port *port) * tb_lc_is_xhci_connected() - Is the internal xHCI connected * @port: Device router lane 0 adapter * - * Returns true if the internal xHCI has been connected to @port. + * Return: %true if the internal xHCI has been connected to + * @port, %false otherwise. */ bool tb_lc_is_xhci_connected(struct tb_port *port) { @@ -343,9 +356,10 @@ static int __tb_lc_xhci_connect(struct tb_port *port, bool connect) * tb_lc_xhci_connect() - Connect internal xHCI * @port: Device router lane 0 adapter * - * Tells LC to connect the internal xHCI to @port. Returns %0 on success - * and negative errno in case of failure. Can be called for Thunderbolt 3 - * routers only. + * Tells LC to connect the internal xHCI to @port. Can be called for + * Thunderbolt 3 routers only. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_xhci_connect(struct tb_port *port) { @@ -408,6 +422,8 @@ static int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset, * @flags: Wakeup flags (%0 to disable) * * For each LC sets wake bits accordingly. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags) { @@ -447,6 +463,8 @@ int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags) * * Let the switch link controllers know that the switch is going to * sleep. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_set_sleep(struct tb_switch *sw) { @@ -491,6 +509,8 @@ int tb_lc_set_sleep(struct tb_switch *sw) * * Checks whether conditions for lane bonding from parent to @sw are * possible. + * + * Return: %true if lane bonding is possible, %false otherwise. */ bool tb_lc_lane_bonding_possible(struct tb_switch *sw) { @@ -562,6 +582,8 @@ static int tb_lc_dp_sink_available(struct tb_switch *sw, int sink) * * Queries through LC SNK_ALLOCATION registers whether DP sink is available * for the given DP IN port or not. + * + * Return: %true if DP sink is available, %false otherwise. */ bool tb_lc_dp_sink_query(struct tb_switch *sw, struct tb_port *in) { @@ -586,10 +608,12 @@ bool tb_lc_dp_sink_query(struct tb_switch *sw, struct tb_port *in) * @sw: Switch whose DP sink is allocated * @in: DP IN port the DP sink is allocated for * - * Allocate DP sink for @in via LC SNK_ALLOCATION registers. If the - * resource is available and allocation is successful returns %0. In all - * other cases returs negative errno. In particular %-EBUSY is returned if - * the resource was not available. + * Allocate DP sink for @in via LC SNK_ALLOCATION registers. + * + * Return: + * * %0 - If the resource is available and allocation is successful. + * * %-EBUSY - If resource is not available. + * * Negative errno - Another error occurred. */ int tb_lc_dp_sink_alloc(struct tb_switch *sw, struct tb_port *in) { @@ -637,6 +661,8 @@ int tb_lc_dp_sink_alloc(struct tb_switch *sw, struct tb_port *in) * @in: DP IN port whose DP sink is de-allocated * * De-allocate DP sink from @in using LC SNK_ALLOCATION registers. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_dp_sink_dealloc(struct tb_switch *sw, struct tb_port *in) { @@ -680,6 +706,8 @@ int tb_lc_dp_sink_dealloc(struct tb_switch *sw, struct tb_port *in) * * This is useful to let authentication cycle pass even without * a Thunderbolt link present. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_force_power(struct tb_switch *sw) { From a38523805007a7498a4cbc56239ffb7709826179 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:30 +0200 Subject: [PATCH 091/132] thunderbolt: Update nhi.c function documentation Make nhi.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 8a38b05d25c5..5f63f9b9cf40 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -344,8 +344,10 @@ EXPORT_SYMBOL_GPL(__tb_ring_enqueue); * * This function can be called when @start_poll callback of the @ring * has been called. It will read one completed frame from the ring and - * return it to the caller. Returns %NULL if there is no more completed - * frames. + * return it to the caller. + * + * Return: Pointer to &struct ring_frame, %NULL if there is no more + * completed frames. */ struct ring_frame *tb_ring_poll(struct tb_ring *ring) { @@ -640,6 +642,8 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, * @hop: HopID (ring) to allocate * @size: Number of entries in the ring * @flags: Flags for the ring + * + * Return: Pointer to &struct tb_ring, %NULL otherwise. */ struct tb_ring *tb_ring_alloc_tx(struct tb_nhi *nhi, int hop, int size, unsigned int flags) @@ -661,6 +665,8 @@ EXPORT_SYMBOL_GPL(tb_ring_alloc_tx); * interrupt is triggered and masked, instead of callback * in each Rx frame. * @poll_data: Optional data passed to @start_poll + * + * Return: Pointer to &struct tb_ring, %NULL otherwise. */ struct tb_ring *tb_ring_alloc_rx(struct tb_nhi *nhi, int hop, int size, unsigned int flags, int e2e_tx_hop, @@ -854,8 +860,9 @@ EXPORT_SYMBOL_GPL(tb_ring_free); * @cmd: Command to send * @data: Data to be send with the command * - * Sends mailbox command to the firmware running on NHI. Returns %0 in - * case of success and negative errno in case of failure. + * Sends mailbox command to the firmware running on NHI. + * + * Return: %0 on success, negative errno otherwise. */ int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data) { @@ -891,6 +898,8 @@ int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data) * * The function reads current firmware operation mode using NHI mailbox * registers and returns it to the caller. + * + * Return: &enum nhi_fw_mode. */ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi) { From bbbca9bfd1720d5eaeede878d63a171e34ea4b9b Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:31 +0200 Subject: [PATCH 092/132] thunderbolt: Add missing documentation in nhi_regs.h ring_desc structure Add missing description of fields in ring_desc struct found in "nhi_regs.h". No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi_regs.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h index 297a3e440648..cf5222bee971 100644 --- a/drivers/thunderbolt/nhi_regs.h +++ b/drivers/thunderbolt/nhi_regs.h @@ -21,6 +21,12 @@ enum ring_flags { /** * struct ring_desc - TX/RX ring entry + * @phys: DMA mapped address of the frame + * @length: Size of the ring + * @eof: End of frame protocol defined field + * @sof: Start of frame protocol defined field + * @flags: Ring descriptor flags + * @time: Fill with zero * * For TX set length/eof/sof. * For RX length/eof/sof are set by the NHI. From fe83a27383ca0a95022f5b5807e8516d4d7554fe Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:32 +0200 Subject: [PATCH 093/132] thunderbolt: Update nvm.c function documentation Make nvm.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nvm.c | 42 ++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/drivers/thunderbolt/nvm.c b/drivers/thunderbolt/nvm.c index da11c8112e29..6901058b7ac0 100644 --- a/drivers/thunderbolt/nvm.c +++ b/drivers/thunderbolt/nvm.c @@ -278,9 +278,13 @@ static const struct tb_nvm_vendor retimer_nvm_vendors[] = { * tb_nvm_alloc() - Allocate new NVM structure * @dev: Device owning the NVM * - * Allocates new NVM structure with unique @id and returns it. In case - * of error returns ERR_PTR(). Specifically returns %-EOPNOTSUPP if the - * NVM format of the @dev is not known by the kernel. + * Allocates new NVM structure with unique @id and returns it. + * + * Return: + * * Pointer to &struct tb_nvm - On success. + * * %-EOPNOTSUPP - If the NVM format of the @dev is not known by the + * kernel. + * * %ERR_PTR - In case of failure. */ struct tb_nvm *tb_nvm_alloc(struct device *dev) { @@ -347,9 +351,10 @@ struct tb_nvm *tb_nvm_alloc(struct device *dev) * tb_nvm_read_version() - Read and populate NVM version * @nvm: NVM structure * - * Uses vendor specific means to read out and fill in the existing - * active NVM version. Returns %0 in case of success and negative errno - * otherwise. + * Uses vendor specific means to read and fill out the existing + * active NVM version. + * + * Return: %0 on success, negative errno otherwise. */ int tb_nvm_read_version(struct tb_nvm *nvm) { @@ -365,12 +370,11 @@ int tb_nvm_read_version(struct tb_nvm *nvm) * tb_nvm_validate() - Validate new NVM image * @nvm: NVM structure * - * Runs vendor specific validation over the new NVM image and if all - * checks pass returns %0. As side effect updates @nvm->buf_data_start - * and @nvm->buf_data_size fields to match the actual data to be written - * to the NVM. + * Runs vendor specific validation over the new NVM image. As a + * side effect, updates @nvm->buf_data_start and @nvm->buf_data_size + * fields to match the actual data to be written to the NVM. * - * If the validation does not pass then returns negative errno. + * Return: %0 on successful validation, negative errno otherwise. */ int tb_nvm_validate(struct tb_nvm *nvm) { @@ -405,7 +409,7 @@ int tb_nvm_validate(struct tb_nvm *nvm) * the image, this function does that. Can be called even if the device * does not need this. * - * Returns %0 in case of success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_nvm_write_headers(struct tb_nvm *nvm) { @@ -423,7 +427,8 @@ int tb_nvm_write_headers(struct tb_nvm *nvm) * Registers new active NVmem device for @nvm. The @reg_read is called * directly from NVMem so it must handle possible concurrent access if * needed. The first parameter passed to @reg_read is @nvm structure. - * Returns %0 in success and negative errno otherwise. + * + * Return: %0 on success, negative errno otherwise. */ int tb_nvm_add_active(struct tb_nvm *nvm, nvmem_reg_read_t reg_read) { @@ -461,6 +466,11 @@ int tb_nvm_add_active(struct tb_nvm *nvm, nvmem_reg_read_t reg_read) * Helper function to cache the new NVM image before it is actually * written to the flash. Copies @bytes from @val to @nvm->buf starting * from @offset. + * + * Return: + * * %0 - On success. + * * %-ENOMEM - If buffer allocation failed. + * * Negative errno - Another error occurred. */ int tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val, size_t bytes) @@ -488,7 +498,7 @@ int tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val, * needed. The first parameter passed to @reg_write is @nvm structure. * The size of the NVMem device is set to %NVM_MAX_SIZE. * - * Returns %0 in success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_nvm_add_non_active(struct tb_nvm *nvm, nvmem_reg_write_t reg_write) { @@ -545,7 +555,7 @@ void tb_nvm_free(struct tb_nvm *nvm) * This is a generic function that reads data from NVM or NVM like * device. * - * Returns %0 on success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_nvm_read_data(unsigned int address, void *buf, size_t size, unsigned int retries, read_block_fn read_block, @@ -592,7 +602,7 @@ int tb_nvm_read_data(unsigned int address, void *buf, size_t size, * * This is generic function that writes data to NVM or NVM like device. * - * Returns %0 on success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_nvm_write_data(unsigned int address, const void *buf, size_t size, unsigned int retries, write_block_fn write_block, From a6e3f939ada8c4502bb9264adce106f5f2c9d51d Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:33 +0200 Subject: [PATCH 094/132] thunderbolt: Update path.c function documentation Make path.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/path.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c index e1a5f6e3d0b6..f9b11dadfbdd 100644 --- a/drivers/thunderbolt/path.c +++ b/drivers/thunderbolt/path.c @@ -96,7 +96,7 @@ static int tb_path_find_src_hopid(struct tb_port *src, * that the @dst port is the expected one. If it is not, the path can be * cleaned up by calling tb_path_deactivate() before tb_path_free(). * - * Return: Discovered path on success, %NULL in case of failure + * Return: Pointer to &struct tb_path, %NULL in case of failure. */ struct tb_path *tb_path_discover(struct tb_port *src, int src_hopid, struct tb_port *dst, int dst_hopid, @@ -233,7 +233,7 @@ struct tb_path *tb_path_discover(struct tb_port *src, int src_hopid, * links on the path, prioritizes using @link_nr but takes into account * that the lanes may be bonded. * - * Return: Returns a tb_path on success or NULL on failure. + * Return: Pointer to &struct tb_path, %NULL in case of failure. */ struct tb_path *tb_path_alloc(struct tb *tb, struct tb_port *src, int src_hopid, struct tb_port *dst, int dst_hopid, int link_nr, @@ -452,7 +452,9 @@ static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index, * @hop_index: HopID of the path to be cleared * * This deactivates or clears a single path config space entry at - * @hop_index. Returns %0 in success and negative errno otherwise. + * @hop_index. + * + * Return: %0 on success, negative errno otherwise. */ int tb_path_deactivate_hop(struct tb_port *port, int hop_index) { @@ -498,7 +500,7 @@ void tb_path_deactivate(struct tb_path *path) * Activate a path starting with the last hop and iterating backwards. The * caller must fill path->hops before calling tb_path_activate(). * - * Return: Returns 0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ int tb_path_activate(struct tb_path *path) { @@ -592,7 +594,7 @@ int tb_path_activate(struct tb_path *path) * tb_path_is_invalid() - check whether any ports on the path are invalid * @path: Path to check * - * Return: Returns true if the path is invalid, false otherwise. + * Return: %true if the path is invalid, %false otherwise. */ bool tb_path_is_invalid(struct tb_path *path) { @@ -613,6 +615,8 @@ bool tb_path_is_invalid(struct tb_path *path) * * Goes over all hops on path and checks if @port is any of them. * Direction does not matter. + * + * Return: %true if port is on the path, %false otherwise. */ bool tb_path_port_on_path(const struct tb_path *path, const struct tb_port *port) { From d015642ad36d78e6eba12d8ab96cea6fd4602b49 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:34 +0200 Subject: [PATCH 095/132] thunderbolt: Update property.c function documentation Make property.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/property.c | 38 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/drivers/thunderbolt/property.c b/drivers/thunderbolt/property.c index dc555cda98e6..31aa0516932a 100644 --- a/drivers/thunderbolt/property.c +++ b/drivers/thunderbolt/property.c @@ -211,11 +211,13 @@ static struct tb_property_dir *__tb_property_parse_dir(const u32 *block, * * This function parses the XDomain properties data block into format that * can be traversed using the helper functions provided by this module. - * Upon success returns the parsed directory. In case of error returns - * %NULL. The resulting &struct tb_property_dir needs to be released by + * + * The resulting &struct tb_property_dir needs to be released by * calling tb_property_free_dir() when not needed anymore. * * The @block is expected to be root directory. + * + * Return: Pointer to &struct tb_property_dir, %NULL in case of failure. */ struct tb_property_dir *tb_property_parse_dir(const u32 *block, size_t block_len) @@ -238,6 +240,8 @@ struct tb_property_dir *tb_property_parse_dir(const u32 *block, * * Creates new, empty property directory. If @uuid is %NULL then the * directory is assumed to be root directory. + * + * Return: Pointer to &struct tb_property_dir, %NULL in case of failure. */ struct tb_property_dir *tb_property_create_dir(const uuid_t *uuid) { @@ -481,9 +485,11 @@ static ssize_t __tb_property_format_dir(const struct tb_property_dir *dir, * @block_len: Length of the property block * * This function formats the directory to the packed format that can be - * then send over the thunderbolt fabric to receiving host. Returns %0 in - * case of success and negative errno on faulure. Passing %NULL in @block - * returns number of entries the block takes. + * then sent over the thunderbolt fabric to receiving host. + * + * Passing %NULL in @block returns number of entries the block takes. + * + * Return: %0 on success, negative errno otherwise. */ ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block, size_t block_len) @@ -505,9 +511,9 @@ ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block, * tb_property_copy_dir() - Take a deep copy of directory * @dir: Directory to copy * - * This function takes a deep copy of @dir and returns back the copy. In - * case of error returns %NULL. The resulting directory needs to be - * released by calling tb_property_free_dir(). + * The resulting directory needs to be released by calling tb_property_free_dir(). + * + * Return: Pointer to &struct tb_property_dir, %NULL in case of failure. */ struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir) { @@ -577,6 +583,8 @@ struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir) * @parent: Directory to add the property * @key: Key for the property * @value: Immediate value to store with the property + * + * Return: %0 on success, negative errno otherwise. */ int tb_property_add_immediate(struct tb_property_dir *parent, const char *key, u32 value) @@ -606,6 +614,8 @@ EXPORT_SYMBOL_GPL(tb_property_add_immediate); * @buflen: Number of bytes in the data buffer * * Function takes a copy of @buf and adds it to the directory. + * + * Return: %0 on success, negative errno otherwise. */ int tb_property_add_data(struct tb_property_dir *parent, const char *key, const void *buf, size_t buflen) @@ -642,6 +652,8 @@ EXPORT_SYMBOL_GPL(tb_property_add_data); * @text: String to add * * Function takes a copy of @text and adds it to the directory. + * + * Return: %0 on success, negative errno otherwise. */ int tb_property_add_text(struct tb_property_dir *parent, const char *key, const char *text) @@ -676,6 +688,8 @@ EXPORT_SYMBOL_GPL(tb_property_add_text); * @parent: Directory to add the property * @key: Key for the property * @dir: Directory to add + * + * Return: %0 on success, negative errno otherwise. */ int tb_property_add_dir(struct tb_property_dir *parent, const char *key, struct tb_property_dir *dir) @@ -716,8 +730,10 @@ EXPORT_SYMBOL_GPL(tb_property_remove); * @key: Key to look for * @type: Type of the property * - * Finds and returns property from the given directory. Does not recurse - * into sub-directories. Returns %NULL if the property was not found. + * Finds and returns property from the given directory. Does not + * recurse into sub-directories. + * + * Return: Pointer to &struct tb_property, %NULL if the property was not found. */ struct tb_property *tb_property_find(struct tb_property_dir *dir, const char *key, enum tb_property_type type) @@ -737,6 +753,8 @@ EXPORT_SYMBOL_GPL(tb_property_find); * tb_property_get_next() - Get next property from directory * @dir: Directory holding properties * @prev: Previous property in the directory (%NULL returns the first) + * + * Return: Pointer to &struct tb_property, %NULL if property was not found. */ struct tb_property *tb_property_get_next(struct tb_property_dir *dir, struct tb_property *prev) From f72f4d5cdb1ddbc323df6c3f638dd2499c038bef Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:35 +0200 Subject: [PATCH 096/132] thunderbolt: Update retimer.c function documentation Make retimer.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/retimer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/retimer.c b/drivers/thunderbolt/retimer.c index 361fece3d818..3a0f486a24d5 100644 --- a/drivers/thunderbolt/retimer.c +++ b/drivers/thunderbolt/retimer.c @@ -27,8 +27,9 @@ * @buf: Data read from NVM is stored here * @size: Number of bytes to read * - * Reads retimer NVM and copies the contents to @buf. Returns %0 if the - * read was successful and negative errno in case of failure. + * Reads retimer NVM and copies the contents to @buf. + * + * Return: %0 if the read was successful, negative errno in case of failure. */ int tb_retimer_nvm_read(struct tb_retimer *rt, unsigned int address, void *buf, size_t size) @@ -503,6 +504,8 @@ static struct tb_retimer *tb_port_find_retimer(struct tb_port *port, u8 index) * Then Tries to enumerate on-board retimers connected to @port. Found * retimers are registered as children of @port if @add is set. Does * not scan for cable retimers for now. + * + * Return: %0 on success, negative errno otherwise. */ int tb_retimer_scan(struct tb_port *port, bool add) { From 207b8a260578b3240f1501feb75376e4b00706b1 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:36 +0200 Subject: [PATCH 097/132] thunderbolt: Update switch.c function documentation Make switch.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 140 +++++++++++++++++++++++++---------- 1 file changed, 102 insertions(+), 38 deletions(-) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index e9f4186f20f4..0e07904aa73b 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -290,8 +290,9 @@ static int nvm_authenticate(struct tb_switch *sw, bool auth_only) * @size: Size of the buffer in bytes * * Reads from router NVM and returns the requested data in @buf. Locking - * is up to the caller. Returns %0 in success and negative errno in case - * of failure. + * is up to the caller. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size) @@ -464,7 +465,7 @@ static void tb_dump_port(struct tb *tb, const struct tb_port *port) * * The port must have a TB_CAP_PHY (i.e. it should be a real port). * - * Return: Returns an enum tb_port_state on success or an error code on failure. + * Return: &enum tb_port_state or negative error code on failure. */ int tb_port_state(struct tb_port *port) { @@ -491,9 +492,11 @@ int tb_port_state(struct tb_port *port) * switch resume). Otherwise we only wait if a device is registered but the link * has not yet been established. * - * Return: Returns an error code on failure. Returns 0 if the port is not - * connected or failed to reach state TB_PORT_UP within one second. Returns 1 - * if the port is connected and in state TB_PORT_UP. + * Return: + * * %0 - If the port is not connected or failed to reach + * state %TB_PORT_UP within one second. + * * %1 - If the port is connected and in state %TB_PORT_UP. + * * Negative errno - An error occurred. */ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged) { @@ -562,7 +565,7 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged) * Change the number of NFC credits allocated to @port by @credits. To remove * NFC credits pass a negative amount of credits. * - * Return: Returns 0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ int tb_port_add_nfc_credits(struct tb_port *port, int credits) { @@ -599,7 +602,7 @@ int tb_port_add_nfc_credits(struct tb_port *port, int credits) * @port: Port whose counters to clear * @counter: Counter index to clear * - * Return: Returns 0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ int tb_port_clear_counter(struct tb_port *port, int counter) { @@ -614,6 +617,8 @@ int tb_port_clear_counter(struct tb_port *port, int counter) * * Needed for USB4 but can be called for any CIO/USB4 ports. Makes the * downstream router accessible for CM. + * + * Return: %0 on success, negative errno otherwise. */ int tb_port_unlock(struct tb_port *port) { @@ -659,6 +664,8 @@ static int __tb_port_enable(struct tb_port *port, bool enable) * @port: Port to enable (can be %NULL) * * This is used for lane 0 and 1 adapters to enable it. + * + * Return: %0 on success, negative errno otherwise. */ int tb_port_enable(struct tb_port *port) { @@ -670,6 +677,8 @@ int tb_port_enable(struct tb_port *port) * @port: Port to disable (can be %NULL) * * This is used for lane 0 and 1 adapters to disable it. + * + * Return: %0 on success, negative errno otherwise. */ int tb_port_disable(struct tb_port *port) { @@ -689,7 +698,7 @@ static int tb_port_reset(struct tb_port *port) * This is a helper method for tb_switch_alloc. Does not check or initialize * any downstream switches. * - * Return: Returns 0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ static int tb_init_port(struct tb_port *port) { @@ -847,9 +856,9 @@ static inline bool tb_switch_is_reachable(const struct tb_switch *parent, * link port, the function follows that link and returns another end on * that same link. * - * If the @end port has been reached, return %NULL. - * * Domain tb->lock must be held when this function is called. + * + * Return: Pointer to &struct tb_port, %NULL if the @end port has been reached. */ struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end, struct tb_port *prev) @@ -894,7 +903,7 @@ struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end, * tb_port_get_link_speed() - Get current link speed * @port: Port to check (USB4 or CIO) * - * Returns link speed in Gb/s or negative errno in case of failure. + * Return: Link speed in Gb/s or negative errno in case of failure. */ int tb_port_get_link_speed(struct tb_port *port) { @@ -926,9 +935,11 @@ int tb_port_get_link_speed(struct tb_port *port) * tb_port_get_link_generation() - Returns link generation * @port: Lane adapter * - * Returns link generation as number or negative errno in case of - * failure. Does not distinguish between Thunderbolt 1 and Thunderbolt 2 - * links so for those always returns 2. + * Return: Link generation as a number or negative errno in case of + * failure. + * + * Does not distinguish between Thunderbolt 1 and Thunderbolt 2 + * links so for those always returns %2. */ int tb_port_get_link_generation(struct tb_port *port) { @@ -952,8 +963,8 @@ int tb_port_get_link_generation(struct tb_port *port) * tb_port_get_link_width() - Get current link width * @port: Port to check (USB4 or CIO) * - * Returns link width. Return the link width as encoded in &enum - * tb_link_width or negative errno in case of failure. + * Return: Link width encoded in &enum tb_link_width or + * negative errno in case of failure. */ int tb_port_get_link_width(struct tb_port *port) { @@ -979,7 +990,9 @@ int tb_port_get_link_width(struct tb_port *port) * @width: Widths to check (bitmask) * * Can be called to any lane adapter. Checks if given @width is - * supported by the hardware and returns %true if it is. + * supported by the hardware. + * + * Return: %true if link width is supported, %false otherwise. */ bool tb_port_width_supported(struct tb_port *port, unsigned int width) { @@ -1016,7 +1029,7 @@ bool tb_port_width_supported(struct tb_port *port, unsigned int width) * Sets the target link width of the lane adapter to @width. Does not * enable/disable lane bonding. For that call tb_port_set_lane_bonding(). * - * Return: %0 in case of success and negative errno in case of error + * Return: %0 on success, negative errno otherwise. */ int tb_port_set_link_width(struct tb_port *port, enum tb_link_width width) { @@ -1070,7 +1083,7 @@ int tb_port_set_link_width(struct tb_port *port, enum tb_link_width width) * cases one should use tb_port_lane_bonding_enable() instead to enable * lane bonding. * - * Return: %0 in case of success and negative errno in case of error + * Return: %0 on success, negative errno otherwise. */ static int tb_port_set_lane_bonding(struct tb_port *port, bool bonding) { @@ -1104,7 +1117,7 @@ static int tb_port_set_lane_bonding(struct tb_port *port, bool bonding) * tb_port_wait_for_link_width() before enabling any paths through the * link to make sure the link is in expected state. * - * Return: %0 in case of success and negative errno in case of error + * Return: %0 on success, negative errno otherwise. */ int tb_port_lane_bonding_enable(struct tb_port *port) { @@ -1181,9 +1194,14 @@ void tb_port_lane_bonding_disable(struct tb_port *port) * * Should be used after both ends of the link have been bonded (or * bonding has been disabled) to wait until the link actually reaches - * the expected state. Returns %-ETIMEDOUT if the width was not reached - * within the given timeout, %0 if it did. Can be passed a mask of - * expected widths and succeeds if any of the widths is reached. + * the expected state. + * + * Can be passed a mask of expected widths. + * + * Return: + * * %0 - If link reaches any of the specified widths. + * * %-ETIMEDOUT - If link does not reach specified width. + * * Negative errno - Another error occurred. */ int tb_port_wait_for_link_width(struct tb_port *port, unsigned int width, int timeout_msec) @@ -1248,6 +1266,8 @@ static int tb_port_do_update_credits(struct tb_port *port) * After the link is bonded (or bonding was disabled) the port total * credits may change, so this function needs to be called to re-read * the credits. Updates also the second lane adapter. + * + * Return: %0 on success, negative errno otherwise. */ int tb_port_update_credits(struct tb_port *port) { @@ -1303,6 +1323,8 @@ static bool tb_port_resume(struct tb_port *port) /** * tb_port_is_enabled() - Is the adapter port enabled * @port: Port to check + * + * Return: %true if port is enabled, %false otherwise. */ bool tb_port_is_enabled(struct tb_port *port) { @@ -1327,6 +1349,8 @@ bool tb_port_is_enabled(struct tb_port *port) /** * tb_usb3_port_is_enabled() - Is the USB3 adapter port enabled * @port: USB3 adapter port to check + * + * Return: %true if port is enabled, %false otherwise. */ bool tb_usb3_port_is_enabled(struct tb_port *port) { @@ -1343,6 +1367,8 @@ bool tb_usb3_port_is_enabled(struct tb_port *port) * tb_usb3_port_enable() - Enable USB3 adapter port * @port: USB3 adapter port to enable * @enable: Enable/disable the USB3 adapter + * + * Return: %0 on success, negative errno otherwise. */ int tb_usb3_port_enable(struct tb_port *port, bool enable) { @@ -1358,6 +1384,8 @@ int tb_usb3_port_enable(struct tb_port *port, bool enable) /** * tb_pci_port_is_enabled() - Is the PCIe adapter port enabled * @port: PCIe port to check + * + * Return: %true if port is enabled, %false otherwise. */ bool tb_pci_port_is_enabled(struct tb_port *port) { @@ -1374,6 +1402,8 @@ bool tb_pci_port_is_enabled(struct tb_port *port) * tb_pci_port_enable() - Enable PCIe adapter port * @port: PCIe port to enable * @enable: Enable/disable the PCIe adapter + * + * Return: %0 on success, negative errno otherwise. */ int tb_pci_port_enable(struct tb_port *port, bool enable) { @@ -1389,6 +1419,8 @@ int tb_pci_port_enable(struct tb_port *port, bool enable) * @port: DP out port to check * * Checks if the DP OUT adapter port has HPD bit already set. + * + * Return: %1 if HPD is active, %0 otherwise. */ int tb_dp_port_hpd_is_active(struct tb_port *port) { @@ -1408,6 +1440,8 @@ int tb_dp_port_hpd_is_active(struct tb_port *port) * @port: Port to clear HPD * * If the DP IN port has HPD set, this function can be used to clear it. + * + * Return: %0 on success, negative errno otherwise. */ int tb_dp_port_hpd_clear(struct tb_port *port) { @@ -1434,6 +1468,8 @@ int tb_dp_port_hpd_clear(struct tb_port *port) * Programs specified Hop IDs for DP IN/OUT port. Can be called for USB4 * router DP adapters too but does not program the values as the fields * are read-only. + * + * Return: %0 on success, negative errno otherwise. */ int tb_dp_port_set_hops(struct tb_port *port, unsigned int video, unsigned int aux_tx, unsigned int aux_rx) @@ -1466,6 +1502,8 @@ int tb_dp_port_set_hops(struct tb_port *port, unsigned int video, /** * tb_dp_port_is_enabled() - Is DP adapter port enabled * @port: DP adapter port to check + * + * Return: %true if DP port is enabled, %false otherwise. */ bool tb_dp_port_is_enabled(struct tb_port *port) { @@ -1485,6 +1523,8 @@ bool tb_dp_port_is_enabled(struct tb_port *port) * * Once Hop IDs are programmed DP paths can be enabled or disabled by * calling this function. + * + * Return: %0 on success, negative errno otherwise. */ int tb_dp_port_enable(struct tb_port *port, bool enable) { @@ -1634,7 +1674,7 @@ static bool tb_switch_enumerated(struct tb_switch *sw) * * If the router is not enumerated does nothing. * - * Returns %0 on success or negative errno in case of failure. + * Return: %0 on success, negative errno otherwise. */ int tb_switch_reset(struct tb_switch *sw) { @@ -1670,8 +1710,12 @@ int tb_switch_reset(struct tb_switch *sw) * @timeout_msec: Timeout in ms how long to wait * * Wait till the specified bits in specified offset reach specified value. - * Returns %0 in case of success, %-ETIMEDOUT if the @value was not reached - * within the given timeout or a negative errno in case of failure. + * + * Return: + * * %0 - On success. + * * %-ETIMEDOUT - If the @value was not reached within + * the given timeout. + * * Negative errno - In case of failure. */ int tb_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit, u32 value, int timeout_msec) @@ -1700,7 +1744,7 @@ int tb_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit, * * Also configures a sane plug_events_delay of 255ms. * - * Return: Returns 0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ static int tb_plug_events_active(struct tb_switch *sw, bool active) { @@ -2406,8 +2450,7 @@ static bool tb_switch_exceeds_max_depth(const struct tb_switch *sw, int depth) * separately. The returned switch should be released by calling * tb_switch_put(). * - * Return: Pointer to the allocated switch or ERR_PTR() in case of - * failure. + * Return: Pointer to &struct tb_switch or ERR_PTR() in case of failure. */ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent, u64 route) @@ -2526,7 +2569,7 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent, * * The returned switch must be released by calling tb_switch_put(). * - * Return: Pointer to the allocated switch or ERR_PTR() in case of failure + * Return: Pointer to &struct tb_switch or ERR_PTR() in case of failure. */ struct tb_switch * tb_switch_alloc_safe_mode(struct tb *tb, struct device *parent, u64 route) @@ -2562,7 +2605,7 @@ tb_switch_alloc_safe_mode(struct tb *tb, struct device *parent, u64 route) * connection manager to use. Can be called to the switch again after * resume from low power states to re-initialize it. * - * Return: %0 in case of success and negative errno in case of failure + * Return: %0 on success, negative errno otherwise. */ int tb_switch_configure(struct tb_switch *sw) { @@ -2625,7 +2668,7 @@ int tb_switch_configure(struct tb_switch *sw) * Needs to be called before any tunnels can be setup through the * router. Can be called to any router. * - * Returns %0 in success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_switch_configuration_valid(struct tb_switch *sw) { @@ -2900,6 +2943,8 @@ static void tb_switch_link_init(struct tb_switch *sw) * Connection manager can call this function to enable lane bonding of a * switch. If conditions are correct and both switches support the feature, * lanes are bonded. It is safe to call this to any switch. + * + * Return: %0 on success, negative errno otherwise. */ static int tb_switch_lane_bonding_enable(struct tb_switch *sw) { @@ -2950,6 +2995,8 @@ static int tb_switch_lane_bonding_enable(struct tb_switch *sw) * * Disables lane bonding between @sw and parent. This can be called even * if lanes were not bonded originally. + * + * Return: %0 on success, negative errno otherwise. */ static int tb_switch_lane_bonding_disable(struct tb_switch *sw) { @@ -3074,7 +3121,7 @@ static int tb_switch_asym_disable(struct tb_switch *sw) * * Does nothing for host router. * - * Returns %0 in case of success, negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_switch_set_link_width(struct tb_switch *sw, enum tb_link_width width) { @@ -3145,7 +3192,7 @@ int tb_switch_set_link_width(struct tb_switch *sw, enum tb_link_width width) * * It is recommended that this is called after lane bonding is enabled. * - * Returns %0 on success and negative errno in case of error. + * Return: %0 on success and negative errno otherwise. */ int tb_switch_configure_link(struct tb_switch *sw) { @@ -3245,7 +3292,7 @@ static int tb_switch_port_hotplug_enable(struct tb_switch *sw) * exposed to the userspace when this function successfully returns. To * remove and release the switch, call tb_switch_remove(). * - * Return: %0 in case of success and negative errno in case of failure + * Return: %0 on success, negative errno otherwise. */ int tb_switch_add(struct tb_switch *sw) { @@ -3467,6 +3514,8 @@ static void tb_switch_check_wakes(struct tb_switch *sw) * suspend. If this is resume from system sleep, notifies PM core about the * wakes occurred during suspend. Disables all wakes, except USB4 wake of * upstream port for USB4 routers that shall be always enabled. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_resume(struct tb_switch *sw, bool runtime) { @@ -3617,7 +3666,9 @@ void tb_switch_suspend(struct tb_switch *sw, bool runtime) * @in: DP IN port * * Queries availability of DP resource for DP tunneling using switch - * specific means. Returns %true if resource is available. + * specific means. + * + * Return: %true if resource is available, %false otherwise. */ bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in) { @@ -3633,7 +3684,8 @@ bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in) * * Allocates DP resource for DP tunneling. The resource must be * available for this to succeed (see tb_switch_query_dp_resource()). - * Returns %0 in success and negative errno otherwise. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in) { @@ -3718,6 +3770,8 @@ static int tb_switch_match(struct device *dev, const void *data) * * Returned switch has reference count increased so the caller needs to * call tb_switch_put() when done with the switch. + * + * Return: Pointer to &struct tb_switch, %NULL if not found. */ struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link, u8 depth) { @@ -3743,6 +3797,8 @@ struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link, u8 depth) * * Returned switch has reference count increased so the caller needs to * call tb_switch_put() when done with the switch. + * + * Return: Pointer to &struct tb_switch, %NULL if not found. */ struct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid) { @@ -3767,6 +3823,8 @@ struct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid) * * Returned switch has reference count increased so the caller needs to * call tb_switch_put() when done with the switch. + * + * Return: Pointer to &struct tb_switch, %NULL if not found. */ struct tb_switch *tb_switch_find_by_route(struct tb *tb, u64 route) { @@ -3791,6 +3849,8 @@ struct tb_switch *tb_switch_find_by_route(struct tb *tb, u64 route) * tb_switch_find_port() - return the first port of @type on @sw or NULL * @sw: Switch to find the port from * @type: Port type to look for + * + * Return: Pointer to &struct tb_port, %NULL if not found. */ struct tb_port *tb_switch_find_port(struct tb_switch *sw, enum tb_port_type type) @@ -3859,6 +3919,8 @@ static int tb_switch_pcie_bridge_write(struct tb_switch *sw, unsigned int bridge * entry to PCIe L1 state. Shall be called after the upstream PCIe tunnel * was configured. Due to Intel platforms limitation, shall be called only * for first hop switch. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_pcie_l1_enable(struct tb_switch *sw) { @@ -3893,6 +3955,8 @@ int tb_switch_pcie_l1_enable(struct tb_switch *sw) * connected to the type-C port. Call only after PCIe tunnel has been * established. The function only does the connect if not done already * so can be called several times for the same router. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_xhci_connect(struct tb_switch *sw) { From d05cc39d1d2b9df8cd86ca24c21f36408a5c72a7 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:37 +0200 Subject: [PATCH 098/132] thunderbolt: Update tb.c function documentation Make tb.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 83a33fc1486a..4a94cb406bdf 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -225,14 +225,12 @@ static int tb_enable_clx(struct tb_switch *sw) return ret == -EOPNOTSUPP ? 0 : ret; } -/** - * tb_disable_clx() - Disable CL states up to host router - * @sw: Router to start +/* + * Disables CL states from @sw up to the host router. * - * Disables CL states from @sw up to the host router. Returns true if - * any CL state were disabled. This can be used to figure out whether - * the link was setup by us or the boot firmware so we don't - * accidentally enable them if they were not enabled during discovery. + * This can be used to figure out whether the link was setup by us or the + * boot firmware so we don't accidentally enable them if they were not + * enabled during discovery. */ static bool tb_disable_clx(struct tb_switch *sw) { @@ -456,10 +454,8 @@ static void tb_scan_xdomain(struct tb_port *port) } } -/** - * tb_find_unused_port() - return the first inactive port on @sw - * @sw: Switch to find the port on - * @type: Port type to look for +/* + * Returns the first inactive port on @sw. */ static struct tb_port *tb_find_unused_port(struct tb_switch *sw, enum tb_port_type type) @@ -549,6 +545,8 @@ static struct tb_tunnel *tb_find_first_usb3_tunnel(struct tb *tb, * from @src_port to @dst_port. Does not take USB3 tunnel starting from * @src_port and ending on @src_port into account because that bandwidth is * already included in as part of the "first hop" USB3 tunnel. + * + * Return: %0 on success, negative errno otherwise. */ static int tb_consumed_usb3_pcie_bandwidth(struct tb *tb, struct tb_port *src_port, @@ -601,6 +599,8 @@ static int tb_consumed_usb3_pcie_bandwidth(struct tb *tb, * If there is bandwidth reserved for any of the groups between * @src_port and @dst_port (but not yet used) that is also taken into * account in the returned consumed bandwidth. + * + * Return: %0 on success, negative errno otherwise. */ static int tb_consumed_dp_bandwidth(struct tb *tb, struct tb_port *src_port, @@ -701,6 +701,8 @@ static bool tb_asym_supported(struct tb_port *src_port, struct tb_port *dst_port * single link at @port. If @include_asym is set then includes the * additional banwdith if the links are transitioned into asymmetric to * direction from @src_port to @dst_port. + * + * Return: %0 on success, negative errno otherwise. */ static int tb_maximum_bandwidth(struct tb *tb, struct tb_port *src_port, struct tb_port *dst_port, struct tb_port *port, @@ -807,6 +809,8 @@ static int tb_maximum_bandwidth(struct tb *tb, struct tb_port *src_port, * If @include_asym is true then includes also bandwidth that can be * added when the links are transitioned into asymmetric (but does not * transition the links). + * + * Return: %0 on success, negative errno otherwise. */ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port, struct tb_port *dst_port, int *available_up, @@ -1029,6 +1033,8 @@ static int tb_create_usb3_tunnels(struct tb_switch *sw) * (requested + currently consumed) on that link exceed @asym_threshold. * * Must be called with available >= requested over all links. + * + * Return: %0 on success, negative errno otherwise. */ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port, struct tb_port *dst_port, int requested_up, @@ -1135,6 +1141,8 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port, * Goes over each link from @src_port to @dst_port and tries to * transition the link to symmetric if the currently consumed bandwidth * allows and link asymmetric preference is ignored (if @keep_asym is %false). + * + * Return: %0 on success, negative errno otherwise. */ static int tb_configure_sym(struct tb *tb, struct tb_port *src_port, struct tb_port *dst_port, bool keep_asym) From 978a3d608f9f162ba2f0fa3c392ce8adaa171c95 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:38 +0200 Subject: [PATCH 099/132] thunderbolt: Update tb.h function documentation Make tb.h function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.h | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index f503bad86413..c1ffaf0bb39e 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -558,7 +558,7 @@ static inline void *tb_priv(struct tb *tb) * During switch alloc/init tb_upstream_port()->remote may be NULL, even for * non root switches (on the NHI port remote is always NULL). * - * Return: Returns the upstream port of the switch. + * Return: Pointer to &struct tb_port. */ static inline struct tb_port *tb_upstream_port(struct tb_switch *sw) { @@ -569,8 +569,8 @@ static inline struct tb_port *tb_upstream_port(struct tb_switch *sw) * tb_is_upstream_port() - Is the port upstream facing * @port: Port to check * - * Returns true if @port is upstream facing port. In case of dual link - * ports both return true. + * Return: %true if @port is upstream facing port. In case of dual link + * ports, both return %true. */ static inline bool tb_is_upstream_port(const struct tb_port *port) { @@ -613,7 +613,7 @@ static inline const char *tb_width_name(enum tb_link_width width) * tb_port_has_remote() - Does the port have switch connected downstream * @port: Port to check * - * Returns true only when the port is primary port and has remote set. + * Return: %true only when the port is primary port and has remote set. */ static inline bool tb_port_has_remote(const struct tb_port *port) { @@ -905,8 +905,9 @@ static inline struct tb_switch *tb_switch_parent(struct tb_switch *sw) * tb_switch_downstream_port() - Return downstream facing port of parent router * @sw: Device router pointer * - * Only call for device routers. Returns the downstream facing port of - * the parent router. + * Call only for device routers. + * + * Return: Pointer to &struct tb_port or %NULL in case of failure. */ static inline struct tb_port *tb_switch_downstream_port(struct tb_switch *sw) { @@ -918,6 +919,8 @@ static inline struct tb_port *tb_switch_downstream_port(struct tb_switch *sw) /** * tb_switch_depth() - Returns depth of the connected router * @sw: Router + * + * Return: Router depth level as a number. */ static inline int tb_switch_depth(const struct tb_switch *sw) { @@ -1010,6 +1013,9 @@ static inline bool tb_switch_is_tiger_lake(const struct tb_switch *sw) * is handling @sw this function can be called. It is valid to call this * after tb_switch_alloc() and tb_switch_configure() has been called * (latter only for SW CM case). + * + * Return: %true if switch is handled by ICM, %false if handled by + * software CM. */ static inline bool tb_switch_is_icm(const struct tb_switch *sw) { @@ -1037,6 +1043,8 @@ int tb_switch_tmu_configure(struct tb_switch *sw, enum tb_switch_tmu_mode mode); * * Checks if given router TMU mode is configured to @mode. Note the * router TMU might not be enabled to this mode. + * + * Return: %true if TMU mode is equal to @mode, %false otherwise. */ static inline bool tb_switch_tmu_is_configured(const struct tb_switch *sw, enum tb_switch_tmu_mode mode) @@ -1048,8 +1056,8 @@ static inline bool tb_switch_tmu_is_configured(const struct tb_switch *sw, * tb_switch_tmu_is_enabled() - Checks if the specified TMU mode is enabled * @sw: Router whose TMU mode to check * - * Return true if hardware TMU configuration matches the requested - * configuration (and is not %TB_SWITCH_TMU_MODE_OFF). + * Return: %true if hardware TMU configuration matches the requested + * configuration (and is not %TB_SWITCH_TMU_MODE_OFF), %false otherwise. */ static inline bool tb_switch_tmu_is_enabled(const struct tb_switch *sw) { @@ -1069,9 +1077,10 @@ int tb_switch_clx_disable(struct tb_switch *sw); * @clx: The CLx states to check for * * Checks if the specified CLx is enabled on the router upstream link. - * Returns true if any of the given states is enabled. * * Not applicable for a host router. + * + * Return: %true if any of the given states is enabled, %false otherwise. */ static inline bool tb_switch_clx_is_enabled(const struct tb_switch *sw, unsigned int clx) @@ -1103,7 +1112,7 @@ struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end, * @src: Source adapter * @dst: Destination adapter * - * Returns %true only if the specified path from source adapter (@src) + * Return: %true only if the specified path from source adapter (@src) * to destination adapter (@dst) is directed downstream. */ static inline bool @@ -1235,7 +1244,7 @@ static inline int tb_route_length(u64 route) * * Port must not be the upstream port (otherwise a loop is created). * - * Return: Returns a route to the switch behind @port. + * Return: Route to the switch behind @port. */ static inline u64 tb_downstream_route(struct tb_port *port) { @@ -1263,7 +1272,7 @@ static inline struct tb_switch *tb_xdomain_parent(struct tb_xdomain *xd) * tb_xdomain_downstream_port() - Return downstream facing port of parent router * @xd: Xdomain pointer * - * Returns the downstream port the XDomain is connected to. + * Return: Pointer to &struct tb_port or %NULL in case of failure. */ static inline struct tb_port *tb_xdomain_downstream_port(struct tb_xdomain *xd) { @@ -1291,7 +1300,7 @@ static inline struct tb_retimer *tb_to_retimer(struct device *dev) * usb4_switch_version() - Returns USB4 version of the router * @sw: Router to check * - * Returns major version of USB4 router (%1 for v1, %2 for v2 and so + * Return: Major version of USB4 router (%1 for v1, %2 for v2 and so * on). Can be called to pre-USB4 router too and in that case returns %0. */ static inline unsigned int usb4_switch_version(const struct tb_switch *sw) @@ -1303,7 +1312,7 @@ static inline unsigned int usb4_switch_version(const struct tb_switch *sw) * tb_switch_is_usb4() - Is the switch USB4 compliant * @sw: Switch to check * - * Returns true if the @sw is USB4 compliant router, false otherwise. + * Return: %true if the @sw is USB4 compliant router, %false otherwise. */ static inline bool tb_switch_is_usb4(const struct tb_switch *sw) { From b30234f27396c915547fa5905dcf79e5e9be3ff6 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:39 +0200 Subject: [PATCH 100/132] thunderbolt: Add missing documentation in tb.h Add missing parameters and struct/enum description in tb.h kernel-doc comments. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index c1ffaf0bb39e..8e2762ff8d51 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -324,7 +324,7 @@ struct usb4_port { }; /** - * tb_retimer: Thunderbolt retimer + * struct tb_retimer - Thunderbolt retimer * @dev: Device for the retimer * @tb: Pointer to the domain the retimer belongs to * @index: Retimer index facing the router USB4 port @@ -552,6 +552,7 @@ static inline void *tb_priv(struct tb *tb) /** * tb_upstream_port() - return the upstream port of a switch + * @sw: Router * * Every switch has an upstream port (for the root switch it is the NHI). * @@ -1241,6 +1242,7 @@ static inline int tb_route_length(u64 route) /** * tb_downstream_route() - get route to downstream switch + * @port: Port to check * * Port must not be the upstream port (otherwise a loop is created). * @@ -1364,7 +1366,7 @@ int usb4_port_asym_set_link_width(struct tb_port *port, enum tb_link_width width int usb4_port_asym_start(struct tb_port *port); /** - * enum tb_sb_target - Sideband transaction target + * enum usb4_sb_target - Sideband transaction target * @USB4_SB_TARGET_ROUTER: Target is the router itself * @USB4_SB_TARGET_PARTNER: Target is partner * @USB4_SB_TARGET_RETIMER: Target is retimer @@ -1409,6 +1411,8 @@ enum usb4_margining_lane { * @voltage_time_offset: Offset for voltage / time for software margining * @optional_voltage_offset_range: Enable optional extended voltage range * @right_high: %false if left/low margin test is performed, %true if right/high + * @upper_eye: %true if margin test is done on upper eye, %false if done on + * lower eye * @time: %true if time margining is used instead of voltage */ struct usb4_port_margining_params { From 371c2374449db296675a865c46cde54336ff2ef7 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:40 +0200 Subject: [PATCH 101/132] thunderbolt: Update tmu.c function documentation Make tmu.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tmu.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/thunderbolt/tmu.c b/drivers/thunderbolt/tmu.c index 9a259c72e5a7..b22831b41ec0 100644 --- a/drivers/thunderbolt/tmu.c +++ b/drivers/thunderbolt/tmu.c @@ -405,6 +405,8 @@ static int tmu_mode_init(struct tb_switch *sw) * This function must be called before other TMU related functions to * makes the internal structures are filled in correctly. Does not * change any hardware configuration. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_tmu_init(struct tb_switch *sw) { @@ -439,6 +441,8 @@ int tb_switch_tmu_init(struct tb_switch *sw) * @sw: Switch whose time to update * * Updates switch local time using time posting procedure. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_tmu_post_time(struct tb_switch *sw) { @@ -555,6 +559,8 @@ static int disable_enhanced(struct tb_port *up, struct tb_port *down) * @sw: Switch whose TMU to disable * * Turns off TMU of @sw if it is enabled. If not enabled does nothing. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_tmu_disable(struct tb_switch *sw) { @@ -938,6 +944,8 @@ static int tb_switch_tmu_change_mode(struct tb_switch *sw) * Enables TMU of a router to be in uni-directional Normal/HiFi or * bi-directional HiFi mode. Calling tb_switch_tmu_configure() is * required before calling this function. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_tmu_enable(struct tb_switch *sw) { @@ -1017,9 +1025,11 @@ int tb_switch_tmu_enable(struct tb_switch *sw) * Selects the TMU mode that is enabled when tb_switch_tmu_enable() is * next called. * - * Returns %0 in success and negative errno otherwise. Specifically - * returns %-EOPNOTSUPP if the requested mode is not possible (not - * supported by the router and/or topology). + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the requested mode is not possible (not supported by + * the router and/or topology). + * * Negative errno - Another error occurred. */ int tb_switch_tmu_configure(struct tb_switch *sw, enum tb_switch_tmu_mode mode) { From 6f3ed985b7d1e4950f1f63139f32a7d889e4aaee Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:41 +0200 Subject: [PATCH 102/132] thunderbolt: Update tunnel.c function documentation Make tunnel.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tunnel.c | 85 ++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index d52efe3f658c..850d9b107abd 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -121,6 +121,8 @@ static inline unsigned int tb_usable_credits(const struct tb_port *port) * @port: Lane adapter to check * @max_dp_streams: If non-%NULL stores maximum number of simultaneous DP * streams possible through this lane adapter + * + * Return: Number of available credits. */ static unsigned int tb_available_credits(const struct tb_port *port, size_t *max_dp_streams) @@ -415,8 +417,9 @@ static int tb_pci_init_path(struct tb_path *path) * @alloc_hopid: Allocate HopIDs from visited ports * * If @down adapter is active, follows the tunnel to the PCIe upstream - * adapter and back. Returns the discovered tunnel or %NULL if there was - * no tunnel. + * adapter and back. + * + * Return: Pointer to &struct tb_tunnel or %NULL if there was no tunnel. */ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, bool alloc_hopid) @@ -496,7 +499,7 @@ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, * Allocate a PCI tunnel. The ports must be of type TB_TYPE_PCIE_UP and * TB_TYPE_PCIE_DOWN. * - * Return: Returns a tb_tunnel on success or NULL on failure. + * Return: Pointer to @struct tb_tunnel or %NULL on failure. */ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, struct tb_port *down) @@ -543,9 +546,12 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, * * Can be called to any connected lane 0 adapter to find out how much * bandwidth needs to be left in reserve for possible PCIe bulk traffic. - * Returns true if there is something to be reserved and writes the - * amount to @reserved_down/@reserved_up. Otherwise returns false and - * does not touch the parameters. + * + * Return: + * * %true - If there is something to be reserved. Writes the amount to + * @reserved_down/@reserved_up. + * * %false - Nothing to be reserved. Leaves @reserved_down/@reserved_up + * unmodified. */ bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up, int *reserved_down) @@ -1151,7 +1157,8 @@ static int tb_dp_activate(struct tb_tunnel *tunnel, bool active) * @tunnel: DP tunnel to check * @max_bw_rounded: Maximum bandwidth in Mb/s rounded up to the next granularity * - * Returns maximum possible bandwidth for this tunnel in Mb/s. + * Return: Maximum possible bandwidth for this tunnel in Mb/s, negative errno + * in case of failure. */ static int tb_dp_bandwidth_mode_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_bw_rounded) @@ -1547,7 +1554,7 @@ static void tb_dp_dump(struct tb_tunnel *tunnel) * and back. Returns the discovered tunnel or %NULL if there was no * tunnel. * - * Return: DP tunnel or %NULL if no tunnel found. + * Return: Pointer to &struct tb_tunnel or %NULL if no tunnel found. */ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, bool alloc_hopid) @@ -1648,7 +1655,7 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, * successful (or if it returns %false there was some sort of issue). * The @callback is called without @tb->lock held. * - * Return: Returns a tb_tunnel on success or &NULL on failure. + * Return: Pointer to @struct tb_tunnel or %NULL in case of failure. */ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, struct tb_port *out, int link_nr, @@ -1861,7 +1868,7 @@ static void tb_dma_destroy(struct tb_tunnel *tunnel) * @receive_ring: NHI ring number used to receive packets from the * other domain. Set to %-1 if RX path is not needed. * - * Return: Returns a tb_tunnel on success or NULL on failure. + * Return: Pointer to @struct tb_tunnel or %NULL in case of failure. */ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, struct tb_port *dst, int transmit_path, @@ -1938,7 +1945,8 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, * * This function can be used to match specific DMA tunnel, if there are * multiple DMA tunnels going through the same XDomain connection. - * Returns true if there is match and false otherwise. + * + * Return: %true if there is a match, %false otherwise. */ bool tb_tunnel_match_dma(const struct tb_tunnel *tunnel, int transmit_path, int transmit_ring, int receive_path, int receive_ring) @@ -2160,8 +2168,9 @@ static void tb_usb3_init_path(struct tb_path *path) * @alloc_hopid: Allocate HopIDs from visited ports * * If @down adapter is active, follows the tunnel to the USB3 upstream - * adapter and back. Returns the discovered tunnel or %NULL if there was - * no tunnel. + * adapter and back. + * + * Return: Pointer to &struct tb_tunnel or %NULL if there was no tunnel. */ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down, bool alloc_hopid) @@ -2266,7 +2275,7 @@ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down, * Allocate an USB3 tunnel. The ports must be of type @TB_TYPE_USB3_UP and * @TB_TYPE_USB3_DOWN. * - * Return: Returns a tb_tunnel on success or %NULL on failure. + * Return: Pointer to @struct tb_tunnel or %NULL in case of failure. */ struct tb_tunnel *tb_tunnel_alloc_usb3(struct tb *tb, struct tb_port *up, struct tb_port *down, int max_up, @@ -2337,6 +2346,8 @@ struct tb_tunnel *tb_tunnel_alloc_usb3(struct tb *tb, struct tb_port *up, /** * tb_tunnel_is_invalid - check whether an activated path is still valid * @tunnel: Tunnel to check + * + * Return: %true if path is valid, %false otherwise. */ bool tb_tunnel_is_invalid(struct tb_tunnel *tunnel) { @@ -2355,10 +2366,11 @@ bool tb_tunnel_is_invalid(struct tb_tunnel *tunnel) * tb_tunnel_activate() - activate a tunnel * @tunnel: Tunnel to activate * - * Return: 0 on success and negative errno in case if failure. - * Specifically returns %-EINPROGRESS if the tunnel activation is still - * in progress (that's for DP tunnels to complete DPRX capabilities - * read). + * Return: + * * %0 - On success. + * * %-EINPROGRESS - If the tunnel activation is still in progress (that's + * for DP tunnels to complete DPRX capabilities read). + * * Negative errno - Another error occurred. */ int tb_tunnel_activate(struct tb_tunnel *tunnel) { @@ -2438,8 +2450,8 @@ void tb_tunnel_deactivate(struct tb_tunnel *tunnel) * @tunnel: Tunnel to check * @port: Port to check * - * Returns true if @tunnel goes through @port (direction does not matter), - * false otherwise. + * Return: %true if @tunnel goes through @port (direction does not matter), + * %false otherwise. */ bool tb_tunnel_port_on_path(const struct tb_tunnel *tunnel, const struct tb_port *port) @@ -2469,9 +2481,11 @@ static bool tb_tunnel_is_activated(const struct tb_tunnel *tunnel) * @max_up: Maximum upstream bandwidth in Mb/s * @max_down: Maximum downstream bandwidth in Mb/s * - * Returns maximum possible bandwidth this tunnel can go if not limited - * by other bandwidth clients. If the tunnel does not support this - * returns %-EOPNOTSUPP. + * Return: + * * Maximum possible bandwidth this tunnel can support if not + * limited by other bandwidth clients. + * * %-EOPNOTSUPP - If the tunnel does not support this function. + * * %-ENOTCONN - If the tunnel is not active. */ int tb_tunnel_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up, int *max_down) @@ -2491,8 +2505,12 @@ int tb_tunnel_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up, * @allocated_down: Currently allocated downstream bandwidth in Mb/s is * stored here * - * Returns the bandwidth allocated for the tunnel. This may be higher - * than what the tunnel actually consumes. + * Return: + * * Bandwidth allocated for the tunnel. This may be higher than what the + * tunnel actually consumes. + * * %-EOPNOTSUPP - If the tunnel does not support this function. + * * %-ENOTCONN - If the tunnel is not active. + * * Negative errno - Another error occurred. */ int tb_tunnel_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up, int *allocated_down) @@ -2512,10 +2530,12 @@ int tb_tunnel_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up, * @alloc_up: New upstream bandwidth in Mb/s * @alloc_down: New downstream bandwidth in Mb/s * - * Tries to change tunnel bandwidth allocation. If succeeds returns %0 - * and updates @alloc_up and @alloc_down to that was actually allocated - * (it may not be the same as passed originally). Returns negative errno - * in case of failure. + * Tries to change tunnel bandwidth allocation. + * + * Return: + * * %0 - On success. Updates @alloc_up and @alloc_down to values that were + * actually allocated (it may not be the same as passed originally). + * * Negative errno - In case of failure. */ int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up, int *alloc_down) @@ -2546,8 +2566,9 @@ int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up, * Can be %NULL. * * Stores the amount of isochronous bandwidth @tunnel consumes in - * @consumed_up and @consumed_down. In case of success returns %0, - * negative errno otherwise. + * @consumed_up and @consumed_down. + * + * Return: %0 on success, negative errno otherwise. */ int tb_tunnel_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up, int *consumed_down) @@ -2585,7 +2606,7 @@ int tb_tunnel_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up, * If tunnel supports dynamic bandwidth management (USB3 tunnels at the * moment) this function makes it to release all the unused bandwidth. * - * Returns %0 in case of success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_tunnel_release_unused_bandwidth(struct tb_tunnel *tunnel) { From e262b91b223a237fa87c83ce1b4e4e2dafd053ad Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:42 +0200 Subject: [PATCH 103/132] thunderbolt: Update tunnel.h function documentation Make tunnel.h function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tunnel.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h index 5e9fb73d5220..2c44fc8a10bc 100644 --- a/drivers/thunderbolt/tunnel.h +++ b/drivers/thunderbolt/tunnel.h @@ -142,10 +142,11 @@ void tb_tunnel_deactivate(struct tb_tunnel *tunnel); * tb_tunnel_is_active() - Is tunnel fully activated * @tunnel: Tunnel to check * - * Returns %true if @tunnel is fully activated. For other than DP - * tunnels this is pretty much once tb_tunnel_activate() returns - * successfully. However, for DP tunnels this returns %true only once the - * DPRX capabilities read has been issued successfully. + * Return: %true if @tunnel is fully activated. + * + * Note for DP tunnels this returns %true only once the DPRX capabilities + * read has been issued successfully. For other tunnels, this function + * returns %true pretty much once tb_tunnel_activate() returns successfully. */ static inline bool tb_tunnel_is_active(const struct tb_tunnel *tunnel) { From 9a5abaf8be02aeedd8f374b253da472f9eedfbf1 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:43 +0200 Subject: [PATCH 104/132] thunderbolt: Update usb4.c function documentation Make usb4.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/usb4.c | 333 +++++++++++++++++++++++++------------ 1 file changed, 229 insertions(+), 104 deletions(-) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index e160afadd501..76f01713a875 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -237,6 +237,8 @@ static bool link_is_usb4(struct tb_port *port) * * This does not set the configuration valid bit of the router. To do * that call usb4_switch_configuration_valid(). + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_setup(struct tb_switch *sw) { @@ -304,7 +306,7 @@ int usb4_switch_setup(struct tb_switch *sw) * usb4_switch_setup() has been called. Can be called to host and device * routers (does nothing for the latter). * - * Returns %0 in success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_configuration_valid(struct tb_switch *sw) { @@ -334,6 +336,8 @@ int usb4_switch_configuration_valid(struct tb_switch *sw) * @uid: UID is stored here * * Reads 64-bit UID from USB4 router config space. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid) { @@ -371,6 +375,8 @@ static int usb4_switch_drom_read_block(void *data, * Uses USB4 router operations to read router DROM. For devices this * should always work but for hosts it may return %-EOPNOTSUPP in which * case the host router does not have DROM. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size) @@ -385,6 +391,8 @@ int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf, * * Checks whether conditions are met so that lane bonding can be * established with the upstream router. Call only for device routers. + * + * Return: %true if lane bonding is possible, %false otherwise. */ bool usb4_switch_lane_bonding_possible(struct tb_switch *sw) { @@ -407,6 +415,8 @@ bool usb4_switch_lane_bonding_possible(struct tb_switch *sw) * @runtime: Wake is being programmed during system runtime * * Enables/disables router to wake up from sleep. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags, bool runtime) { @@ -484,8 +494,10 @@ int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags, bool runtime) * usb4_switch_set_sleep() - Prepare the router to enter sleep * @sw: USB4 router * - * Sets sleep bit for the router. Returns when the router sleep ready + * Sets sleep bit for the router and waits until router sleep ready * bit has been asserted. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_set_sleep(struct tb_switch *sw) { @@ -511,9 +523,10 @@ int usb4_switch_set_sleep(struct tb_switch *sw) * usb4_switch_nvm_sector_size() - Return router NVM sector size * @sw: USB4 router * - * If the router supports NVM operations this function returns the NVM - * sector size in bytes. If NVM operations are not supported returns - * %-EOPNOTSUPP. + * Return: + * * NVM sector size in bytes if router supports NVM operations. + * * %-EOPNOTSUPP - If router does not support NVM operations. + * * Negative errno - Another error occurred. */ int usb4_switch_nvm_sector_size(struct tb_switch *sw) { @@ -560,8 +573,12 @@ static int usb4_switch_nvm_read_block(void *data, * @buf: Read data is placed here * @size: How many bytes to read * - * Reads NVM contents of the router. If NVM is not supported returns - * %-EOPNOTSUPP. + * Reads NVM contents of the router. + * + * Return: + * * %0 - Read completed successfully. + * * %-EOPNOTSUPP - NVM not supported. + * * Negative errno - Another error occurred. */ int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size) @@ -578,7 +595,7 @@ int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, * Explicitly sets NVM write offset. Normally when writing to NVM this * is done automatically by usb4_switch_nvm_write(). * - * Returns %0 in success and negative errno if there was a failure. + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_nvm_set_offset(struct tb_switch *sw, unsigned int address) { @@ -620,8 +637,12 @@ static int usb4_switch_nvm_write_next_block(void *data, unsigned int dwaddress, * @buf: Pointer to the data to write * @size: Size of @buf in bytes * - * Writes @buf to the router NVM using USB4 router operations. If NVM - * write is not supported returns %-EOPNOTSUPP. + * Writes @buf to the router NVM using USB4 router operations. + * + * Return: + * * %0 - Write completed successfully. + * * %-EOPNOTSUPP - NVM write not supported. + * * Negative errno - Another error occurred. */ int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address, const void *buf, size_t size) @@ -643,11 +664,13 @@ int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address, * After the new NVM has been written via usb4_switch_nvm_write(), this * function triggers NVM authentication process. The router gets power * cycled and if the authentication is successful the new NVM starts - * running. In case of failure returns negative errno. + * running. * * The caller should call usb4_switch_nvm_authenticate_status() to read * the status of the authentication after power cycle. It should be the * first router operation to avoid the status being lost. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_nvm_authenticate(struct tb_switch *sw) { @@ -675,11 +698,13 @@ int usb4_switch_nvm_authenticate(struct tb_switch *sw) * @status: Status code of the operation * * The function checks if there is status available from the last NVM - * authenticate router operation. If there is status then %0 is returned - * and the status code is placed in @status. Returns negative errno in case - * of failure. + * authenticate router operation. * * Must be called before any other router operation. + * + * Return: + * * %0 - If there is status. Status code is placed in @status. + * * Negative errno - Failure occurred. */ int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status) { @@ -723,7 +748,7 @@ int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status) * allocation fields accordingly. Specifically @sw->credits_allocation * is set to %true if these parameters can be used in tunneling. * - * Returns %0 on success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_credits_init(struct tb_switch *sw) { @@ -862,8 +887,10 @@ int usb4_switch_credits_init(struct tb_switch *sw) * @in: DP IN adapter * * For DP tunneling this function can be used to query availability of - * DP IN resource. Returns true if the resource is available for DP - * tunneling, false otherwise. + * DP IN resource. + * + * Return: %true if the resource is available for DP tunneling, %false + * otherwise. */ bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in) { @@ -891,9 +918,12 @@ bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in) * @in: DP IN adapter * * Allocates DP IN resource for DP tunneling using USB4 router - * operations. If the resource was allocated returns %0. Otherwise - * returns negative errno, in particular %-EBUSY if the resource is - * already allocated. + * operations. + * + * Return: + * * %0 - Resource allocated successfully. + * * %-EBUSY - Resource is already allocated. + * * Negative errno - Other failure occurred. */ int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in) { @@ -917,6 +947,8 @@ int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in) * @in: DP IN adapter * * Releases the previously allocated DP IN resource. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in) { @@ -972,6 +1004,8 @@ int usb4_port_index(const struct tb_switch *sw, const struct tb_port *port) * downstream adapters where the PCIe topology is extended. This * function returns the corresponding downstream PCIe adapter or %NULL * if no such mapping was possible. + * + * Return: Pointer to &struct tb_port or %NULL if not found. */ struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw, const struct tb_port *port) @@ -1003,6 +1037,8 @@ struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw, * downstream adapters where the USB 3.x topology is extended. This * function returns the corresponding downstream USB 3.x adapter or * %NULL if no such mapping was possible. + * + * Return: Pointer to &struct tb_port or %NULL if not found. */ struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw, const struct tb_port *port) @@ -1032,7 +1068,7 @@ struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw, * For USB4 router finds all USB4 ports and registers devices for each. * Can be called to any router. * - * Return %0 in case of success and negative errno in case of failure. + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_add_ports(struct tb_switch *sw) { @@ -1085,6 +1121,8 @@ void usb4_switch_remove_ports(struct tb_switch *sw) * * Unlocks USB4 downstream port so that the connection manager can * access the router below this port. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_unlock(struct tb_port *port) { @@ -1105,6 +1143,8 @@ int usb4_port_unlock(struct tb_port *port) * * Enables hot plug events on a given port. This is only intended * to be used on lane, DP-IN, and DP-OUT adapters. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_hotplug_enable(struct tb_port *port) { @@ -1124,6 +1164,8 @@ int usb4_port_hotplug_enable(struct tb_port *port) * @port: USB4 port to reset * * Issues downstream port reset to @port. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_reset(struct tb_port *port) { @@ -1185,6 +1227,8 @@ static int usb4_port_set_configured(struct tb_port *port, bool configured) * @port: USB4 router * * Sets the USB4 link to be configured for power management purposes. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_configure(struct tb_port *port) { @@ -1196,6 +1240,8 @@ int usb4_port_configure(struct tb_port *port) * @port: USB4 router * * Sets the USB4 link to be unconfigured for power management purposes. + * + * Return: %0 on success, negative errno otherwise. */ void usb4_port_unconfigure(struct tb_port *port) { @@ -1230,7 +1276,9 @@ static int usb4_set_xdomain_configured(struct tb_port *port, bool configured) * @xd: XDomain that is connected to the port * * Marks the USB4 port as being connected to another host and updates - * the link type. Returns %0 in success and negative errno in failure. + * the link type. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_configure_xdomain(struct tb_port *port, struct tb_xdomain *xd) { @@ -1300,7 +1348,8 @@ static int usb4_port_write_data(struct tb_port *port, const void *data, * @size: Size of @buf * * Reads data from sideband register @reg and copies it into @buf. - * Returns %0 in case of success and negative errno in case of failure. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_sb_read(struct tb_port *port, enum usb4_sb_target target, u8 index, u8 reg, void *buf, u8 size) @@ -1351,8 +1400,9 @@ int usb4_port_sb_read(struct tb_port *port, enum usb4_sb_target target, u8 index * @buf: Data to write * @size: Size of @buf * - * Writes @buf to sideband register @reg. Returns %0 in case of success - * and negative errno in case of failure. + * Writes @buf to sideband register @reg. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_sb_write(struct tb_port *port, enum usb4_sb_target target, u8 index, u8 reg, const void *buf, u8 size) @@ -1469,8 +1519,7 @@ static int usb4_port_set_router_offline(struct tb_port *port, bool offline) * port does not react on hotplug events anymore. This needs to be * called before retimer access is done when the USB4 links is not up. * - * Returns %0 in case of success and negative errno if there was an - * error. + * Return: %0 on success, negative errno otherwise. */ int usb4_port_router_offline(struct tb_port *port) { @@ -1482,6 +1531,8 @@ int usb4_port_router_offline(struct tb_port *port) * @port: USB4 port * * Makes the USB4 port functional again. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_router_online(struct tb_port *port) { @@ -1493,8 +1544,9 @@ int usb4_port_router_online(struct tb_port *port) * @port: USB4 port * * This forces the USB4 port to send broadcast RT transaction which - * makes the retimers on the link to assign index to themselves. Returns - * %0 in case of success and negative errno if there was an error. + * makes the retimers on the link assign index to themselves. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_enumerate_retimers(struct tb_port *port) { @@ -1511,6 +1563,8 @@ int usb4_port_enumerate_retimers(struct tb_port *port) * * PORT_CS_18_CPS bit reflects if the link supports CLx including * active cables (if connected on the link). + * + * Return: %true if Clx is supported, %false otherwise. */ bool usb4_port_clx_supported(struct tb_port *port) { @@ -1529,8 +1583,9 @@ bool usb4_port_clx_supported(struct tb_port *port) * usb4_port_asym_supported() - If the port supports asymmetric link * @port: USB4 port * - * Checks if the port and the cable supports asymmetric link and returns - * %true in that case. + * Checks if the port and the cable support asymmetric link. + * + * Return: %true if asymmetric link is supported, %false otherwise. */ bool usb4_port_asym_supported(struct tb_port *port) { @@ -1552,6 +1607,8 @@ bool usb4_port_asym_supported(struct tb_port *port) * * Sets USB4 port link width to @width. Can be called for widths where * usb4_port_asym_width_supported() returned @true. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_asym_set_link_width(struct tb_port *port, enum tb_link_width width) { @@ -1596,8 +1653,10 @@ int usb4_port_asym_set_link_width(struct tb_port *port, enum tb_link_width width * (according to what was previously set in tb_port_set_link_width(). * Wait for completion of the change. * - * Returns %0 in case of success, %-ETIMEDOUT if case of timeout or - * a negative errno in case of a failure. + * Return: + * * %0 - Symmetry change was successful. + * * %-ETIMEDOUT - Timeout occurred. + * * Negative errno - Other failure occurred. */ int usb4_port_asym_start(struct tb_port *port) { @@ -1641,6 +1700,8 @@ int usb4_port_asym_start(struct tb_port *port) * @ncaps: Number of elements in the caps array * * Reads the USB4 port lane margining capabilities into @caps. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target, u8 index, u32 *caps, size_t ncaps) @@ -1667,6 +1728,8 @@ int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target, * * Runs hardware lane margining on USB4 port and returns the result in * @results. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target, u8 index, const struct usb4_port_margining_params *params, @@ -1711,8 +1774,9 @@ int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target, * @results: Data word for the operation completion data * * Runs software lane margining on USB4 port. Read back the error - * counters by calling usb4_port_sw_margin_errors(). Returns %0 in - * success and negative errno otherwise. + * counters by calling usb4_port_sw_margin_errors(). + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target, u8 index, const struct usb4_port_margining_params *params, @@ -1759,7 +1823,8 @@ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target, * @errors: Error metadata is copied here. * * This reads back the software margining error counters from the port. - * Returns %0 in success and negative errno otherwise. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target, u8 index, u32 *errors) @@ -1790,6 +1855,8 @@ static inline int usb4_port_retimer_op(struct tb_port *port, u8 index, * * Enables sideband channel transations on SBTX. Can be used when USB4 * link does not go up, for example if there is no device connected. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index) { @@ -1817,6 +1884,8 @@ int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index) * * Disables sideband channel transations on SBTX. The reverse of * usb4_port_retimer_set_inbound_sbtx(). + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index) { @@ -1829,10 +1898,12 @@ int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index) * @port: USB4 port * @index: Retimer index * - * If the retimer at @index is last one (connected directly to the - * Type-C port) this function returns %1. If it is not returns %0. If - * the retimer is not present returns %-ENODEV. Otherwise returns - * negative errno. + * Return: + * * %1 - Retimer at @index is the last one (connected directly to the + * Type-C port). + * * %0 - Retimer at @index is not the last one. + * * %-ENODEV - Retimer is not present. + * * Negative errno - Other failure occurred. */ int usb4_port_retimer_is_last(struct tb_port *port, u8 index) { @@ -1854,9 +1925,11 @@ int usb4_port_retimer_is_last(struct tb_port *port, u8 index) * @port: USB4 port * @index: Retimer index * - * If the retimer at @index is last cable retimer this function returns - * %1 and %0 if it is on-board retimer. In case a retimer is not present - * at @index returns %-ENODEV. Otherwise returns negative errno. + * Return: + * * %1 - Retimer at @index is the last cable retimer. + * * %0 - Retimer at @index is on-board retimer. + * * %-ENODEV - Retimer is not present. + * * Negative errno - Other failure occurred. */ int usb4_port_retimer_is_cable(struct tb_port *port, u8 index) { @@ -1880,9 +1953,12 @@ int usb4_port_retimer_is_cable(struct tb_port *port, u8 index) * * Reads NVM sector size (in bytes) of a retimer at @index. This * operation can be used to determine whether the retimer supports NVM - * upgrade for example. Returns sector size in bytes or negative errno - * in case of error. Specifically returns %-ENODEV if there is no - * retimer at @index. + * upgrade for example. + * + * Return: + * * Sector size in bytes. + * * %-ENODEV - If there is no retimer at @index. + * * Negative errno - In case of an error. */ int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index) { @@ -1908,7 +1984,7 @@ int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index) * Exlicitly sets NVM write offset. Normally when writing to NVM this is * done automatically by usb4_port_retimer_nvm_write(). * - * Returns %0 in success and negative errno if there was a failure. + * Return: %0 on success, negative errno otherwise. */ int usb4_port_retimer_nvm_set_offset(struct tb_port *port, u8 index, unsigned int address) @@ -1961,9 +2037,12 @@ static int usb4_port_retimer_nvm_write_next_block(void *data, * @size: Size in bytes how much to write * * Writes @size bytes from @buf to the retimer NVM. Used for NVM - * upgrade. Returns %0 if the data was written successfully and negative - * errno in case of failure. Specifically returns %-ENODEV if there is - * no retimer at @index. + * upgrade. + * + * Return: + * * %0 - If the data was written successfully. + * * %-ENODEV - If there is no retimer at @index. + * * Negative errno - In case of an error. */ int usb4_port_retimer_nvm_write(struct tb_port *port, u8 index, unsigned int address, const void *buf, size_t size) @@ -1989,6 +2068,8 @@ int usb4_port_retimer_nvm_write(struct tb_port *port, u8 index, unsigned int add * successful the retimer restarts with the new NVM and may not have the * index set so one needs to call usb4_port_enumerate_retimers() to * force index to be assigned. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_retimer_nvm_authenticate(struct tb_port *port, u8 index) { @@ -2013,9 +2094,9 @@ int usb4_port_retimer_nvm_authenticate(struct tb_port *port, u8 index) * This can be called after usb4_port_retimer_nvm_authenticate() and * usb4_port_enumerate_retimers() to fetch status of the NVM upgrade. * - * Returns %0 if the authentication status was successfully read. The + * Return: %0 if the authentication status was successfully read. The * completion metadata (the result) is then stored into @status. If - * reading the status fails, returns negative errno. + * status read fails, returns negative errno. */ int usb4_port_retimer_nvm_authenticate_status(struct tb_port *port, u8 index, u32 *status) @@ -2083,9 +2164,12 @@ static int usb4_port_retimer_nvm_read_block(void *data, unsigned int dwaddress, * @buf: Data read from NVM is stored here * @size: Number of bytes to read * - * Reads retimer NVM and copies the contents to @buf. Returns %0 if the - * read was successful and negative errno in case of failure. - * Specifically returns %-ENODEV if there is no retimer at @index. + * Reads retimer NVM and copies the contents to @buf. + * + * Return: + * * %0 - If the read was successful. + * * %-ENODEV - If there is no retimer at @index. + * * Negative errno - In case of an error. */ int usb4_port_retimer_nvm_read(struct tb_port *port, u8 index, unsigned int address, void *buf, size_t size) @@ -2109,8 +2193,8 @@ usb4_usb3_port_max_bandwidth(const struct tb_port *port, unsigned int bw) * usb4_usb3_port_max_link_rate() - Maximum support USB3 link rate * @port: USB3 adapter port * - * Return maximum supported link rate of a USB3 adapter in Mb/s. - * Negative errno in case of error. + * Return: Maximum supported link rate of a USB3 adapter in Mb/s. + * Negative errno in case of an error. */ int usb4_usb3_port_max_link_rate(struct tb_port *port) { @@ -2228,8 +2312,9 @@ static int usb4_usb3_port_read_allocated_bandwidth(struct tb_port *port, * @downstream_bw: Allocated downstream bandwidth is stored here * * Stores currently allocated USB3 bandwidth into @upstream_bw and - * @downstream_bw in Mb/s. Returns %0 in case of success and negative - * errno in failure. + * @downstream_bw in Mb/s. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_usb3_port_allocated_bandwidth(struct tb_port *port, int *upstream_bw, int *downstream_bw) @@ -2331,8 +2416,7 @@ static int usb4_usb3_port_write_allocated_bandwidth(struct tb_port *port, * cannot be taken away by CM). The actual new values are returned in * @upstream_bw and @downstream_bw. * - * Returns %0 in case of success and negative errno if there was a - * failure. + * Return: %0 on success, negative errno otherwise. */ int usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw, int *downstream_bw) @@ -2374,7 +2458,7 @@ int usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw, * Releases USB3 allocated bandwidth down to what is actually consumed. * The new bandwidth is returned in @upstream_bw and @downstream_bw. * - * Returns 0% in success and negative errno in case of failure. + * Return: %0 on success, negative errno otherwise. */ int usb4_usb3_port_release_bandwidth(struct tb_port *port, int *upstream_bw, int *downstream_bw) @@ -2426,9 +2510,12 @@ static bool is_usb4_dpin(const struct tb_port *port) * @port: DP IN adapter * @cm_id: CM ID to assign * - * Sets CM ID for the @port. Returns %0 on success and negative errno - * otherwise. Speficially returns %-EOPNOTSUPP if the @port does not - * support this. + * Sets CM ID for the @port. + * + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the @port does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id) { @@ -2455,8 +2542,10 @@ int usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id) * supported * @port: DP IN adapter to check * - * Can be called to any DP IN adapter. Returns true if the adapter - * supports USB4 bandwidth allocation mode, false otherwise. + * Can be called to any DP IN adapter. + * + * Return: %true if the adapter supports USB4 bandwidth allocation mode, + * %false otherwise. */ bool usb4_dp_port_bandwidth_mode_supported(struct tb_port *port) { @@ -2479,8 +2568,10 @@ bool usb4_dp_port_bandwidth_mode_supported(struct tb_port *port) * enabled * @port: DP IN adapter to check * - * Can be called to any DP IN adapter. Returns true if the bandwidth - * allocation mode has been enabled, false otherwise. + * Can be called to any DP IN adapter. + * + * Return: %true if the bandwidth allocation mode has been enabled, + * %false otherwise. */ bool usb4_dp_port_bandwidth_mode_enabled(struct tb_port *port) { @@ -2505,9 +2596,12 @@ bool usb4_dp_port_bandwidth_mode_enabled(struct tb_port *port) * @supported: Does the CM support bandwidth allocation mode * * Can be called to any DP IN adapter. Sets or clears the CM support bit - * of the DP IN adapter. Returns %0 in success and negative errno - * otherwise. Specifically returns %-OPNOTSUPP if the passed in adapter - * does not support this. + * of the DP IN adapter. + * + * * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the passed IN adapter does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_set_cm_bandwidth_mode_supported(struct tb_port *port, bool supported) @@ -2537,8 +2631,12 @@ int usb4_dp_port_set_cm_bandwidth_mode_supported(struct tb_port *port, * @port: DP IN adapter * * Reads bandwidth allocation Group ID from the DP IN adapter and - * returns it. If the adapter does not support setting Group_ID - * %-EOPNOTSUPP is returned. + * returns it. + * + * Return: + * * Group ID assigned to adapter @port. + * * %-EOPNOTSUPP - If adapter does not support setting GROUP_ID. + * * Negative errno - Another error occurred. */ int usb4_dp_port_group_id(struct tb_port *port) { @@ -2562,9 +2660,11 @@ int usb4_dp_port_group_id(struct tb_port *port) * @group_id: Group ID for the adapter * * Sets bandwidth allocation mode Group ID for the DP IN adapter. - * Returns %0 in case of success and negative errno otherwise. - * Specifically returns %-EOPNOTSUPP if the adapter does not support - * this. + * + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the adapter does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_set_group_id(struct tb_port *port, int group_id) { @@ -2592,9 +2692,12 @@ int usb4_dp_port_set_group_id(struct tb_port *port, int group_id) * @rate: Non-reduced rate in Mb/s is placed here * @lanes: Non-reduced lanes are placed here * - * Reads the non-reduced rate and lanes from the DP IN adapter. Returns - * %0 in success and negative errno otherwise. Specifically returns - * %-EOPNOTSUPP if the adapter does not support this. + * Reads the non-reduced rate and lanes from the DP IN adapter. + * + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the adapter does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_nrd(struct tb_port *port, int *rate, int *lanes) { @@ -2647,10 +2750,13 @@ int usb4_dp_port_nrd(struct tb_port *port, int *rate, int *lanes) * @rate: Non-reduced rate in Mb/s * @lanes: Non-reduced lanes * - * Before the capabilities reduction this function can be used to set - * the non-reduced values for the DP IN adapter. Returns %0 in success - * and negative errno otherwise. If the adapter does not support this - * %-EOPNOTSUPP is returned. + * Before the capabilities reduction, this function can be used to set + * the non-reduced values for the DP IN adapter. + * + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the adapter does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_set_nrd(struct tb_port *port, int rate, int lanes) { @@ -2709,9 +2815,13 @@ int usb4_dp_port_set_nrd(struct tb_port *port, int rate, int lanes) * usb4_dp_port_granularity() - Return granularity for the bandwidth values * @port: DP IN adapter * - * Reads the programmed granularity from @port. If the DP IN adapter does - * not support bandwidth allocation mode returns %-EOPNOTSUPP and negative - * errno in other error cases. + * Reads the programmed granularity from @port. + * + * Return: + * * Granularity value of a @port. + * * %-EOPNOTSUPP - If the DP IN adapter does not support bandwidth + * allocation mode. + * * Negative errno - Another error occurred. */ int usb4_dp_port_granularity(struct tb_port *port) { @@ -2747,8 +2857,12 @@ int usb4_dp_port_granularity(struct tb_port *port) * @granularity: Granularity in Mb/s. Supported values: 1000, 500 and 250. * * Sets the granularity used with the estimated, allocated and requested - * bandwidth. Returns %0 in success and negative errno otherwise. If the - * adapter does not support this %-EOPNOTSUPP is returned. + * bandwidth. + * + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the adapter does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_set_granularity(struct tb_port *port, int granularity) { @@ -2789,10 +2903,13 @@ int usb4_dp_port_set_granularity(struct tb_port *port, int granularity) * @bw: Estimated bandwidth in Mb/s. * * Sets the estimated bandwidth to @bw. Set the granularity by calling - * usb4_dp_port_set_granularity() before calling this. The @bw is round - * down to the closest granularity multiplier. Returns %0 in success - * and negative errno otherwise. Specifically returns %-EOPNOTSUPP if - * the adapter does not support this. + * usb4_dp_port_set_granularity() before calling this. The @bw is rounded + * down to the closest granularity multiplier. + * + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the adapter does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_set_estimated_bandwidth(struct tb_port *port, int bw) { @@ -2823,9 +2940,10 @@ int usb4_dp_port_set_estimated_bandwidth(struct tb_port *port, int bw) * usb4_dp_port_allocated_bandwidth() - Return allocated bandwidth * @port: DP IN adapter * - * Reads and returns allocated bandwidth for @port in Mb/s (taking into - * account the programmed granularity). Returns negative errno in case - * of error. + * Reads the allocated bandwidth for @port in Mb/s (taking into account + * the programmed granularity). + * + * Return: Allocated bandwidth in Mb/s or negative errno in case of an error. */ int usb4_dp_port_allocated_bandwidth(struct tb_port *port) { @@ -2920,8 +3038,9 @@ static int usb4_dp_port_wait_and_clear_cm_ack(struct tb_port *port, * @bw: New allocated bandwidth in Mb/s * * Communicates the new allocated bandwidth with the DPCD (graphics - * driver). Takes into account the programmed granularity. Returns %0 in - * success and negative errno in case of error. + * driver). Takes into account the programmed granularity. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_dp_port_allocate_bandwidth(struct tb_port *port, int bw) { @@ -2961,10 +3080,15 @@ int usb4_dp_port_allocate_bandwidth(struct tb_port *port, int bw) * @port: DP IN adapter * * Reads the DPCD (graphics driver) requested bandwidth and returns it - * in Mb/s. Takes the programmed granularity into account. In case of - * error returns negative errno. Specifically returns %-EOPNOTSUPP if - * the adapter does not support bandwidth allocation mode, and %ENODATA - * if there is no active bandwidth request from the graphics driver. + * in Mb/s. Takes the programmed granularity into account. + * + * Return: + * * Requested bandwidth in Mb/s - On success. + * * %-EOPNOTSUPP - If the adapter does not support bandwidth allocation + * mode. + * * %ENODATA - If there is no active bandwidth request from the graphics + * driver. + * * Negative errno - On failure. */ int usb4_dp_port_requested_bandwidth(struct tb_port *port) { @@ -2996,8 +3120,9 @@ int usb4_dp_port_requested_bandwidth(struct tb_port *port) * @enable: Enable/disable extended encapsulation * * Enables or disables extended encapsulation used in PCIe tunneling. Caller - * needs to make sure both adapters support this before enabling. Returns %0 on - * success and negative errno otherwise. + * needs to make sure both adapters support this before enabling. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_pci_port_set_ext_encapsulation(struct tb_port *port, bool enable) { From a2ba553cd45a5fb33f0edc6dd8c6b5280cad4ab0 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:44 +0200 Subject: [PATCH 105/132] thunderbolt: Update usb4_port.c function documentation Make usb4_port.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/usb4_port.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c index 852a45fcd19d..b5e06237261b 100644 --- a/drivers/thunderbolt/usb4_port.c +++ b/drivers/thunderbolt/usb4_port.c @@ -296,8 +296,9 @@ const struct device_type usb4_port_device_type = { * usb4_port_device_add() - Add USB4 port device * @port: Lane 0 adapter port to add the USB4 port * - * Creates and registers a USB4 port device for @port. Returns the new - * USB4 port device pointer or ERR_PTR() in case of error. + * Creates and registers a USB4 port device for @port. + * + * Return: Pointer to &struct usb4_port or ERR_PTR() in case of an error. */ struct usb4_port *usb4_port_device_add(struct tb_port *port) { @@ -356,6 +357,8 @@ void usb4_port_device_remove(struct usb4_port *usb4) * @usb4: USB4 port device * * Used to resume USB4 port device after sleep state. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_device_resume(struct usb4_port *usb4) { From 81a1962cb281636a95c49f02ef57d37deb6ceb8f Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:45 +0200 Subject: [PATCH 106/132] thunderbolt: Update xdomain.c function documentation Make xdomain.c function documentation compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- drivers/thunderbolt/xdomain.c | 46 +++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index fffa28cc491d..9d220ba544ec 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -160,7 +160,7 @@ static int __tb_xdomain_response(struct tb_ctl *ctl, const void *response, * This can be used to send a XDomain response message to the other * domain. No response for the message is expected. * - * Return: %0 in case of success and negative errno in case of failure + * Return: %0 on success, negative errno otherwise. */ int tb_xdomain_response(struct tb_xdomain *xd, const void *response, size_t size, enum tb_cfg_pkg_type type) @@ -212,7 +212,7 @@ static int __tb_xdomain_request(struct tb_ctl *ctl, const void *request, * the other domain. The function waits until the response is received * or when timeout triggers. Whichever comes first. * - * Return: %0 in case of success and negative errno in case of failure + * Return: %0 on success, negative errno otherwise. */ int tb_xdomain_request(struct tb_xdomain *xd, const void *request, size_t request_size, enum tb_cfg_pkg_type request_type, @@ -613,6 +613,8 @@ static int tb_xdp_link_state_change_response(struct tb_ctl *ctl, u64 route, * messages. After this function is called the service driver needs to * be able to handle calls to callback whenever a package with the * registered protocol is received. + * + * Return: %0 on success, negative errno otherwise. */ int tb_register_protocol_handler(struct tb_protocol_handler *handler) { @@ -877,6 +879,8 @@ tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr, * @drv: Driver to register * * Registers new service driver from @drv to the bus. + * + * Return: %0 on success, negative errno otherwise. */ int tb_register_service_driver(struct tb_service_driver *drv) { @@ -1955,6 +1959,8 @@ static void tb_xdomain_link_exit(struct tb_xdomain *xd) * * Allocates new XDomain structure and returns pointer to that. The * object must be released by calling tb_xdomain_put(). + * + * Return: Pointer to &struct tb_xdomain, %NULL in case of failure. */ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent, u64 route, const uuid_t *local_uuid, @@ -2091,7 +2097,7 @@ void tb_xdomain_remove(struct tb_xdomain *xd) * to enable bonding by first enabling the port and waiting for the CL0 * state. * - * Return: %0 in case of success and negative errno in case of error. + * Return: %0 on success, negative errno otherwise. */ int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd) { @@ -2171,10 +2177,14 @@ EXPORT_SYMBOL_GPL(tb_xdomain_lane_bonding_disable); * @xd: XDomain connection * @hopid: Preferred HopID or %-1 for next available * - * Returns allocated HopID or negative errno. Specifically returns - * %-ENOSPC if there are no more available HopIDs. Returned HopID is - * guaranteed to be within range supported by the input lane adapter. + * Returned HopID is guaranteed to be within range supported by the input + * lane adapter. * Call tb_xdomain_release_in_hopid() to release the allocated HopID. + * + * Return: + * * Allocated HopID - On success. + * * %-ENOSPC - If there are no more available HopIDs. + * * Negative errno - Another error occurred. */ int tb_xdomain_alloc_in_hopid(struct tb_xdomain *xd, int hopid) { @@ -2193,10 +2203,14 @@ EXPORT_SYMBOL_GPL(tb_xdomain_alloc_in_hopid); * @xd: XDomain connection * @hopid: Preferred HopID or %-1 for next available * - * Returns allocated HopID or negative errno. Specifically returns - * %-ENOSPC if there are no more available HopIDs. Returned HopID is - * guaranteed to be within range supported by the output lane adapter. - * Call tb_xdomain_release_in_hopid() to release the allocated HopID. + * Returned HopID is guaranteed to be within range supported by the + * output lane adapter. + * Call tb_xdomain_release_out_hopid() to release the allocated HopID. + * + * Return: + * * Allocated HopID - On success. + * * %-ENOSPC - If there are no more available HopIDs. + * * Negative errno - Another error occurred. */ int tb_xdomain_alloc_out_hopid(struct tb_xdomain *xd, int hopid) { @@ -2245,7 +2259,7 @@ EXPORT_SYMBOL_GPL(tb_xdomain_release_out_hopid); * path. If a transmit or receive path is not needed, pass %-1 for those * parameters. * - * Return: %0 in case of success and negative errno in case of error + * Return: %0 on success, negative errno otherwise. */ int tb_xdomain_enable_paths(struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, @@ -2270,7 +2284,7 @@ EXPORT_SYMBOL_GPL(tb_xdomain_enable_paths); * as path/ring parameter means don't care. Normally the callers should * pass the same values here as they do when paths are enabled. * - * Return: %0 in case of success and negative errno in case of error + * Return: %0 on success, negative errno otherwise. */ int tb_xdomain_disable_paths(struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, @@ -2335,6 +2349,8 @@ static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw, * to the bus (handshake is still in progress). * * The caller needs to hold @tb->lock. + * + * Return: Pointer to &struct tb_xdomain or %NULL if not found. */ struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const uuid_t *uuid) { @@ -2364,6 +2380,8 @@ EXPORT_SYMBOL_GPL(tb_xdomain_find_by_uuid); * to the bus (handshake is still in progress). * * The caller needs to hold @tb->lock. + * + * Return: Pointer to &struct tb_xdomain or %NULL if not found. */ struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link, u8 depth) @@ -2393,6 +2411,8 @@ struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link, * to the bus (handshake is still in progress). * * The caller needs to hold @tb->lock. + * + * Return: Pointer to &struct tb_xdomain or %NULL if not found. */ struct tb_xdomain *tb_xdomain_find_by_route(struct tb *tb, u64 route) { @@ -2491,7 +2511,7 @@ static bool remove_directory(const char *key, const struct tb_property_dir *dir) * notified so they can re-read properties of this host if they are * interested. * - * Return: %0 on success and negative errno on failure + * Return: %0 on success, negative errno otherwise. */ int tb_register_property_dir(const char *key, struct tb_property_dir *dir) { From ea6bb47fd6a4c5a332f9349c39bf7462e3e7a35b Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 27 Aug 2025 13:56:46 +0200 Subject: [PATCH 107/132] thunderbolt: Update thunderbolt.h header file Make Thunderbolt header file compliant with current kernel-doc standards. No functional changes. Signed-off-by: Alan Borzeszkowski Signed-off-by: Mika Westerberg --- include/linux/thunderbolt.h | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 75247486616b..0ba112175bb3 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -213,7 +213,7 @@ enum tb_link_width { * queried first * @service_ids: Used to generate IDs for the services * @in_hopids: Input HopIDs for DMA tunneling - * @out_hopids; Output HopIDs for DMA tunneling + * @out_hopids: Output HopIDs for DMA tunneling * @local_property_block: Local block of properties * @local_property_block_gen: Generation of @local_property_block * @local_property_block_len: Length of the @local_property_block in dwords @@ -356,7 +356,7 @@ int tb_xdomain_request(struct tb_xdomain *xd, const void *request, unsigned int timeout_msec); /** - * tb_protocol_handler - Protocol specific handler + * struct tb_protocol_handler - Protocol specific handler * @uuid: XDomain messages with this UUID are dispatched to this handler * @callback: Callback called with the XDomain message. Returning %1 * here tells the XDomain core that the message was handled @@ -437,7 +437,7 @@ static inline struct tb_service *tb_to_service(struct device *dev) } /** - * tb_service_driver - Thunderbolt service driver + * struct tb_service_driver - Thunderbolt service driver * @driver: Driver structure * @probe: Called when the driver is probed * @remove: Called when the driver is removed (optional) @@ -519,6 +519,7 @@ struct tb_nhi { * @head: Head of the ring (write next descriptor here) * @tail: Tail of the ring (complete next descriptor here) * @descriptors: Allocated descriptors for this ring + * @descriptors_dma: DMA address of descriptors for this ring * @queue: Queue holding frames to be transferred over this ring * @in_flight: Queue holding frames that are currently in flight * @work: Interrupt work structure @@ -571,12 +572,12 @@ typedef void (*ring_cb)(struct tb_ring *, struct ring_frame *, bool canceled); /** * enum ring_desc_flags - Flags for DMA ring descriptor - * %RING_DESC_ISOCH: Enable isonchronous DMA (Tx only) - * %RING_DESC_CRC_ERROR: In frame mode CRC check failed for the frame (Rx only) - * %RING_DESC_COMPLETED: Descriptor completed (set by NHI) - * %RING_DESC_POSTED: Always set this - * %RING_DESC_BUFFER_OVERRUN: RX buffer overrun - * %RING_DESC_INTERRUPT: Request an interrupt on completion + * @RING_DESC_ISOCH: Enable isonchronous DMA (Tx only) + * @RING_DESC_CRC_ERROR: In frame mode CRC check failed for the frame (Rx only) + * @RING_DESC_COMPLETED: Descriptor completed (set by NHI) + * @RING_DESC_POSTED: Always set this + * @RING_DESC_BUFFER_OVERRUN: RX buffer overrun + * @RING_DESC_INTERRUPT: Request an interrupt on completion */ enum ring_desc_flags { RING_DESC_ISOCH = 0x1, @@ -636,7 +637,7 @@ int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame); * If ring_stop() is called after the packet has been enqueued * @frame->callback will be called with canceled set to true. * - * Return: Returns %-ESHUTDOWN if ring_stop has been called. Zero otherwise. + * Return: %-ESHUTDOWN if ring_stop() has been called, %0 otherwise. */ static inline int tb_ring_rx(struct tb_ring *ring, struct ring_frame *frame) { @@ -657,7 +658,7 @@ static inline int tb_ring_rx(struct tb_ring *ring, struct ring_frame *frame) * If ring_stop() is called after the packet has been enqueued @frame->callback * will be called with canceled set to true. * - * Return: Returns %-ESHUTDOWN if ring_stop has been called. Zero otherwise. + * Return: %-ESHUTDOWN if ring_stop has been called, %0 otherwise. */ static inline int tb_ring_tx(struct tb_ring *ring, struct ring_frame *frame) { @@ -675,6 +676,8 @@ void tb_ring_poll_complete(struct tb_ring *ring); * * Use this function when you are mapping DMA for buffers that are * passed to the ring for sending/receiving. + * + * Return: Pointer to device used for DMA mapping. */ static inline struct device *tb_ring_dma_device(struct tb_ring *ring) { From 7d547c1c249872b7732767e483758cb4fcb2d99b Mon Sep 17 00:00:00 2001 From: Ze Huang Date: Sat, 13 Sep 2025 00:53:47 +0800 Subject: [PATCH 108/132] dt-bindings: usb: dwc3: add support for SpacemiT K1 Add support for the USB 3.0 Dual-Role Device (DRD) controller embedded in the SpacemiT K1 SoC. The controller is based on the Synopsys DesignWare Core USB 3 (DWC3) IP, supporting USB3.0 host mode and USB 2.0 DRD mode. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Ze Huang Link: https://lore.kernel.org/r/20250913-dwc3_generic-v8-1-b50f81f05f95@linux.dev Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250913-dwc3_generic-v8-1-b50f81f05f95@linux.dev --- .../bindings/usb/spacemit,k1-dwc3.yaml | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/spacemit,k1-dwc3.yaml diff --git a/Documentation/devicetree/bindings/usb/spacemit,k1-dwc3.yaml b/Documentation/devicetree/bindings/usb/spacemit,k1-dwc3.yaml new file mode 100644 index 000000000000..0f0b5e061ca1 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/spacemit,k1-dwc3.yaml @@ -0,0 +1,121 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/spacemit,k1-dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SpacemiT K1 SuperSpeed DWC3 USB SoC Controller + +maintainers: + - Ze Huang + +description: | + The SpacemiT K1 embeds a DWC3 USB IP Core which supports Host functions + for USB 3.0 and DRD for USB 2.0. + + Key features: + - USB3.0 SuperSpeed and USB2.0 High/Full/Low-Speed support + - Supports low-power modes (USB2.0 suspend, USB3.0 U1/U2/U3) + - Internal DMA controller and flexible endpoint FIFO sizing + + Communication Interface: + - Use of PIPE3 (125MHz) interface for USB3.0 PHY + - Use of UTMI+ (30/60MHz) interface for USB2.0 PHY + +allOf: + - $ref: snps,dwc3-common.yaml# + +properties: + compatible: + const: spacemit,k1-dwc3 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + const: usbdrd30 + + interrupts: + maxItems: 1 + + phys: + items: + - description: phandle to USB2/HS PHY + - description: phandle to USB3/SS PHY + + phy-names: + items: + - const: usb2-phy + - const: usb3-phy + + resets: + items: + - description: USB3.0 AHB reset + - description: USB3.0 VCC reset + - description: USB3.0 PHY reset + + reset-names: + items: + - const: ahb + - const: vcc + - const: phy + + reset-delay: + $ref: /schemas/types.yaml#/definitions/uint32 + default: 2 + description: delay after reset sequence [us] + + vbus-supply: + description: A phandle to the regulator supplying the VBUS voltage. + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - phys + - phy-names + - resets + - reset-names + +unevaluatedProperties: false + +examples: + - | + usb@c0a00000 { + compatible = "spacemit,k1-dwc3"; + reg = <0xc0a00000 0x10000>; + clocks = <&syscon_apmu 16>; + clock-names = "usbdrd30"; + interrupts = <125>; + phys = <&usb2phy>, <&usb3phy>; + phy-names = "usb2-phy", "usb3-phy"; + resets = <&syscon_apmu 8>, + <&syscon_apmu 9>, + <&syscon_apmu 10>; + reset-names = "ahb", "vcc", "phy"; + reset-delay = <2>; + vbus-supply = <&usb3_vbus>; + #address-cells = <1>; + #size-cells = <0>; + + hub_2_0: hub@1 { + compatible = "usb2109,2817"; + reg = <1>; + vdd-supply = <&usb3_vhub>; + peer-hub = <&hub_3_0>; + reset-gpios = <&gpio 3 28 1>; + }; + + hub_3_0: hub@2 { + compatible = "usb2109,817"; + reg = <2>; + vdd-supply = <&usb3_vhub>; + peer-hub = <&hub_2_0>; + reset-gpios = <&gpio 3 28 1>; + }; + }; From e0b6dc00c701e600e655417aab1e100b73de821a Mon Sep 17 00:00:00 2001 From: Ze Huang Date: Sat, 13 Sep 2025 00:53:48 +0800 Subject: [PATCH 109/132] usb: dwc3: add generic driver to support flattened To support flattened dwc3 dt model and drop the glue layer, introduce the `dwc3-generic` driver. This enables direct binding of the DWC3 core driver and offers an alternative to the existing glue driver `dwc3-of-simple`. Acked-by: Thinh Nguyen Signed-off-by: Ze Huang Link: https://lore.kernel.org/r/20250913-dwc3_generic-v8-2-b50f81f05f95@linux.dev Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250913-dwc3_generic-v8-2-b50f81f05f95@linux.dev --- drivers/usb/dwc3/Kconfig | 11 ++ drivers/usb/dwc3/Makefile | 1 + drivers/usb/dwc3/dwc3-generic-plat.c | 166 +++++++++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 drivers/usb/dwc3/dwc3-generic-plat.c diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 310d182e10b5..4925d15084f8 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -189,4 +189,15 @@ config USB_DWC3_RTK or dual-role mode. Say 'Y' or 'M' if you have such device. +config USB_DWC3_GENERIC_PLAT + tristate "DWC3 Generic Platform Driver" + depends on OF && COMMON_CLK + default USB_DWC3 + help + Support USB3 functionality in simple SoC integrations. + Currently supports SpacemiT DWC USB3. Platforms using + dwc3-of-simple can easily switch to dwc3-generic by flattening + the dwc3 child node in the device tree. + Say 'Y' or 'M' here if your platform integrates DWC3 in a similar way. + endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 830e6c9e5fe0..96469e48ff9d 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -57,3 +57,4 @@ obj-$(CONFIG_USB_DWC3_IMX8MP) += dwc3-imx8mp.o obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o obj-$(CONFIG_USB_DWC3_OCTEON) += dwc3-octeon.o obj-$(CONFIG_USB_DWC3_RTK) += dwc3-rtk.o +obj-$(CONFIG_USB_DWC3_GENERIC_PLAT) += dwc3-generic-plat.o diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c new file mode 100644 index 000000000000..d96b20570002 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-generic-plat.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dwc3-generic-plat.c - DesignWare USB3 generic platform driver + * + * Copyright (C) 2025 Ze Huang + * + * Inspired by dwc3-qcom.c and dwc3-of-simple.c + */ + +#include +#include +#include +#include "glue.h" + +struct dwc3_generic { + struct device *dev; + struct dwc3 dwc; + struct clk_bulk_data *clks; + int num_clocks; + struct reset_control *resets; +}; + +#define to_dwc3_generic(d) container_of((d), struct dwc3_generic, dwc) + +static void dwc3_generic_reset_control_assert(void *data) +{ + reset_control_assert(data); +} + +static int dwc3_generic_probe(struct platform_device *pdev) +{ + struct dwc3_probe_data probe_data = {}; + struct device *dev = &pdev->dev; + struct dwc3_generic *dwc3g; + struct resource *res; + int ret; + + dwc3g = devm_kzalloc(dev, sizeof(*dwc3g), GFP_KERNEL); + if (!dwc3g) + return -ENOMEM; + + dwc3g->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory resource\n"); + return -ENODEV; + } + + dwc3g->resets = devm_reset_control_array_get_optional_exclusive(dev); + if (IS_ERR(dwc3g->resets)) + return dev_err_probe(dev, PTR_ERR(dwc3g->resets), "failed to get resets\n"); + + ret = reset_control_assert(dwc3g->resets); + if (ret) + return dev_err_probe(dev, ret, "failed to assert resets\n"); + + /* Not strict timing, just for safety */ + udelay(2); + + ret = reset_control_deassert(dwc3g->resets); + if (ret) + return dev_err_probe(dev, ret, "failed to deassert resets\n"); + + ret = devm_add_action_or_reset(dev, dwc3_generic_reset_control_assert, dwc3g->resets); + if (ret) + return ret; + + ret = devm_clk_bulk_get_all_enabled(dwc3g->dev, &dwc3g->clks); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get clocks\n"); + + dwc3g->num_clocks = ret; + dwc3g->dwc.dev = dev; + probe_data.dwc = &dwc3g->dwc; + probe_data.res = res; + probe_data.ignore_clocks_and_resets = true; + ret = dwc3_core_probe(&probe_data); + if (ret) + return dev_err_probe(dev, ret, "failed to register DWC3 Core\n"); + + return 0; +} + +static void dwc3_generic_remove(struct platform_device *pdev) +{ + struct dwc3 *dwc = platform_get_drvdata(pdev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + + dwc3_core_remove(dwc); + + clk_bulk_disable_unprepare(dwc3g->num_clocks, dwc3g->clks); +} + +static int dwc3_generic_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + int ret; + + ret = dwc3_pm_suspend(dwc); + if (ret) + return ret; + + clk_bulk_disable_unprepare(dwc3g->num_clocks, dwc3g->clks); + + return 0; +} + +static int dwc3_generic_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + int ret; + + ret = clk_bulk_prepare_enable(dwc3g->num_clocks, dwc3g->clks); + if (ret) + return ret; + + ret = dwc3_pm_resume(dwc); + if (ret) + return ret; + + return 0; +} + +static int dwc3_generic_runtime_suspend(struct device *dev) +{ + return dwc3_runtime_suspend(dev_get_drvdata(dev)); +} + +static int dwc3_generic_runtime_resume(struct device *dev) +{ + return dwc3_runtime_resume(dev_get_drvdata(dev)); +} + +static int dwc3_generic_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); +} + +static const struct dev_pm_ops dwc3_generic_dev_pm_ops = { + SYSTEM_SLEEP_PM_OPS(dwc3_generic_suspend, dwc3_generic_resume) + RUNTIME_PM_OPS(dwc3_generic_runtime_suspend, dwc3_generic_runtime_resume, + dwc3_generic_runtime_idle) +}; + +static const struct of_device_id dwc3_generic_of_match[] = { + { .compatible = "spacemit,k1-dwc3", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dwc3_generic_of_match); + +static struct platform_driver dwc3_generic_driver = { + .probe = dwc3_generic_probe, + .remove = dwc3_generic_remove, + .driver = { + .name = "dwc3-generic-plat", + .of_match_table = dwc3_generic_of_match, + .pm = pm_ptr(&dwc3_generic_dev_pm_ops), + }, +}; +module_platform_driver(dwc3_generic_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DesignWare USB3 generic platform driver"); From 41cf11946b9076383a2222bbf1ef57d64d033f66 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Tue, 16 Sep 2025 17:34:36 +0530 Subject: [PATCH 110/132] usb: xhci: plat: Facilitate using autosuspend for xhci plat devices Allow autosuspend to be used by xhci plat device. For Qualcomm SoCs, when in host mode, it is intended that the controller goes to suspend state to save power and wait for interrupts from connected peripheral to wake it up. This is particularly used in cases where a HID or Audio device is connected. In such scenarios, the usb controller can enter auto suspend and resume action after getting interrupts from the connected device. Signed-off-by: Krishna Kurapati Link: https://lore.kernel.org/r/20250916120436.3617598-1-krishna.kurapati@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 3a56d8f94519..b3f895505cbe 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -172,6 +172,7 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s return ret; pm_runtime_set_active(&pdev->dev); + pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_enable(&pdev->dev); pm_runtime_get_noresume(&pdev->dev); From 368ed48a5ef52e384f54d5809f0a0b79ac567479 Mon Sep 17 00:00:00 2001 From: Forest Crossman Date: Mon, 15 Sep 2025 15:55:10 -0400 Subject: [PATCH 111/132] usb: mon: Increase BUFF_MAX to 64 MiB to support multi-MB URBs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The usbmon binary interface currently truncates captures of large transfers from higher-speed USB devices. Because a single event capture is limited to one-fifth of the total buffer size, the current maximum size of a captured URB is around 240 KiB. This is insufficient when capturing traffic from modern devices that use transfers of several hundred kilobytes or more, as truncated URBs can make it impossible for user-space USB analysis tools like Wireshark to properly defragment and reassemble higher-level protocol packets in the captured data. The root cause of this issue is the 1200 KiB BUFF_MAX limit, which has not been changed since the binary interface was introduced in 2006. To resolve this issue, this patch increases BUFF_MAX to 64 MiB. The original comment for BUFF_MAX based the limit's calculation on a saturated 480 Mbit/s bus. Applying the same logic to a modern USB 3.2 Gen 2×2 20 Gbit/s bus (~2500 MB/s over a 20ms window) indicates the buffer should be at least 50 MB. The new limit of 64 MiB covers that, plus a little extra for any overhead. With this change, both users and developers should now be able to debug and reverse engineer modern USB devices even when running unmodified distro kernels. Please note that this change does not affect the default buffer size. A larger buffer is only allocated when a user explicitly requests it via the MON_IOCT_RING_SIZE ioctl, so the change to the maximum buffer size should not unduly increase memory usage for users that don't deliberately request a larger buffer. Link: https://lore.kernel.org/CAO3ALPzdUkmMr0YMrODLeDSLZqNCkWcAP8NumuPHLjNJ8wC1kQ@mail.gmail.com Signed-off-by: Forest Crossman Acked-by: Alan Stern Link: https://lore.kernel.org/r/CAO3ALPxU5RzcoueC454L=WZ1qGMfAcnxm+T+p+9D8O9mcrUbCQ@mail.gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mon/mon_bin.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/usb/mon/mon_bin.c b/drivers/usb/mon/mon_bin.c index c93b43f5bc46..e713fc5964b1 100644 --- a/drivers/usb/mon/mon_bin.c +++ b/drivers/usb/mon/mon_bin.c @@ -68,18 +68,20 @@ * The magic limit was calculated so that it allows the monitoring * application to pick data once in two ticks. This way, another application, * which presumably drives the bus, gets to hog CPU, yet we collect our data. - * If HZ is 100, a 480 mbit/s bus drives 614 KB every jiffy. USB has an - * enormous overhead built into the bus protocol, so we need about 1000 KB. + * + * Originally, for a 480 Mbit/s bus this required a buffer of about 1 MB. For + * modern 20 Gbps buses, this value increases to over 50 MB. The maximum + * buffer size is set to 64 MiB to accommodate this. * * This is still too much for most cases, where we just snoop a few * descriptor fetches for enumeration. So, the default is a "reasonable" - * amount for systems with HZ=250 and incomplete bus saturation. + * amount for typical, low-throughput use cases. * * XXX What about multi-megabyte URBs which take minutes to transfer? */ -#define BUFF_MAX CHUNK_ALIGN(1200*1024) -#define BUFF_DFL CHUNK_ALIGN(300*1024) -#define BUFF_MIN CHUNK_ALIGN(8*1024) +#define BUFF_MAX CHUNK_ALIGN(64*1024*1024) +#define BUFF_DFL CHUNK_ALIGN(300*1024) +#define BUFF_MIN CHUNK_ALIGN(8*1024) /* * The per-event API header (2 per URB). From 45fe729be9a6be326a1ca25af82d34de32ba2ce8 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Tue, 16 Sep 2025 10:16:20 +0800 Subject: [PATCH 112/132] usb: typec: Stub out typec_switch APIs when CONFIG_TYPEC=n Ease driver development by adding stubs for the typec_switch APIs when CONFIG_TYPEC=n. Copy the same method used for the typec_mux APIs to be consistent. Acked-by: Greg Kroah-Hartman Reviewed-by: Heikki Krogerus Signed-off-by: Stephen Boyd Signed-off-by: Xu Yang Link: https://lore.kernel.org/r/20250916021620.1303995-1-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/typec_mux.h | 46 +++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h index 2489a7857d8e..aa9ebb7e2fe0 100644 --- a/include/linux/usb/typec_mux.h +++ b/include/linux/usb/typec_mux.h @@ -3,6 +3,7 @@ #ifndef __USB_TYPEC_MUX #define __USB_TYPEC_MUX +#include #include #include @@ -24,16 +25,13 @@ struct typec_switch_desc { void *drvdata; }; +#if IS_ENABLED(CONFIG_TYPEC) + struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode); void typec_switch_put(struct typec_switch *sw); int typec_switch_set(struct typec_switch *sw, enum typec_orientation orientation); -static inline struct typec_switch *typec_switch_get(struct device *dev) -{ - return fwnode_typec_switch_get(dev_fwnode(dev)); -} - struct typec_switch_dev * typec_switch_register(struct device *parent, const struct typec_switch_desc *desc); @@ -42,6 +40,44 @@ void typec_switch_unregister(struct typec_switch_dev *sw); void typec_switch_set_drvdata(struct typec_switch_dev *sw, void *data); void *typec_switch_get_drvdata(struct typec_switch_dev *sw); +#else + +static inline struct typec_switch * +fwnode_typec_switch_get(struct fwnode_handle *fwnode) +{ + return NULL; +} + +static inline void typec_switch_put(struct typec_switch *sw) {} + +static inline int typec_switch_set(struct typec_switch *sw, + enum typec_orientation orientation) +{ + return 0; +} + +static inline struct typec_switch_dev * +typec_switch_register(struct device *parent, + const struct typec_switch_desc *desc) +{ + return ERR_PTR(-EOPNOTSUPP); +} + +static inline void typec_switch_unregister(struct typec_switch_dev *sw) {} + +static inline void typec_switch_set_drvdata(struct typec_switch_dev *sw, void *data) {} +static inline void *typec_switch_get_drvdata(struct typec_switch_dev *sw) +{ + return ERR_PTR(-EOPNOTSUPP); +} + +#endif /* CONFIG_TYPEC */ + +static inline struct typec_switch *typec_switch_get(struct device *dev) +{ + return fwnode_typec_switch_get(dev_fwnode(dev)); +} + struct typec_mux_state { struct typec_altmode *alt; unsigned long mode; From cfd6f1a7b42f62523c96d9703ef32b0dbc495ba4 Mon Sep 17 00:00:00 2001 From: Owen Gu Date: Mon, 15 Sep 2025 17:29:07 +0800 Subject: [PATCH 113/132] usb: gadget: f_fs: Fix epfile null pointer access after ep enable. A race condition occurs when ffs_func_eps_enable() runs concurrently with ffs_data_reset(). The ffs_data_clear() called in ffs_data_reset() sets ffs->epfiles to NULL before resetting ffs->eps_count to 0, leading to a NULL pointer dereference when accessing epfile->ep in ffs_func_eps_enable() after successful usb_ep_enable(). The ffs->epfiles pointer is set to NULL in both ffs_data_clear() and ffs_data_close() functions, and its modification is protected by the spinlock ffs->eps_lock. And the whole ffs_func_eps_enable() function is also protected by ffs->eps_lock. Thus, add NULL pointer handling for ffs->epfiles in the ffs_func_eps_enable() function to fix issues Signed-off-by: Owen Gu Link: https://lore.kernel.org/r/20250915092907.17802-1-guhuinan@xiaomi.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 08a251df20c4..04058261cdd0 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -2407,7 +2407,12 @@ static int ffs_func_eps_enable(struct ffs_function *func) ep = func->eps; epfile = ffs->epfiles; count = ffs->eps_count; - while(count--) { + if (!epfile) { + ret = -ENOMEM; + goto done; + } + + while (count--) { ep->ep->driver_data = ep; ret = config_ep_by_speed(func->gadget, &func->function, ep->ep); @@ -2431,6 +2436,7 @@ static int ffs_func_eps_enable(struct ffs_function *func) } wake_up_interruptible(&ffs->wait); +done: spin_unlock_irqrestore(&func->ffs->eps_lock, flags); return ret; From dd0d2618e3f815a030622599e540d3be1964a888 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Tue, 16 Sep 2025 16:02:39 +0100 Subject: [PATCH 114/132] dt-bindings: usb: Document Renesas RZ/G3E USB3HOST Document the Renesas RZ/G3E USB3.2 Gen2 Host Controller (a.k.a USB3HOST). The USB3HOST is compliant with the Universal Serial Bus 3.2 Specification Revision 1.0. - Supports 1 downstream USB receptacles - Number of SSP Gen2 or SS ports: 1 - Number of HS or FS or LS ports: 1 - Supports Super Speed Plus Gen2x1 (10 Gbps), Super Speed (5 Gbps), High Speed (480 Mbps), Full Speed (12Mbps), and Low Speed (1.5 Mbps). - Supports all transfer-types: Control, Bulk, Interrupt, Isochronous, and these split-transactions. - Supports Power Control and Over Current Detection. Reviewed-by: Rob Herring (Arm) Signed-off-by: Biju Das Link: https://lore.kernel.org/r/20250916150255.4231-4-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/renesas,rzg3e-xhci.yaml | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/renesas,rzg3e-xhci.yaml diff --git a/Documentation/devicetree/bindings/usb/renesas,rzg3e-xhci.yaml b/Documentation/devicetree/bindings/usb/renesas,rzg3e-xhci.yaml new file mode 100644 index 000000000000..98260f9fb442 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/renesas,rzg3e-xhci.yaml @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/renesas,rzg3e-xhci.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas RZ/G3E USB 3.2 Gen2 Host controller + +maintainers: + - Biju Das + +properties: + compatible: + const: renesas,r9a09g047-xhci + + reg: + maxItems: 1 + + interrupts: + items: + - description: Logical OR of all interrupt signals. + - description: System management interrupt + - description: Host system error interrupt + - description: Power management event interrupt + - description: xHC interrupt + + interrupt-names: + items: + - const: all + - const: smi + - const: hse + - const: pme + - const: xhc + + clocks: + maxItems: 1 + + phys: + maxItems: 2 + + phy-names: + items: + - const: usb2-phy + - const: usb3-phy + + power-domains: + maxItems: 1 + + resets: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - interrupt-names + - clocks + - power-domains + - resets + - phys + - phy-names + +allOf: + - $ref: usb-xhci.yaml + +additionalProperties: false + +examples: + - | + #include + #include + + usb@15850000 { + compatible = "renesas,r9a09g047-xhci"; + reg = <0x15850000 0x10000>; + interrupts = , + , + , + , + ; + interrupt-names = "all", "smi", "hse", "pme", "xhc"; + clocks = <&cpg CPG_MOD 0xaf>; + power-domains = <&cpg>; + resets = <&cpg 0xaa>; + phys = <&usb3_phy>, <&usb3_phy>; + phy-names = "usb2-phy", "usb3-phy"; + }; From f7acd12eba05fb9e7dba3222e2aeca5f49d38b3a Mon Sep 17 00:00:00 2001 From: Biju Das Date: Tue, 16 Sep 2025 16:02:40 +0100 Subject: [PATCH 115/132] usb: host: xhci-rcar: Move R-Car reg definitions Move xhci-rcar reg definitions to a header file for the preparation of adding support for RZ/G3E XHCI that has different register definitions. Signed-off-by: Biju Das Link: https://lore.kernel.org/r/20250916150255.4231-5-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-rcar-regs.h | 49 +++++++++++++++++++++++++++++++ drivers/usb/host/xhci-rcar.c | 45 +--------------------------- 2 files changed, 50 insertions(+), 44 deletions(-) create mode 100644 drivers/usb/host/xhci-rcar-regs.h diff --git a/drivers/usb/host/xhci-rcar-regs.h b/drivers/usb/host/xhci-rcar-regs.h new file mode 100644 index 000000000000..5ecbda858be0 --- /dev/null +++ b/drivers/usb/host/xhci-rcar-regs.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __XHCI_RCAR_H +#define __XHCI_RCAR_H + +/*** Register Offset ***/ +#define RCAR_USB3_AXH_STA 0x104 /* AXI Host Control Status */ +#define RCAR_USB3_INT_ENA 0x224 /* Interrupt Enable */ +#define RCAR_USB3_DL_CTRL 0x250 /* FW Download Control & Status */ +#define RCAR_USB3_FW_DATA0 0x258 /* FW Data0 */ + +#define RCAR_USB3_LCLK 0xa44 /* LCLK Select */ +#define RCAR_USB3_CONF1 0xa48 /* USB3.0 Configuration1 */ +#define RCAR_USB3_CONF2 0xa5c /* USB3.0 Configuration2 */ +#define RCAR_USB3_CONF3 0xaa8 /* USB3.0 Configuration3 */ +#define RCAR_USB3_RX_POL 0xab0 /* USB3.0 RX Polarity */ +#define RCAR_USB3_TX_POL 0xab8 /* USB3.0 TX Polarity */ + +/*** Register Settings ***/ +/* AXI Host Control Status */ +#define RCAR_USB3_AXH_STA_B3_PLL_ACTIVE 0x00010000 +#define RCAR_USB3_AXH_STA_B2_PLL_ACTIVE 0x00000001 +#define RCAR_USB3_AXH_STA_PLL_ACTIVE_MASK (RCAR_USB3_AXH_STA_B3_PLL_ACTIVE | \ + RCAR_USB3_AXH_STA_B2_PLL_ACTIVE) + +/* Interrupt Enable */ +#define RCAR_USB3_INT_XHC_ENA 0x00000001 +#define RCAR_USB3_INT_PME_ENA 0x00000002 +#define RCAR_USB3_INT_HSE_ENA 0x00000004 +#define RCAR_USB3_INT_ENA_VAL (RCAR_USB3_INT_XHC_ENA | \ + RCAR_USB3_INT_PME_ENA | RCAR_USB3_INT_HSE_ENA) + +/* FW Download Control & Status */ +#define RCAR_USB3_DL_CTRL_ENABLE 0x00000001 +#define RCAR_USB3_DL_CTRL_FW_SUCCESS 0x00000010 +#define RCAR_USB3_DL_CTRL_FW_SET_DATA0 0x00000100 + +/* LCLK Select */ +#define RCAR_USB3_LCLK_ENA_VAL 0x01030001 + +/* USB3.0 Configuration */ +#define RCAR_USB3_CONF1_VAL 0x00030204 +#define RCAR_USB3_CONF2_VAL 0x00030300 +#define RCAR_USB3_CONF3_VAL 0x13802007 + +/* USB3.0 Polarity */ +#define RCAR_USB3_RX_POL_VAL BIT(21) +#define RCAR_USB3_TX_POL_VAL BIT(4) + +#endif /* __XHCI_RCAR_H */ diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c index 1cc082a3b793..6d4662def87f 100644 --- a/drivers/usb/host/xhci-rcar.c +++ b/drivers/usb/host/xhci-rcar.c @@ -14,6 +14,7 @@ #include "xhci.h" #include "xhci-plat.h" +#include "xhci-rcar-regs.h" #include "xhci-rzv2m.h" #define XHCI_RCAR_FIRMWARE_NAME_V1 "r8a779x_usb3_v1.dlmem" @@ -29,50 +30,6 @@ MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V1); MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V3); -/*** Register Offset ***/ -#define RCAR_USB3_AXH_STA 0x104 /* AXI Host Control Status */ -#define RCAR_USB3_INT_ENA 0x224 /* Interrupt Enable */ -#define RCAR_USB3_DL_CTRL 0x250 /* FW Download Control & Status */ -#define RCAR_USB3_FW_DATA0 0x258 /* FW Data0 */ - -#define RCAR_USB3_LCLK 0xa44 /* LCLK Select */ -#define RCAR_USB3_CONF1 0xa48 /* USB3.0 Configuration1 */ -#define RCAR_USB3_CONF2 0xa5c /* USB3.0 Configuration2 */ -#define RCAR_USB3_CONF3 0xaa8 /* USB3.0 Configuration3 */ -#define RCAR_USB3_RX_POL 0xab0 /* USB3.0 RX Polarity */ -#define RCAR_USB3_TX_POL 0xab8 /* USB3.0 TX Polarity */ - -/*** Register Settings ***/ -/* AXI Host Control Status */ -#define RCAR_USB3_AXH_STA_B3_PLL_ACTIVE 0x00010000 -#define RCAR_USB3_AXH_STA_B2_PLL_ACTIVE 0x00000001 -#define RCAR_USB3_AXH_STA_PLL_ACTIVE_MASK (RCAR_USB3_AXH_STA_B3_PLL_ACTIVE | \ - RCAR_USB3_AXH_STA_B2_PLL_ACTIVE) - -/* Interrupt Enable */ -#define RCAR_USB3_INT_XHC_ENA 0x00000001 -#define RCAR_USB3_INT_PME_ENA 0x00000002 -#define RCAR_USB3_INT_HSE_ENA 0x00000004 -#define RCAR_USB3_INT_ENA_VAL (RCAR_USB3_INT_XHC_ENA | \ - RCAR_USB3_INT_PME_ENA | RCAR_USB3_INT_HSE_ENA) - -/* FW Download Control & Status */ -#define RCAR_USB3_DL_CTRL_ENABLE 0x00000001 -#define RCAR_USB3_DL_CTRL_FW_SUCCESS 0x00000010 -#define RCAR_USB3_DL_CTRL_FW_SET_DATA0 0x00000100 - -/* LCLK Select */ -#define RCAR_USB3_LCLK_ENA_VAL 0x01030001 - -/* USB3.0 Configuration */ -#define RCAR_USB3_CONF1_VAL 0x00030204 -#define RCAR_USB3_CONF2_VAL 0x00030300 -#define RCAR_USB3_CONF3_VAL 0x13802007 - -/* USB3.0 Polarity */ -#define RCAR_USB3_RX_POL_VAL BIT(21) -#define RCAR_USB3_TX_POL_VAL BIT(4) - static void xhci_rcar_start_gen2(struct usb_hcd *hcd) { /* LCLK Select */ From 2ef16e4eb41fe711479d92805e4b9e430c7bbefd Mon Sep 17 00:00:00 2001 From: Biju Das Date: Tue, 16 Sep 2025 16:02:41 +0100 Subject: [PATCH 116/132] usb: host: xhci-plat: Add .post_resume_quirk for struct xhci_plat_priv Some SoCs (eg Renesas RZ/G3E SoC) have special sequence after xhci_resume, add .post_resume_quick for it. Signed-off-by: Biju Das Link: https://lore.kernel.org/r/20250916150255.4231-6-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.c | 14 ++++++++++++++ drivers/usb/host/xhci-plat.h | 1 + 2 files changed, 15 insertions(+) diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index b3f895505cbe..074d9c731639 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -75,6 +75,16 @@ static int xhci_priv_resume_quirk(struct usb_hcd *hcd) return priv->resume_quirk(hcd); } +static int xhci_priv_post_resume_quirk(struct usb_hcd *hcd) +{ + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (!priv->post_resume_quirk) + return 0; + + return priv->post_resume_quirk(hcd); +} + static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci) { struct xhci_plat_priv *priv = xhci_to_priv(xhci); @@ -530,6 +540,10 @@ static int xhci_plat_resume_common(struct device *dev, bool power_lost) if (ret) goto disable_clks; + ret = xhci_priv_post_resume_quirk(hcd); + if (ret) + goto disable_clks; + pm_runtime_disable(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h index 2b32a93d2b76..00751d851831 100644 --- a/drivers/usb/host/xhci-plat.h +++ b/drivers/usb/host/xhci-plat.h @@ -21,6 +21,7 @@ struct xhci_plat_priv { int (*init_quirk)(struct usb_hcd *); int (*suspend_quirk)(struct usb_hcd *); int (*resume_quirk)(struct usb_hcd *); + int (*post_resume_quirk)(struct usb_hcd *); }; #define hcd_to_xhci_priv(h) ((struct xhci_plat_priv *)hcd_to_xhci(h)->priv) From 5db5025d32e5b0b4c13198b5570f33d92ae941d3 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Tue, 16 Sep 2025 16:02:42 +0100 Subject: [PATCH 117/132] usb: host: xhci-rcar: Add Renesas RZ/G3E USB3 Host driver support The USB3.2 Gen2 Host controller (a.k.a USB3HOST), IP found on the RZ/G3E SoC is similar to R-Car XHCI, but it doesn't require any firmware. Signed-off-by: Biju Das Link: https://lore.kernel.org/r/20250916150255.4231-7-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 2 +- drivers/usb/host/xhci-rcar.c | 55 ++++++++++++++++++++++++++++++ drivers/usb/host/xhci-rzg3e-regs.h | 12 +++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 drivers/usb/host/xhci-rzg3e-regs.h diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 109100cc77a3..c4f17ce5c77b 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -93,7 +93,7 @@ config USB_XHCI_RCAR default ARCH_RENESAS help Say 'Y' to enable the support for the xHCI host controller - found in Renesas R-Car ARM SoCs. + found in Renesas R-Car and RZ/G3E alike ARM SoCs. config USB_XHCI_RZV2M bool "xHCI support for Renesas RZ/V2M SoC" diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c index 6d4662def87f..8a993ee21c87 100644 --- a/drivers/usb/host/xhci-rcar.c +++ b/drivers/usb/host/xhci-rcar.c @@ -11,10 +11,12 @@ #include #include #include +#include #include "xhci.h" #include "xhci-plat.h" #include "xhci-rcar-regs.h" +#include "xhci-rzg3e-regs.h" #include "xhci-rzv2m.h" #define XHCI_RCAR_FIRMWARE_NAME_V1 "r8a779x_usb3_v1.dlmem" @@ -67,6 +69,48 @@ static void xhci_rcar_start(struct usb_hcd *hcd) } } +static void xhci_rzg3e_start(struct usb_hcd *hcd) +{ + u32 int_en; + + if (hcd->regs) { + /* Update the controller initial setting */ + writel(0x03130200, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(0)); + writel(0x00160200, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(1)); + writel(0x03150000, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(2)); + writel(0x03130200, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(3)); + writel(0x00180000, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(4)); + + /* Interrupt Enable */ + int_en = readl(hcd->regs + RZG3E_USB3_HOST_INTEN); + int_en |= RZG3E_USB3_HOST_INTEN_ENA; + writel(int_en, hcd->regs + RZG3E_USB3_HOST_INTEN); + } +} + +static int xhci_rzg3e_resume(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + return reset_control_deassert(xhci->reset); +} + +static int xhci_rzg3e_post_resume(struct usb_hcd *hcd) +{ + xhci_rzg3e_start(hcd); + + return 0; +} + +static int xhci_rzg3e_suspend(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + reset_control_assert(xhci->reset); + + return 0; +} + static int xhci_rcar_download_firmware(struct usb_hcd *hcd) { struct device *dev = hcd->self.controller; @@ -190,6 +234,14 @@ static const struct xhci_plat_priv xhci_plat_renesas_rzv2m = { .plat_start = xhci_rzv2m_start, }; +static const struct xhci_plat_priv xhci_plat_renesas_rzg3e = { + .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_RESET_ON_RESUME | XHCI_SUSPEND_RESUME_CLKS, + .plat_start = xhci_rzg3e_start, + .suspend_quirk = xhci_rzg3e_suspend, + .resume_quirk = xhci_rzg3e_resume, + .post_resume_quirk = xhci_rzg3e_post_resume, +}; + static const struct of_device_id usb_xhci_of_match[] = { { .compatible = "renesas,xhci-r8a7790", @@ -206,6 +258,9 @@ static const struct of_device_id usb_xhci_of_match[] = { }, { .compatible = "renesas,xhci-r8a7796", .data = &xhci_plat_renesas_rcar_gen3, + }, { + .compatible = "renesas,r9a09g047-xhci", + .data = &xhci_plat_renesas_rzg3e, }, { .compatible = "renesas,rcar-gen2-xhci", .data = &xhci_plat_renesas_rcar_gen2, diff --git a/drivers/usb/host/xhci-rzg3e-regs.h b/drivers/usb/host/xhci-rzg3e-regs.h new file mode 100644 index 000000000000..7a244a47b882 --- /dev/null +++ b/drivers/usb/host/xhci-rzg3e-regs.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __XHCI_RZG3E_H +#define __XHCI_RZG3E_H + +#define RZG3E_USB3_HOST_INTEN 0x1044 /* Interrupt Enable */ +#define RZG3E_USB3_HOST_U3P0PIPESC(x) (0x10c0 + (x) * 4) /* PIPE Status and Control Register */ + +#define RZG3E_USB3_HOST_INTEN_XHC BIT(0) +#define RZG3E_USB3_HOST_INTEN_HSE BIT(2) +#define RZG3E_USB3_HOST_INTEN_ENA (RZG3E_USB3_HOST_INTEN_XHC | RZG3E_USB3_HOST_INTEN_HSE) + +#endif /* __XHCI_RZG3E_H */ From bfb1d99d969fe3b892db30848aeebfa19d21f57f Mon Sep 17 00:00:00 2001 From: Kuen-Han Tsai Date: Tue, 16 Sep 2025 16:21:32 +0800 Subject: [PATCH 118/132] usb: gadget: Store endpoint pointer in usb_request Gadget function drivers often have goto-based error handling in their bind paths, which can be bug-prone. Refactoring these paths to use __free() scope-based cleanup is desirable, but currently blocked. The blocker is that usb_ep_free_request(ep, req) requires two parameters, while the __free() mechanism can only pass a pointer to the request itself. Store an endpoint pointer in the struct usb_request. The pointer is populated centrally in usb_ep_alloc_request() on every successful allocation, making the request object self-contained. Signed-off-by: Kuen-Han Tsai Link: https://lore.kernel.org/r/20250916-ready-v1-1-4997bf277548@google.com Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250916-ready-v1-1-4997bf277548@google.com --- drivers/usb/gadget/udc/core.c | 3 +++ include/linux/usb/gadget.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index d709e24c1fd4..e3d63b8fa0f4 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -194,6 +194,9 @@ struct usb_request *usb_ep_alloc_request(struct usb_ep *ep, req = ep->ops->alloc_request(ep, gfp_flags); + if (req) + req->ep = ep; + trace_usb_ep_alloc_request(ep, req, req ? 0 : -ENOMEM); return req; diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 0f28c5512fcb..0f2079476088 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -32,6 +32,7 @@ struct usb_ep; /** * struct usb_request - describes one i/o request + * @ep: The associated endpoint set by usb_ep_alloc_request(). * @buf: Buffer used for data. Always provide this; some controllers * only use PIO, or don't use DMA for some endpoints. * @dma: DMA address corresponding to 'buf'. If you don't set this @@ -98,6 +99,7 @@ struct usb_ep; */ struct usb_request { + struct usb_ep *ep; void *buf; unsigned length; dma_addr_t dma; From 201c53c687f2b55a7cc6d9f4000af4797860174b Mon Sep 17 00:00:00 2001 From: Kuen-Han Tsai Date: Tue, 16 Sep 2025 16:21:33 +0800 Subject: [PATCH 119/132] usb: gadget: Introduce free_usb_request helper Introduce the free_usb_request() function that frees both the request's buffer and the request itself. This function serves as the cleanup callback for DEFINE_FREE() to enable automatic, scope-based cleanup for usb_request pointers. Signed-off-by: Kuen-Han Tsai Link: https://lore.kernel.org/r/20250916-ready-v1-2-4997bf277548@google.com Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250916-ready-v1-2-4997bf277548@google.com --- include/linux/usb/gadget.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 0f2079476088..3aaf19e77558 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -15,6 +15,7 @@ #ifndef __LINUX_USB_GADGET_H #define __LINUX_USB_GADGET_H +#include #include #include #include @@ -293,6 +294,28 @@ static inline void usb_ep_fifo_flush(struct usb_ep *ep) /*-------------------------------------------------------------------------*/ +/** + * free_usb_request - frees a usb_request object and its buffer + * @req: the request being freed + * + * This helper function frees both the request's buffer and the request object + * itself by calling usb_ep_free_request(). Its signature is designed to be used + * with DEFINE_FREE() to enable automatic, scope-based cleanup for usb_request + * pointers. + */ +static inline void free_usb_request(struct usb_request *req) +{ + if (!req) + return; + + kfree(req->buf); + usb_ep_free_request(req->ep, req); +} + +DEFINE_FREE(free_usb_request, struct usb_request *, free_usb_request(_T)) + +/*-------------------------------------------------------------------------*/ + struct usb_dcd_config_params { __u8 bU1devExitLat; /* U1 Device exit Latency */ #define USB_DEFAULT_U1_DEV_EXIT_LAT 0x01 /* Less then 1 microsec */ From 75a5b8d4ddd4eb6b16cb0b475d14ff4ae64295ef Mon Sep 17 00:00:00 2001 From: Kuen-Han Tsai Date: Tue, 16 Sep 2025 16:21:34 +0800 Subject: [PATCH 120/132] usb: gadget: f_ncm: Refactor bind path to use __free() After an bind/unbind cycle, the ncm->notify_req is left stale. If a subsequent bind fails, the unified error label attempts to free this stale request, leading to a NULL pointer dereference when accessing ep->ops->free_request. Refactor the error handling in the bind path to use the __free() automatic cleanup mechanism. Unable to handle kernel NULL pointer dereference at virtual address 0000000000000020 Call trace: usb_ep_free_request+0x2c/0xec ncm_bind+0x39c/0x3dc usb_add_function+0xcc/0x1f0 configfs_composite_bind+0x468/0x588 gadget_bind_driver+0x104/0x270 really_probe+0x190/0x374 __driver_probe_device+0xa0/0x12c driver_probe_device+0x3c/0x218 __device_attach_driver+0x14c/0x188 bus_for_each_drv+0x10c/0x168 __device_attach+0xfc/0x198 device_initial_probe+0x14/0x24 bus_probe_device+0x94/0x11c device_add+0x268/0x48c usb_add_gadget+0x198/0x28c dwc3_gadget_init+0x700/0x858 __dwc3_set_mode+0x3cc/0x664 process_scheduled_works+0x1d8/0x488 worker_thread+0x244/0x334 kthread+0x114/0x1bc ret_from_fork+0x10/0x20 Fixes: 9f6ce4240a2b ("usb: gadget: f_ncm.c added") Cc: stable@kernel.org Signed-off-by: Kuen-Han Tsai Link: https://lore.kernel.org/r/20250916-ready-v1-3-4997bf277548@google.com Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250916-ready-v1-3-4997bf277548@google.com --- drivers/usb/gadget/function/f_ncm.c | 78 ++++++++++++----------------- 1 file changed, 33 insertions(+), 45 deletions(-) diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index 58b0dd575af3..0148d60926dc 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -11,6 +11,7 @@ * Copyright (C) 2008 Nokia Corporation */ +#include #include #include #include @@ -20,6 +21,7 @@ #include #include +#include #include "u_ether.h" #include "u_ether_configfs.h" @@ -1436,18 +1438,18 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) struct usb_ep *ep; struct f_ncm_opts *ncm_opts; + struct usb_os_desc_table *os_desc_table __free(kfree) = NULL; + struct usb_request *request __free(free_usb_request) = NULL; + if (!can_support_ecm(cdev->gadget)) return -EINVAL; ncm_opts = container_of(f->fi, struct f_ncm_opts, func_inst); if (cdev->use_os_string) { - f->os_desc_table = kzalloc(sizeof(*f->os_desc_table), - GFP_KERNEL); - if (!f->os_desc_table) + os_desc_table = kzalloc(sizeof(*os_desc_table), GFP_KERNEL); + if (!os_desc_table) return -ENOMEM; - f->os_desc_n = 1; - f->os_desc_table[0].os_desc = &ncm_opts->ncm_os_desc; } mutex_lock(&ncm_opts->lock); @@ -1459,16 +1461,15 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) mutex_unlock(&ncm_opts->lock); if (status) - goto fail; + return status; ncm_opts->bound = true; us = usb_gstrings_attach(cdev, ncm_strings, ARRAY_SIZE(ncm_string_defs)); - if (IS_ERR(us)) { - status = PTR_ERR(us); - goto fail; - } + if (IS_ERR(us)) + return PTR_ERR(us); + ncm_control_intf.iInterface = us[STRING_CTRL_IDX].id; ncm_data_nop_intf.iInterface = us[STRING_DATA_IDX].id; ncm_data_intf.iInterface = us[STRING_DATA_IDX].id; @@ -1478,20 +1479,16 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; ncm->ctrl_id = status; ncm_iad_desc.bFirstInterface = status; ncm_control_intf.bInterfaceNumber = status; ncm_union_desc.bMasterInterface0 = status; - if (cdev->use_os_string) - f->os_desc_table[0].if_id = - ncm_iad_desc.bFirstInterface; - status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; ncm->data_id = status; ncm_data_nop_intf.bInterfaceNumber = status; @@ -1500,35 +1497,31 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) ecm_desc.wMaxSegmentSize = cpu_to_le16(ncm_opts->max_segment_size); - status = -ENODEV; - /* allocate instance-specific endpoints */ ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_in_desc); if (!ep) - goto fail; + return -ENODEV; ncm->port.in_ep = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_out_desc); if (!ep) - goto fail; + return -ENODEV; ncm->port.out_ep = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_notify_desc); if (!ep) - goto fail; + return -ENODEV; ncm->notify = ep; - status = -ENOMEM; - /* allocate notification request and buffer */ - ncm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); - if (!ncm->notify_req) - goto fail; - ncm->notify_req->buf = kmalloc(NCM_STATUS_BYTECOUNT, GFP_KERNEL); - if (!ncm->notify_req->buf) - goto fail; - ncm->notify_req->context = ncm; - ncm->notify_req->complete = ncm_notify_complete; + request = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!request) + return -ENOMEM; + request->buf = kmalloc(NCM_STATUS_BYTECOUNT, GFP_KERNEL); + if (!request->buf) + return -ENOMEM; + request->context = ncm; + request->complete = ncm_notify_complete; /* * support all relevant hardware speeds... we expect that when @@ -1548,7 +1541,7 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, ncm_fs_function, ncm_hs_function, ncm_ss_function, ncm_ss_function); if (status) - goto fail; + return status; /* * NOTE: all that is done without knowing or caring about @@ -1561,23 +1554,18 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) hrtimer_setup(&ncm->task_timer, ncm_tx_timeout, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + if (cdev->use_os_string) { + os_desc_table[0].os_desc = &ncm_opts->ncm_os_desc; + os_desc_table[0].if_id = ncm_iad_desc.bFirstInterface; + f->os_desc_table = no_free_ptr(os_desc_table); + f->os_desc_n = 1; + } + ncm->notify_req = no_free_ptr(request); + DBG(cdev, "CDC Network: IN/%s OUT/%s NOTIFY/%s\n", ncm->port.in_ep->name, ncm->port.out_ep->name, ncm->notify->name); return 0; - -fail: - kfree(f->os_desc_table); - f->os_desc_n = 0; - - if (ncm->notify_req) { - kfree(ncm->notify_req->buf); - usb_ep_free_request(ncm->notify, ncm->notify_req); - } - - ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); - - return status; } static inline struct f_ncm_opts *to_f_ncm_opts(struct config_item *item) From 47b2116e54b4a854600341487e8b55249e926324 Mon Sep 17 00:00:00 2001 From: Kuen-Han Tsai Date: Tue, 16 Sep 2025 16:21:35 +0800 Subject: [PATCH 121/132] usb: gadget: f_acm: Refactor bind path to use __free() After an bind/unbind cycle, the acm->notify_req is left stale. If a subsequent bind fails, the unified error label attempts to free this stale request, leading to a NULL pointer dereference when accessing ep->ops->free_request. Refactor the error handling in the bind path to use the __free() automatic cleanup mechanism. Unable to handle kernel NULL pointer dereference at virtual address 0000000000000020 Call trace: usb_ep_free_request+0x2c/0xec gs_free_req+0x30/0x44 acm_bind+0x1b8/0x1f4 usb_add_function+0xcc/0x1f0 configfs_composite_bind+0x468/0x588 gadget_bind_driver+0x104/0x270 really_probe+0x190/0x374 __driver_probe_device+0xa0/0x12c driver_probe_device+0x3c/0x218 __device_attach_driver+0x14c/0x188 bus_for_each_drv+0x10c/0x168 __device_attach+0xfc/0x198 device_initial_probe+0x14/0x24 bus_probe_device+0x94/0x11c device_add+0x268/0x48c usb_add_gadget+0x198/0x28c dwc3_gadget_init+0x700/0x858 __dwc3_set_mode+0x3cc/0x664 process_scheduled_works+0x1d8/0x488 worker_thread+0x244/0x334 kthread+0x114/0x1bc ret_from_fork+0x10/0x20 Fixes: 1f1ba11b6494 ("usb gadget: issue notifications from ACM function") Cc: stable@kernel.org Signed-off-by: Kuen-Han Tsai Link: https://lore.kernel.org/r/20250916-ready-v1-4-4997bf277548@google.com Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250916-ready-v1-4-4997bf277548@google.com --- drivers/usb/gadget/function/f_acm.c | 42 +++++++++++++---------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/drivers/usb/gadget/function/f_acm.c b/drivers/usb/gadget/function/f_acm.c index 7061720b9732..106046e17c4e 100644 --- a/drivers/usb/gadget/function/f_acm.c +++ b/drivers/usb/gadget/function/f_acm.c @@ -11,12 +11,15 @@ /* #define VERBOSE_DEBUG */ +#include #include #include #include #include #include +#include + #include "u_serial.h" @@ -613,6 +616,7 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) struct usb_string *us; int status; struct usb_ep *ep; + struct usb_request *request __free(free_usb_request) = NULL; /* REVISIT might want instance-specific strings to help * distinguish instances ... @@ -630,7 +634,7 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs, and patch descriptors */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; acm->ctrl_id = status; acm_iad_descriptor.bFirstInterface = status; @@ -639,43 +643,41 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; acm->data_id = status; acm_data_interface_desc.bInterfaceNumber = status; acm_union_desc.bSlaveInterface0 = status; acm_call_mgmt_descriptor.bDataInterface = status; - status = -ENODEV; - /* allocate instance-specific endpoints */ ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc); if (!ep) - goto fail; + return -ENODEV; acm->port.in = ep; ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc); if (!ep) - goto fail; + return -ENODEV; acm->port.out = ep; ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc); if (!ep) - goto fail; + return -ENODEV; acm->notify = ep; acm_iad_descriptor.bFunctionProtocol = acm->bInterfaceProtocol; acm_control_interface_desc.bInterfaceProtocol = acm->bInterfaceProtocol; /* allocate notification */ - acm->notify_req = gs_alloc_req(ep, - sizeof(struct usb_cdc_notification) + 2, - GFP_KERNEL); - if (!acm->notify_req) - goto fail; + request = gs_alloc_req(ep, + sizeof(struct usb_cdc_notification) + 2, + GFP_KERNEL); + if (!request) + return -ENODEV; - acm->notify_req->complete = acm_cdc_notify_complete; - acm->notify_req->context = acm; + request->complete = acm_cdc_notify_complete; + request->context = acm; /* support all relevant hardware speeds... we expect that when * hardware is dual speed, all bulk-capable endpoints work at @@ -692,7 +694,9 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, acm_fs_function, acm_hs_function, acm_ss_function, acm_ss_function); if (status) - goto fail; + return status; + + acm->notify_req = no_free_ptr(request); dev_dbg(&cdev->gadget->dev, "acm ttyGS%d: IN/%s OUT/%s NOTIFY/%s\n", @@ -700,14 +704,6 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) acm->port.in->name, acm->port.out->name, acm->notify->name); return 0; - -fail: - if (acm->notify_req) - gs_free_req(acm->notify, acm->notify_req); - - ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status); - - return status; } static void acm_unbind(struct usb_configuration *c, struct usb_function *f) From 42988380ac67c76bb9dff8f77d7ef3eefd50b7b5 Mon Sep 17 00:00:00 2001 From: Kuen-Han Tsai Date: Tue, 16 Sep 2025 16:21:36 +0800 Subject: [PATCH 122/132] usb: gadget: f_ecm: Refactor bind path to use __free() After an bind/unbind cycle, the ecm->notify_req is left stale. If a subsequent bind fails, the unified error label attempts to free this stale request, leading to a NULL pointer dereference when accessing ep->ops->free_request. Refactor the error handling in the bind path to use the __free() automatic cleanup mechanism. Fixes: da741b8c56d6 ("usb ethernet gadget: split CDC Ethernet function") Cc: stable@kernel.org Signed-off-by: Kuen-Han Tsai Link: https://lore.kernel.org/r/20250916-ready-v1-5-4997bf277548@google.com Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250916-ready-v1-5-4997bf277548@google.com --- drivers/usb/gadget/function/f_ecm.c | 48 ++++++++++++----------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/drivers/usb/gadget/function/f_ecm.c b/drivers/usb/gadget/function/f_ecm.c index 027226325039..675d2bc538a4 100644 --- a/drivers/usb/gadget/function/f_ecm.c +++ b/drivers/usb/gadget/function/f_ecm.c @@ -8,6 +8,7 @@ /* #define VERBOSE_DEBUG */ +#include #include #include #include @@ -15,6 +16,8 @@ #include #include +#include + #include "u_ether.h" #include "u_ether_configfs.h" #include "u_ecm.h" @@ -678,6 +681,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) struct usb_ep *ep; struct f_ecm_opts *ecm_opts; + struct usb_request *request __free(free_usb_request) = NULL; if (!can_support_ecm(cdev->gadget)) return -EINVAL; @@ -711,7 +715,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; ecm->ctrl_id = status; ecm_iad_descriptor.bFirstInterface = status; @@ -720,24 +724,22 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; ecm->data_id = status; ecm_data_nop_intf.bInterfaceNumber = status; ecm_data_intf.bInterfaceNumber = status; ecm_union_desc.bSlaveInterface0 = status; - status = -ENODEV; - /* allocate instance-specific endpoints */ ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_in_desc); if (!ep) - goto fail; + return -ENODEV; ecm->port.in_ep = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_out_desc); if (!ep) - goto fail; + return -ENODEV; ecm->port.out_ep = ep; /* NOTE: a status/notification endpoint is *OPTIONAL* but we @@ -746,20 +748,18 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) */ ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_notify_desc); if (!ep) - goto fail; + return -ENODEV; ecm->notify = ep; - status = -ENOMEM; - /* allocate notification request and buffer */ - ecm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); - if (!ecm->notify_req) - goto fail; - ecm->notify_req->buf = kmalloc(ECM_STATUS_BYTECOUNT, GFP_KERNEL); - if (!ecm->notify_req->buf) - goto fail; - ecm->notify_req->context = ecm; - ecm->notify_req->complete = ecm_notify_complete; + request = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!request) + return -ENOMEM; + request->buf = kmalloc(ECM_STATUS_BYTECOUNT, GFP_KERNEL); + if (!request->buf) + return -ENOMEM; + request->context = ecm; + request->complete = ecm_notify_complete; /* support all relevant hardware speeds... we expect that when * hardware is dual speed, all bulk-capable endpoints work at @@ -778,7 +778,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, ecm_fs_function, ecm_hs_function, ecm_ss_function, ecm_ss_function); if (status) - goto fail; + return status; /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code @@ -788,20 +788,12 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) ecm->port.open = ecm_open; ecm->port.close = ecm_close; + ecm->notify_req = no_free_ptr(request); + DBG(cdev, "CDC Ethernet: IN/%s OUT/%s NOTIFY/%s\n", ecm->port.in_ep->name, ecm->port.out_ep->name, ecm->notify->name); return 0; - -fail: - if (ecm->notify_req) { - kfree(ecm->notify_req->buf); - usb_ep_free_request(ecm->notify, ecm->notify_req); - } - - ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); - - return status; } static inline struct f_ecm_opts *to_f_ecm_opts(struct config_item *item) From 08228941436047bdcd35a612c1aec0912a29d8cd Mon Sep 17 00:00:00 2001 From: Kuen-Han Tsai Date: Tue, 16 Sep 2025 16:21:37 +0800 Subject: [PATCH 123/132] usb: gadget: f_rndis: Refactor bind path to use __free() After an bind/unbind cycle, the rndis->notify_req is left stale. If a subsequent bind fails, the unified error label attempts to free this stale request, leading to a NULL pointer dereference when accessing ep->ops->free_request. Refactor the error handling in the bind path to use the __free() automatic cleanup mechanism. Fixes: 45fe3b8e5342 ("usb ethernet gadget: split RNDIS function") Cc: stable@kernel.org Signed-off-by: Kuen-Han Tsai Link: https://lore.kernel.org/r/20250916-ready-v1-6-4997bf277548@google.com Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250916-ready-v1-6-4997bf277548@google.com --- drivers/usb/gadget/function/f_rndis.c | 85 +++++++++++---------------- 1 file changed, 35 insertions(+), 50 deletions(-) diff --git a/drivers/usb/gadget/function/f_rndis.c b/drivers/usb/gadget/function/f_rndis.c index 7cec19d65fb5..7451e7cb7a85 100644 --- a/drivers/usb/gadget/function/f_rndis.c +++ b/drivers/usb/gadget/function/f_rndis.c @@ -19,6 +19,8 @@ #include +#include + #include "u_ether.h" #include "u_ether_configfs.h" #include "u_rndis.h" @@ -662,6 +664,8 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) struct usb_ep *ep; struct f_rndis_opts *rndis_opts; + struct usb_os_desc_table *os_desc_table __free(kfree) = NULL; + struct usb_request *request __free(free_usb_request) = NULL; if (!can_support_rndis(c)) return -EINVAL; @@ -669,12 +673,9 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) rndis_opts = container_of(f->fi, struct f_rndis_opts, func_inst); if (cdev->use_os_string) { - f->os_desc_table = kzalloc(sizeof(*f->os_desc_table), - GFP_KERNEL); - if (!f->os_desc_table) + os_desc_table = kzalloc(sizeof(*os_desc_table), GFP_KERNEL); + if (!os_desc_table) return -ENOMEM; - f->os_desc_n = 1; - f->os_desc_table[0].os_desc = &rndis_opts->rndis_os_desc; } rndis_iad_descriptor.bFunctionClass = rndis_opts->class; @@ -692,16 +693,14 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) gether_set_gadget(rndis_opts->net, cdev->gadget); status = gether_register_netdev(rndis_opts->net); if (status) - goto fail; + return status; rndis_opts->bound = true; } us = usb_gstrings_attach(cdev, rndis_strings, ARRAY_SIZE(rndis_string_defs)); - if (IS_ERR(us)) { - status = PTR_ERR(us); - goto fail; - } + if (IS_ERR(us)) + return PTR_ERR(us); rndis_control_intf.iInterface = us[0].id; rndis_data_intf.iInterface = us[1].id; rndis_iad_descriptor.iFunction = us[2].id; @@ -709,36 +708,30 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; rndis->ctrl_id = status; rndis_iad_descriptor.bFirstInterface = status; rndis_control_intf.bInterfaceNumber = status; rndis_union_desc.bMasterInterface0 = status; - if (cdev->use_os_string) - f->os_desc_table[0].if_id = - rndis_iad_descriptor.bFirstInterface; - status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; rndis->data_id = status; rndis_data_intf.bInterfaceNumber = status; rndis_union_desc.bSlaveInterface0 = status; - status = -ENODEV; - /* allocate instance-specific endpoints */ ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc); if (!ep) - goto fail; + return -ENODEV; rndis->port.in_ep = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc); if (!ep) - goto fail; + return -ENODEV; rndis->port.out_ep = ep; /* NOTE: a status/notification endpoint is, strictly speaking, @@ -747,21 +740,19 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) */ ep = usb_ep_autoconfig(cdev->gadget, &fs_notify_desc); if (!ep) - goto fail; + return -ENODEV; rndis->notify = ep; - status = -ENOMEM; - /* allocate notification request and buffer */ - rndis->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); - if (!rndis->notify_req) - goto fail; - rndis->notify_req->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL); - if (!rndis->notify_req->buf) - goto fail; - rndis->notify_req->length = STATUS_BYTECOUNT; - rndis->notify_req->context = rndis; - rndis->notify_req->complete = rndis_response_complete; + request = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!request) + return -ENOMEM; + request->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL); + if (!request->buf) + return -ENOMEM; + request->length = STATUS_BYTECOUNT; + request->context = rndis; + request->complete = rndis_response_complete; /* support all relevant hardware speeds... we expect that when * hardware is dual speed, all bulk-capable endpoints work at @@ -778,7 +769,7 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, eth_fs_function, eth_hs_function, eth_ss_function, eth_ss_function); if (status) - goto fail; + return status; rndis->port.open = rndis_open; rndis->port.close = rndis_close; @@ -789,10 +780,19 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) if (rndis->manufacturer && rndis->vendorID && rndis_set_param_vendor(rndis->params, rndis->vendorID, rndis->manufacturer)) { - status = -EINVAL; - goto fail_free_descs; + usb_free_all_descriptors(f); + return -EINVAL; } + if (cdev->use_os_string) { + os_desc_table[0].os_desc = &rndis_opts->rndis_os_desc; + os_desc_table[0].if_id = rndis_iad_descriptor.bFirstInterface; + f->os_desc_table = no_free_ptr(os_desc_table); + f->os_desc_n = 1; + + } + rndis->notify_req = no_free_ptr(request); + /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code * until we're activated via set_alt(). @@ -802,21 +802,6 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) rndis->port.in_ep->name, rndis->port.out_ep->name, rndis->notify->name); return 0; - -fail_free_descs: - usb_free_all_descriptors(f); -fail: - kfree(f->os_desc_table); - f->os_desc_n = 0; - - if (rndis->notify_req) { - kfree(rndis->notify_req->buf); - usb_ep_free_request(rndis->notify, rndis->notify_req); - } - - ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); - - return status; } void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net) From 08fa726e66039dfa80226dfa112931f60ad4c898 Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Thu, 18 Sep 2025 00:07:20 +0300 Subject: [PATCH 124/132] Revert "usb: xhci: Avoid Stop Endpoint retry loop if the endpoint seems Running" This reverts commit 28a76fcc4c85dd39633fb96edb643c91820133e3. No actual HW bugs are known where Endpoint Context shows Running state but Stop Endpoint fails repeatedly with Context State Error and leaves the endpoint state unchanged. Stop Endpoint retries on Running EPs have been performed since early 2021 with no such issues reported so far. Trying to handle this hypothetical case brings a more realistic danger: if Stop Endpoint fails on an endpoint which hasn't yet started after a doorbell ring and enough latency occurs before this completion event is handled, the driver may time out and begin removing cancelled TDs from a running endpoint, even though one more retry would stop it reliably. Such high latency is rare but not impossible, and removing TDs from a running endpoint can cause more damage than not giving back a cancelled URB (which wasn't happening anyway). So err on the side of caution and revert to the old policy of always retrying if the EP appears running. [Remove stable tag as we are dealing with theoretical cases -Mathias] Fixes: 28a76fcc4c85d ("usb: xhci: Avoid Stop Endpoint retry loop if the endpoint seems Running") Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250917210726.97100-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 3c08682020f0..d7bef1dd519f 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1262,19 +1262,16 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, * Stopped state, but it will soon change to Running. * * Assume this bug on unexpected Stop Endpoint failures. - * Keep retrying until the EP starts and stops again. + * Keep retrying until the EP starts and stops again, on + * chips where this is known to help. Wait for 100ms. */ + if (time_is_before_jiffies(ep->stop_time + msecs_to_jiffies(100))) + break; fallthrough; case EP_STATE_RUNNING: /* Race, HW handled stop ep cmd before ep was running */ xhci_dbg(xhci, "Stop ep completion ctx error, ctx_state %d\n", GET_EP_CTX_STATE(ep_ctx)); - /* - * Don't retry forever if we guessed wrong or a defective HC never starts - * the EP or says 'Running' but fails the command. We must give back TDs. - */ - if (time_is_before_jiffies(ep->stop_time + msecs_to_jiffies(100))) - break; command = xhci_alloc_command(xhci, false, GFP_ATOMIC); if (!command) { From 0ed023a88396088d4221c345a3911f553dd42598 Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Thu, 18 Sep 2025 00:07:21 +0300 Subject: [PATCH 125/132] usb: xhci: Update a comment about Stop Endpoint retries Retries are no longer gated by a quirk, so remove that part. Add a brief explanation of the timeout. Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250917210726.97100-3-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index d7bef1dd519f..543cbec560c5 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1262,8 +1262,9 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, * Stopped state, but it will soon change to Running. * * Assume this bug on unexpected Stop Endpoint failures. - * Keep retrying until the EP starts and stops again, on - * chips where this is known to help. Wait for 100ms. + * Keep retrying until the EP starts and stops again or + * up to a timeout (a defective HC may never start, or a + * driver bug may cause stopping an already stopped EP). */ if (time_is_before_jiffies(ep->stop_time + msecs_to_jiffies(100))) break; From 719de070f764e079cdcb4ddeeb5b19b3ddddf9c1 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 18 Sep 2025 00:07:22 +0300 Subject: [PATCH 126/132] usb: xhci-pci: add support for hosts with zero USB3 ports Add xhci support for PCI hosts that have zero USB3 ports. Avoid creating a shared Host Controller Driver (HCD) when there is only one root hub. Additionally, all references to 'xhci->shared_hcd' are now checked before use. Only xhci-pci.c requires modification to accommodate this change, as the xhci core already supports configurations with zero USB3 ports. This capability was introduced when xHCI Platform and MediaTek added support for zero USB3 ports. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220181 Tested-by: Nick Nielsen Tested-by: grm1 Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250917210726.97100-4-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-pci.c | 44 +++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 00fac8b233d2..5c8ab519f497 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -610,7 +610,7 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) { int retval; struct xhci_hcd *xhci; - struct usb_hcd *hcd; + struct usb_hcd *hcd, *usb3_hcd; struct reset_control *reset; reset = devm_reset_control_get_optional_exclusive(&dev->dev, NULL); @@ -636,26 +636,32 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) hcd = dev_get_drvdata(&dev->dev); xhci = hcd_to_xhci(hcd); xhci->reset = reset; - xhci->shared_hcd = usb_create_shared_hcd(&xhci_pci_hc_driver, &dev->dev, - pci_name(dev), hcd); - if (!xhci->shared_hcd) { - retval = -ENOMEM; - goto dealloc_usb2_hcd; + + xhci->allow_single_roothub = 1; + if (!xhci_has_one_roothub(xhci)) { + xhci->shared_hcd = usb_create_shared_hcd(&xhci_pci_hc_driver, &dev->dev, + pci_name(dev), hcd); + if (!xhci->shared_hcd) { + retval = -ENOMEM; + goto dealloc_usb2_hcd; + } + + retval = xhci_ext_cap_init(xhci); + if (retval) + goto put_usb3_hcd; + + retval = usb_add_hcd(xhci->shared_hcd, dev->irq, IRQF_SHARED); + if (retval) + goto put_usb3_hcd; + } else { + retval = xhci_ext_cap_init(xhci); + if (retval) + goto dealloc_usb2_hcd; } - retval = xhci_ext_cap_init(xhci); - if (retval) - goto put_usb3_hcd; - - retval = usb_add_hcd(xhci->shared_hcd, dev->irq, - IRQF_SHARED); - if (retval) - goto put_usb3_hcd; - /* Roothub already marked as USB 3.0 speed */ - - if (!(xhci->quirks & XHCI_BROKEN_STREAMS) && - HCC_MAX_PSA(xhci->hcc_params) >= 4) - xhci->shared_hcd->can_do_streams = 1; + usb3_hcd = xhci_get_usb3_hcd(xhci); + if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && HCC_MAX_PSA(xhci->hcc_params) >= 4) + usb3_hcd->can_do_streams = 1; /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */ pm_runtime_put_noidle(&dev->dev); From 931e468764b22a587febf562e57249e95e84350d Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 18 Sep 2025 00:07:23 +0300 Subject: [PATCH 127/132] usb: xhci: improve TR Dequeue Pointer mask Address the naming and usage of the TR Dequeue Pointer mask in the xhci driver. The Endpoint Context Field at offset 0x08 is defined as follows: Bit 0 Dequeue Cycle State (DCS) Bits 3:1 RsvdZ (Reserved and Zero) Bits 63:4 TR Dequeue Pointer When extracting the TR Dequeue Pointer for an Endpoint without Streams, in xhci_handle_cmd_set_deq(), the inverted Dequeue Cycle State mask (~EP_CTX_CYCLE_MASK) is used, inadvertently including the Reserved bits. Although bits 3:1 are typically zero, using the incorrect mask could cause issues. The existing mask, named "SCTX_DEQ_MASK," is misleading because "SCTX" implies exclusivity to Stream Contexts, whereas the TR Dequeue Pointer is applicable to both Stream and non-Stream Contexts. Rename the mask to "TR_DEQ_PTR_MASK", utilize GENMASK_ULL() macro and use the mask when handling the TR Dequeue Pointer field. Function xhci_get_hw_deq() returns the Endpoint Context Field 0x08, either directly from the Endpoint context or a Stream. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250917210726.97100-5-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 12 ++++++------ drivers/usb/host/xhci.h | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 543cbec560c5..8e209aa33ea7 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -711,7 +711,7 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, return -ENODEV; } - hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id); + hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id) & TR_DEQ_PTR_MASK; new_seg = ep_ring->deq_seg; new_deq = ep_ring->dequeue; new_cycle = le32_to_cpu(td->end_trb->generic.field[3]) & TRB_CYCLE; @@ -723,7 +723,7 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, */ do { if (!hw_dequeue_found && xhci_trb_virt_to_dma(new_seg, new_deq) - == (dma_addr_t)(hw_dequeue & ~0xf)) { + == (dma_addr_t)hw_dequeue) { hw_dequeue_found = true; if (td_last_trb_found) break; @@ -1066,7 +1066,7 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) */ hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, td->urb->stream_id); - hw_deq &= ~0xf; + hw_deq &= TR_DEQ_PTR_MASK; if (td->cancel_status == TD_HALTED || trb_in_td(td, hw_deq)) { switch (td->cancel_status) { @@ -1156,7 +1156,7 @@ static struct xhci_td *find_halted_td(struct xhci_virt_ep *ep) if (!list_empty(&ep->ring->td_list)) { /* Not streams compatible */ hw_deq = xhci_get_hw_deq(ep->xhci, ep->vdev, ep->ep_index, 0); - hw_deq &= ~0xf; + hw_deq &= TR_DEQ_PTR_MASK; td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list); if (trb_in_td(td, hw_deq)) return td; @@ -1479,7 +1479,7 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, u64 deq; /* 4.6.10 deq ptr is written to the stream ctx for streams */ if (ep->ep_state & EP_HAS_STREAMS) { - deq = le64_to_cpu(stream_ctx->stream_ring) & SCTX_DEQ_MASK; + deq = le64_to_cpu(stream_ctx->stream_ring) & TR_DEQ_PTR_MASK; /* * Cadence xHCI controllers store some endpoint state @@ -1495,7 +1495,7 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, stream_ctx->reserved[1] = 0; } } else { - deq = le64_to_cpu(ep_ctx->deq) & ~EP_CTX_CYCLE_MASK; + deq = le64_to_cpu(ep_ctx->deq) & TR_DEQ_PTR_MASK; } xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, "Successful Set TR Deq Ptr cmd, deq = @%08llx", deq); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index e09bd9a5a996..58a51f09cceb 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -500,7 +500,8 @@ struct xhci_ep_ctx { /* deq bitmasks */ #define EP_CTX_CYCLE_MASK (1 << 0) -#define SCTX_DEQ_MASK (~0xfL) +/* bits 63:4 - TR Dequeue Pointer */ +#define TR_DEQ_PTR_MASK GENMASK_ULL(63, 4) /** From e16fdeaa96846aefd05760ca45be32da8c1dfc83 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 18 Sep 2025 00:07:24 +0300 Subject: [PATCH 128/132] usb: xhci: correct indentation for PORTSC tracing function Correct the indentation in USB Port Register Set (PORTSC) tracing. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250917210726.97100-6-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-trace.h | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/drivers/usb/host/xhci-trace.h b/drivers/usb/host/xhci-trace.h index bfb5c5c17012..f303ce600ff5 100644 --- a/drivers/usb/host/xhci-trace.h +++ b/drivers/usb/host/xhci-trace.h @@ -541,23 +541,23 @@ DEFINE_EVENT(xhci_log_ring, xhci_inc_deq, ); DECLARE_EVENT_CLASS(xhci_log_portsc, - TP_PROTO(struct xhci_port *port, u32 portsc), - TP_ARGS(port, portsc), - TP_STRUCT__entry( - __field(u32, busnum) - __field(u32, portnum) - __field(u32, portsc) - ), - TP_fast_assign( - __entry->busnum = port->rhub->hcd->self.busnum; - __entry->portnum = port->hcd_portnum; - __entry->portsc = portsc; - ), - TP_printk("port %d-%d: %s", - __entry->busnum, - __entry->portnum, - xhci_decode_portsc(__get_buf(XHCI_MSG_MAX), __entry->portsc) - ) + TP_PROTO(struct xhci_port *port, u32 portsc), + TP_ARGS(port, portsc), + TP_STRUCT__entry( + __field(u32, busnum) + __field(u32, portnum) + __field(u32, portsc) + ), + TP_fast_assign( + __entry->busnum = port->rhub->hcd->self.busnum; + __entry->portnum = port->hcd_portnum; + __entry->portsc = portsc; + ), + TP_printk("port %d-%d: %s", + __entry->busnum, + __entry->portnum, + xhci_decode_portsc(__get_buf(XHCI_MSG_MAX), __entry->portsc) + ) ); DEFINE_EVENT(xhci_log_portsc, xhci_handle_port_status, From a4e143636d5def935dd461539b67b61287a8dfef Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 18 Sep 2025 00:07:25 +0300 Subject: [PATCH 129/132] usb: xhci: align PORTSC trace with one-based port numbering In the xHCI driver, port numbers are typically described using a one-based index. However, tracing currently uses a zero-based index. To ensure consistency between tracing and dynamic debugging, update the trace port number to use a one-based index. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20250917210726.97100-7-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-trace.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-trace.h b/drivers/usb/host/xhci-trace.h index f303ce600ff5..9abc904f1749 100644 --- a/drivers/usb/host/xhci-trace.h +++ b/drivers/usb/host/xhci-trace.h @@ -550,7 +550,7 @@ DECLARE_EVENT_CLASS(xhci_log_portsc, ), TP_fast_assign( __entry->busnum = port->rhub->hcd->self.busnum; - __entry->portnum = port->hcd_portnum; + __entry->portnum = port->hcd_portnum + 1; __entry->portsc = portsc; ), TP_printk("port %d-%d: %s", From 67600ccfc4f38ebd331b9332ac94717bfbc87ea7 Mon Sep 17 00:00:00 2001 From: Duoming Zhou Date: Tue, 23 Sep 2025 13:13:57 +0800 Subject: [PATCH 130/132] thunderbolt: Fix use-after-free in tb_dp_dprx_work The original code relies on cancel_delayed_work() in tb_dp_dprx_stop(), which does not ensure that the delayed work item tunnel->dprx_work has fully completed if it was already running. This leads to use-after-free scenarios where tb_tunnel is deallocated by tb_tunnel_put(), while tunnel->dprx_work remains active and attempts to dereference tb_tunnel in tb_dp_dprx_work(). A typical race condition is illustrated below: CPU 0 | CPU 1 tb_dp_tunnel_active() | tb_deactivate_and_free_tunnel()| tb_dp_dprx_start() tb_tunnel_deactivate() | queue_delayed_work() tb_dp_activate() | tb_dp_dprx_stop() | tb_dp_dprx_work() //delayed worker cancel_delayed_work() | tb_tunnel_put(tunnel); | | tunnel = container_of(...); //UAF | tunnel-> //UAF Replacing cancel_delayed_work() with cancel_delayed_work_sync() is not feasible as it would introduce a deadlock: both tb_dp_dprx_work() and the cleanup path acquire tb->lock, and cancel_delayed_work_sync() would wait indefinitely for the work item that cannot proceed. Instead, implement proper reference counting: - If cancel_delayed_work() returns true (work is pending), we release the reference in the stop function. - If it returns false (work is executing or already completed), the reference is released in delayed work function itself. This ensures the tb_tunnel remains valid during work item execution while preventing memory leaks. This bug was found by static analysis. Fixes: d6d458d42e1e ("thunderbolt: Handle DisplayPort tunnel activation asynchronously") Cc: stable@vger.kernel.org Signed-off-by: Duoming Zhou Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tunnel.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 850d9b107abd..bfa0607b5574 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -1079,6 +1079,7 @@ static void tb_dp_dprx_work(struct work_struct *work) if (tunnel->callback) tunnel->callback(tunnel, tunnel->callback_data); + tb_tunnel_put(tunnel); } static int tb_dp_dprx_start(struct tb_tunnel *tunnel) @@ -1106,8 +1107,8 @@ static void tb_dp_dprx_stop(struct tb_tunnel *tunnel) if (tunnel->dprx_started) { tunnel->dprx_started = false; tunnel->dprx_canceled = true; - cancel_delayed_work(&tunnel->dprx_work); - tb_tunnel_put(tunnel); + if (cancel_delayed_work(&tunnel->dprx_work)) + tb_tunnel_put(tunnel); } } From 0e0ba0ecec3d6e819e0c2348331ff99afe2eb5d5 Mon Sep 17 00:00:00 2001 From: Xiaowei Li Date: Wed, 24 Sep 2025 11:16:50 +0800 Subject: [PATCH 131/132] USB: serial: option: add SIMCom 8230C compositions Add support for SIMCom 8230C which is based on Qualcomm SDX35 chip. USB Device Listings: 0x9071: tty (DM) + tty (NMEA) + tty (AT) + rmnet (QMI mode) + adb T: Bus=01 Lev=01 Prnt=01 Port=05 Cnt=02 Dev#= 10 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=1e0e ProdID=9071 Rev= 5.15 S: Manufacturer=SIMCOM S: Product=SDXBAAGHA-IDP _SN:D744C4C5 S: SerialNumber=0123456789ABCDEF C:* #Ifs= 5 Cfg#= 1 Atr=a0 MxPwr=500mA I:* If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=50 Driver=qmi_wwan E: Ad=86(I) Atr=03(Int.) MxPS= 8 Ivl=32ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=(none) E: Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms 0x9078: tty (DM) + tty (NMEA) + tty (AT) + ECM + adb T: Bus=01 Lev=01 Prnt=01 Port=05 Cnt=02 Dev#= 9 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=1e0e ProdID=9078 Rev= 5.15 S: Manufacturer=SIMCOM S: Product=SDXBAAGHA-IDP _SN:D744C4C5 S: SerialNumber=0123456789ABCDEF C:* #Ifs= 6 Cfg#= 1 Atr=a0 MxPwr=500mA I:* If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=06 Prot=00 Driver=cdc_ether E: Ad=86(I) Atr=03(Int.) MxPS= 16 Ivl=32ms I: If#= 4 Alt= 0 #EPs= 0 Cls=0a(data ) Sub=00 Prot=00 Driver=cdc_ether I:* If#= 4 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=cdc_ether E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 5 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=(none) E: Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms 0x907b: RNDIS + tty (DM) + tty (NMEA) + tty (AT) + adb T: Bus=01 Lev=01 Prnt=01 Port=05 Cnt=02 Dev#= 8 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=1e0e ProdID=907b Rev= 5.15 S: Manufacturer=SIMCOM S: Product=SDXBAAGHA-IDP _SN:D744C4C5 S: SerialNumber=0123456789ABCDEF C:* #Ifs= 6 Cfg#= 1 Atr=a0 MxPwr=500mA A: FirstIf#= 0 IfCount= 2 Cls=ef(misc ) Sub=04 Prot=01 I:* If#= 0 Alt= 0 #EPs= 1 Cls=ef(misc ) Sub=04 Prot=01 Driver=rndis_host E: Ad=82(I) Atr=03(Int.) MxPS= 8 Ivl=32ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=rndis_host E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=84(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 4 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=86(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 5 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=(none) E: Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms Signed-off-by: Xiaowei Li Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index e5cd33093423..e62e6422de26 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2097,6 +2097,12 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) }, /* Simcom SIM7500/SIM7600 MBIM mode */ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff), /* Simcom SIM7500/SIM7600 RNDIS mode */ .driver_info = RSVD(7) }, + { USB_DEVICE(0x1e0e, 0x9071), /* Simcom SIM8230 RMNET mode */ + .driver_info = RSVD(3) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9078, 0xff), /* Simcom SIM8230 ECM mode */ + .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x907b, 0xff), /* Simcom SIM8230 RNDIS mode */ + .driver_info = RSVD(5) }, { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9205, 0xff) }, /* Simcom SIM7070/SIM7080/SIM7090 AT+ECM mode */ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9206, 0xff) }, /* Simcom SIM7070/SIM7080/SIM7090 AT-only mode */ { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_X060S_X200), From e40b984b6c4ce3f80814f39f86f87b2a48f2e662 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 2 Sep 2025 15:15:46 +0300 Subject: [PATCH 132/132] usb: vhci-hcd: Prevent suspending virtually attached devices The VHCI platform driver aims to forbid entering system suspend when at least one of the virtual USB ports are bound to an active USB/IP connection. However, in some cases, the detection logic doesn't work reliably, i.e. when all devices attached to the virtual root hub have been already suspended, leading to a broken suspend state, with unrecoverable resume. Ensure the virtually attached devices do not enter suspend by setting the syscore PM flag. Note this is currently limited to the client side only, since the server side doesn't implement system suspend prevention. Fixes: 04679b3489e0 ("Staging: USB/IP: add client driver") Signed-off-by: Cristian Ciocaltea Acked-by: Shuah Khan Link: https://lore.kernel.org/r/20250902-vhci-hcd-suspend-fix-v3-1-864e4e833559@collabora.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci_hcd.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index e70fba9f55d6..0d6c10a8490c 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -765,6 +765,17 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag ctrlreq->wValue, vdev->rhport); vdev->udev = usb_get_dev(urb->dev); + /* + * NOTE: A similar operation has been done via + * USB_REQ_GET_DESCRIPTOR handler below, which is + * supposed to always precede USB_REQ_SET_ADDRESS. + * + * It's not entirely clear if operating on a different + * usb_device instance here is a real possibility, + * otherwise this call and vdev->udev assignment above + * should be dropped. + */ + dev_pm_syscore_device(&vdev->udev->dev, true); usb_put_dev(old); spin_lock(&vdev->ud.lock); @@ -785,6 +796,17 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag "Not yet?:Get_Descriptor to device 0 (get max pipe size)\n"); vdev->udev = usb_get_dev(urb->dev); + /* + * Set syscore PM flag for the virtually attached + * devices to ensure they will not enter suspend on + * the client side. + * + * Note this doesn't have any impact on the physical + * devices attached to the host system on the server + * side, hence there is no need to undo the operation + * on disconnect. + */ + dev_pm_syscore_device(&vdev->udev->dev, true); usb_put_dev(old); goto out;