From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 7D389C5B572 for ; Thu, 13 Aug 2026 21:00:00 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender:List-Subscribe:List-Help :List-Post:List-Archive:List-Unsubscribe:List-Id:Cc:To:In-Reply-To:References :Message-Id:Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date: From:Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=9zSpGoeWd2078ZF5D4eB7ejyNjP3AScEl712rHsht8A=; b=BXxApcrgE91fQWEphwBMNnMvnB i41jAeA+NBKRnTYRMoEeCnkJ7TxswNeQ2UniQt/vCHtYBXO2ry3mD/BK0q/F/2Fc1mDbpTXjMJT4e VaM1oYOXziPTyb3ZbxiFXnlllbQKQuNYML76jPUrjVEhE8tUvEq/FgQaKDKtYB0VE0TjUhT+ysIfs aoDkbYYcAI2rJgJqTOlBwTLjPUzf3xgwRC+nnolRMSjKKqkOg2kye/RkWXqIKJCJRcKhgyHQAj7ff dT/PnIHIw+ICJc2ba/29arBWTQMO2Ld9y6Osm/C3a/twPKa6z9pd16N+wcOe58gMrHa2eJD+7ZixW uZGcMZng==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.99.1 #2 (Red Hat Linux)) id 1wucWm-00000001cA0-0K6Q; Thu, 13 Aug 2026 20:59:52 +0000 Received: from bali.collaboradmins.com ([2a01:4f8:201:9162::2]) by bombadil.infradead.org with esmtps (Exim 4.99.1 #2 (Red Hat Linux)) id 1wucWj-00000001c9C-10cs; Thu, 13 Aug 2026 20:59:50 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1786654311; bh=3jZ3XsTAzI6wMwpfaV96FdPbY4A5ppMLD3qENTSviwI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=pFjnvinLQPgdeVrT70t6L+UDvW+omYwCZDewAVzq7KuRms0aoY/AWSsM8TLIvZFZW RPhPFR4NHYhrFmu58U7KeQPm+OXDz65neStLl/KzkT0q4FTJljFihK3JQc9jY2Svhw 1O+WhFz0P1HVqtyF2USmdBR5Kwns6jdSSrrRythYXuOLdEqCF7WNq5IvHIUNTOEgEo dbT1AM95JStchE6bz7a93c69KJCP02pUtQpvo5urmnh8KW/HYHnvUEP3aGEtXp3PE7 wmOVkNLiqVFuB1IBHKwmidmaCNkzK64gNtujkpHtqVix9CYfWCtgyEtAxeypGpGOIq ye2YNrffmuCEQ== Received: from jupiter.universe (unknown [100.64.1.62]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519) (No client certificate requested) (Authenticated sender: sre) by bali.collaboradmins.com (Postfix) with ESMTPSA id 1B72A17E35C8; Thu, 13 Aug 2026 22:51:51 +0200 (CEST) Received: by jupiter.universe (Postfix, from userid 1000) id CCBB948007B; Thu, 13 Aug 2026 22:51:49 +0200 (CEST) From: Sebastian Reichel Date: Thu, 13 Aug 2026 22:52:19 +0200 Subject: [PATCH v14 36/38] phy: rockchip: usbdp: Fix power state handling MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260813-rockchip-usbdp-cleanup-v14-36-b5ad9c68fa11@collabora.com> References: <20260813-rockchip-usbdp-cleanup-v14-0-b5ad9c68fa11@collabora.com> In-Reply-To: <20260813-rockchip-usbdp-cleanup-v14-0-b5ad9c68fa11@collabora.com> To: Vinod Koul , Manivannan Sadhasivam , Neil Armstrong , Heiko Stuebner , Frank Wang , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Thinh Nguyen , Greg Kroah-Hartman , Philipp Zabel Cc: Andy Yan , Dmitry Baryshkov , Yubing Zhang , Alexey Charkov , Igor Paunovic , linux-phy@lists.infradead.org, linux-arm-kernel@lists.infradead.org, linux-rockchip@lists.infradead.org, linux-kernel@vger.kernel.org, kernel@collabora.com, devicetree@vger.kernel.org, linux-usb@vger.kernel.org, Sebastian Reichel X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=8068; i=sebastian.reichel@collabora.com; h=from:subject:message-id; bh=3jZ3XsTAzI6wMwpfaV96FdPbY4A5ppMLD3qENTSviwI=; b=owJ4nAFtApL9kA0DAAoB2O7X88g7+poByyZiAGp+LmVHzNWjhv5taA6PKaTaAxRV2aOmyOOUv Z3xpWThyQuL84kCMwQAAQoAHRYhBO9mDQdGP4tyanlUE9ju1/PIO/qaBQJqfi5lAAoJENju1/PI O/qasVUP/RgYD8FAVgvNM+5C6XF/naYVIaF9I+BcXdPVXKyO5T1HIaSvH6G7iV8ZaieKgd5V0fO 4/Qu37kxRFawvLsFoW2FaWHJ2k8pD36MlzK3nsOT1TK2jPcevhaNJghGit3fw6LltjY1v1sqIyR hVnGFUUNjdnvPqhRAxVPUjJteBjVt7dw2s0WohcFjOgiYNrFWFjxJ6qvzQODMp7N1DKNJpo9RUX jSWj3YpoxpF0llhJT6NgSKO7v4KkbKtrOZZBNGKln2BKl9R2cFAZP/JHlBVr9jKtUgJb242L86C NhXIZ56ukcvkj6U+TdjYnS1HHD1Ic4EEQ6/WcDhCyoMIVqPVHA7TcXV0QunoyzYcISgj8TrgKmm wrdQ8G99Ip5Mt+7/lpJYishxcPgyDbsbO044NKl8lcruEaE4az3wyAcsgXH0WUG/pgCbMdgfc8+ BGH6Hq1LoENLJ4v4IypwymVWphd3pThX5b9ist2QkDJuCwXitVQIvxBHBzqt79G2P71yNNp9K4x FY/l4EoihOJSRA175Ar40v1Ego0ooX319iuetD+lM2CbmqwOCz42vN5ShPe4/W1Enhsrf7mgS0/ hHP+8Lru+me0rB2kwD1HmZBGucd5s2gNINHgoQkXcY6rtLLzUkpJDfuzkS10BUmqYZlvLfih9Yc K54PvX++rtq4dCugmwOYw7w== X-Developer-Key: i=sebastian.reichel@collabora.com; a=openpgp; fpr=EF660D07463F8B726A795413D8EED7F3C83BFA9A X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.9.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20260813_135949_455457_0B0A8569 X-CRM114-Status: GOOD ( 28.90 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org Restructure power state handling by introducing sw_mode in addition to the hw_mode field, so that the PHY knows about the currently supported modes from the hardware perspective, the current modes requested by software and the actual hardware status. Now anything updating either the hardware or software state can simply update the status field and call rk_udphy_update_power_state(). This makes it a lot more obvious what is going on and also fixes a few potential resource leaks identified by Sashiko as a side-effect. For example if USB3 is requested by software while the USB-C is in DP-only mode, things are decently handled after this. Tested-by: Igor Paunovic # Orange Pi 5 Plus Signed-off-by: Sebastian Reichel --- drivers/phy/rockchip/phy-rockchip-usbdp.c | 143 +++++++++++++++++++----------- 1 file changed, 90 insertions(+), 53 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index 405b3d943602..e597cdfe8d79 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -173,9 +173,10 @@ struct rk_udphy { /* PHY status management */ bool flip; - bool phy_needs_reinit; + bool phy_needs_reinit; /* lane mux changed */ u8 hw_mode; /* modes currently supported by hardware */ - u8 status; + u8 sw_mode; /* modes currently requested */ + u8 status; /* current PHY power state */ /* utilized for USB */ bool hs; /* flag for high-speed */ @@ -985,70 +986,95 @@ static int rk_udphy_parse_dt(struct rk_udphy *udphy) return rk_udphy_reset_init(udphy, dev); } -static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode) +static int rk_udphy_update_power_state(struct rk_udphy *udphy) { + bool usb3_port_enable; + u8 target_mode; int ret; - if (!(udphy->hw_mode & mode)) { - dev_info(udphy->dev, "mode 0x%02x is not support\n", mode); + /* + * Initialize PHY mode according to the hardware setup (either described + * in DT or negotiated via the Type-C controller) instead of requesting + * only the needed PHY side, because that would break the USB/DP data + * streams when the other PHY is being requested. This is not an issue + * during the Type-C negotiation as that happens during the hotplug phase + * and not during normal operation. Also disable everything if the + * software has not requested anything, as there shouldn't be any active + * data streams in that case. + */ + target_mode = udphy->hw_mode; + if (udphy->sw_mode == UDPHY_MODE_NONE) + target_mode = UDPHY_MODE_NONE; + + usb3_port_enable = !udphy->hs && (target_mode & UDPHY_MODE_USB); + + if (!udphy->phy_needs_reinit && udphy->status == target_mode) { + if (udphy->sw_mode & UDPHY_MODE_USB) + rk_udphy_u3_port_disable(udphy, !usb3_port_enable); return 0; } - if (udphy->status == UDPHY_MODE_NONE) { - phy_notify_reset(udphy->phy_u3, PHY_NOTIFY_PRE_RESET); + /* Avoid to re-init disabled PHY */ + if (udphy->status == target_mode && target_mode == UDPHY_MODE_NONE) + return 0; + /* + * Inform DWC3 driver, that we are about to reset the PHY, so that it can + * assert its PIPE reset lines and avoid DWC3 getting into a buggy state. + * This is intentionally done for a PHY disable, since that also changes + * the clocks routed to the PHY. + */ + ret = phy_notify_reset(udphy->phy_u3, PHY_NOTIFY_PRE_RESET); + if (ret) + return ret; + + /* + * Disable USB3 port, which among other things re-routes a DWC3 clock to + * avoid SErrors when the DWC3 registers are accessed while the PHY is + * disabled. This is only done, when the DWC3 is running as the accessed + * GRF registers and in PD_USB. + */ + if (udphy->sw_mode & UDPHY_MODE_USB) { rk_udphy_u3_port_disable(udphy, true); udelay(10); + } + if (udphy->status == UDPHY_MODE_NONE) { + /* Power up (incl. clocks) */ ret = rk_udphy_setup(udphy); if (ret) { phy_notify_reset(udphy->phy_u3, PHY_NOTIFY_POST_RESET); return ret; } - - if (!udphy->hs && udphy->hw_mode & UDPHY_MODE_USB) - rk_udphy_u3_port_disable(udphy, false); - udphy->phy_needs_reinit = false; - - phy_notify_reset(udphy->phy_u3, PHY_NOTIFY_POST_RESET); - } else if (udphy->phy_needs_reinit) { - phy_notify_reset(udphy->phy_u3, PHY_NOTIFY_PRE_RESET); - - rk_udphy_u3_port_disable(udphy, true); - udelay(10); - + } else if (target_mode == UDPHY_MODE_NONE) { + /* Power down (incl. clocks) */ + rk_udphy_disable(udphy); + } else { + /* Mode change => re-init */ ret = rk_udphy_init(udphy); if (ret) { phy_notify_reset(udphy->phy_u3, PHY_NOTIFY_POST_RESET); return ret; } - - if (!udphy->hs && udphy->hw_mode & UDPHY_MODE_USB) - rk_udphy_u3_port_disable(udphy, false); - udphy->phy_needs_reinit = false; - - phy_notify_reset(udphy->phy_u3, PHY_NOTIFY_POST_RESET); } - udphy->status |= mode; - - return 0; -} + /* Ensure USB3 support is enabled when supported */ + if (udphy->sw_mode & UDPHY_MODE_USB) + rk_udphy_u3_port_disable(udphy, !usb3_port_enable); -static void rk_udphy_power_off(struct rk_udphy *udphy, u8 mode) -{ - if (!(udphy->hw_mode & mode)) { - dev_info(udphy->dev, "mode 0x%02x is not support\n", mode); - return; - } - - if (!udphy->status) - return; + /* + * Inform DWC3, that we are done with the reset, so that it can deassert + * its PIPE reset line. This is sent in pair with a PRE_RESET allowing + * consumer driver to do paired resource requests (e.g. clocks) in their + * notification handlers. As we reroute the clocks, its also fine to + * send this after completely disabling the PHY. + */ + phy_notify_reset(udphy->phy_u3, PHY_NOTIFY_POST_RESET); - udphy->status &= ~mode; + udphy->status = target_mode; + udphy->phy_needs_reinit = false; - if (udphy->status == UDPHY_MODE_NONE) - rk_udphy_disable(udphy); + return 0; } static int rk_udphy_dp_phy_power_on(struct phy *phy) @@ -1057,11 +1083,15 @@ static int rk_udphy_dp_phy_power_on(struct phy *phy) int ret; scoped_guard(mutex, &udphy->mutex) { + udphy->sw_mode |= UDPHY_MODE_DP; + phy_set_bus_width(phy, udphy->dp_lanes); - ret = rk_udphy_power_on(udphy, UDPHY_MODE_DP); - if (ret) + ret = rk_udphy_update_power_state(udphy); + if (ret) { + udphy->sw_mode &= ~UDPHY_MODE_DP; return ret; + } rk_udphy_dp_lane_enable(udphy, udphy->dp_lanes); @@ -1084,10 +1114,10 @@ static int rk_udphy_dp_phy_power_off(struct phy *phy) guard(mutex)(&udphy->mutex); - rk_udphy_dp_lane_enable(udphy, 0); - rk_udphy_power_off(udphy, UDPHY_MODE_DP); + udphy->sw_mode &= ~UDPHY_MODE_DP; - return 0; + rk_udphy_dp_lane_enable(udphy, 0); + return rk_udphy_update_power_state(udphy); } /* @@ -1292,16 +1322,24 @@ static const struct phy_ops rk_udphy_dp_phy_ops = { static int rk_udphy_usb3_phy_init(struct phy *phy) { struct rk_udphy *udphy = phy_get_drvdata(phy); + int ret; guard(mutex)(&udphy->mutex); - /* DP only or high-speed, disable U3 port */ - if (!(udphy->hw_mode & UDPHY_MODE_USB) || udphy->hs) { + if (udphy->hs) { rk_udphy_u3_port_disable(udphy, true); return 0; } - return rk_udphy_power_on(udphy, UDPHY_MODE_USB); + udphy->sw_mode |= UDPHY_MODE_USB; + + ret = rk_udphy_update_power_state(udphy); + if (ret) { + udphy->sw_mode &= ~UDPHY_MODE_USB; + return ret; + } + + return 0; } static int rk_udphy_usb3_phy_exit(struct phy *phy) @@ -1310,13 +1348,12 @@ static int rk_udphy_usb3_phy_exit(struct phy *phy) guard(mutex)(&udphy->mutex); - /* DP only or high-speed */ - if (!(udphy->hw_mode & UDPHY_MODE_USB) || udphy->hs) + if (udphy->hs) return 0; - rk_udphy_power_off(udphy, UDPHY_MODE_USB); + udphy->sw_mode &= ~UDPHY_MODE_USB; - return 0; + return rk_udphy_update_power_state(udphy); } static const struct phy_ops rk_udphy_usb3_phy_ops = { -- 2.53.0