From: Icenowy Zheng <icenowy@aosc.io> Date: Tue, 6 Apr 2021 23:10:59 +0800 Subject: [PATCH] phy: rockchip: inno-usb2: fix hang when multiple controllers exit The OHCI and EHCI controllers are both bound to the same PHY. They will both do init and power_on operations when the controller is brought up and both do power_off and exit when the controller is stopped. However, the PHY uclass of U-Boot is not as sane as we thought -- they won't maintain a status mark for PHYs, and thus the functions of the PHYs could be called for multiple times. Calling init/power_on for multiple times have no severe problems, however calling power_off/exit for multiple times have a problem -- the first exit call will stop the PHY clock, and power_off/exit calls after it still trying to write to PHY registers. The write operation to PHY registers will fail because clock is already stopped. Adapt the count mechanism from phy-sun4i-usb to both init/exit and power_on/power_off functions to phy-rockchip-inno-usb2 to fix this problem. With this stopping USB controllers (manually or before booting a kernel) will work. Signed-off-by: Icenowy Zheng <icenowy@aosc.io> Fixes: ac97a9ece14e ("phy: rockchip: Add Rockchip USB2PHY driver") Tested-by: Peter Robinson <pbrobinson@gmail.com> --- Fix U-Boot v2020.10 regression, freezing on boot with USB boot enabled: https://gitlab.manjaro.org/manjaro-arm/packages/core/uboot-rockpro64/-/issues/4 Downloaded from: https://patchwork.ozlabs.org/project/uboot/patch/20210406151059.1187379-1-icenowy@aosc.io diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index 43f6e020a6a..2086192445d 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -47,6 +47,8 @@ struct rockchip_usb2phy { struct regmap *reg_base; struct clk phyclk; const struct rockchip_usb2phy_cfg *phy_cfg; + int init_count; + int power_on_count; }; static inline int property_enable(struct regmap *base, @@ -98,6 +100,10 @@ static int rockchip_usb2phy_power_on(struct phy *phy) struct rockchip_usb2phy *priv = dev_get_priv(parent); const struct rockchip_usb2phy_port_cfg *port_cfg = us2phy_get_port(phy); + priv->power_on_count++; + if (priv->power_on_count != 1) + return 0; + property_enable(priv->reg_base, &port_cfg->phy_sus, false); /* waiting for the utmi_clk to become stable */ @@ -112,6 +118,10 @@ static int rockchip_usb2phy_power_off(struct phy *phy) struct rockchip_usb2phy *priv = dev_get_priv(parent); const struct rockchip_usb2phy_port_cfg *port_cfg = us2phy_get_port(phy); + priv->power_on_count--; + if (priv->power_on_count != 0) + return 0; + property_enable(priv->reg_base, &port_cfg->phy_sus, true); return 0; @@ -123,6 +133,10 @@ static int rockchip_usb2phy_init(struct phy *phy) struct rockchip_usb2phy *priv = dev_get_priv(parent); int ret; + priv->init_count++; + if (priv->init_count != 1) + return 0; + ret = clk_enable(&priv->phyclk); if (ret && ret != -ENOSYS) { dev_err(phy->dev, "failed to enable phyclk (ret=%d)\n", ret); @@ -137,6 +151,10 @@ static int rockchip_usb2phy_exit(struct phy *phy) struct udevice *parent = dev_get_parent(phy->dev); struct rockchip_usb2phy *priv = dev_get_priv(parent); + priv->init_count--; + if (priv->init_count != 0) + return 0; + clk_disable(&priv->phyclk); return 0; @@ -281,6 +299,9 @@ static int rockchip_usb2phy_probe(struct udevice *dev) return ret; } + priv->power_on_count = 0; + priv->init_count = 0; + return 0; }