From nobody Tue Dec 16 02:39:14 2025 Received: from smtpbguseast3.qq.com (smtpbguseast3.qq.com [54.243.244.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6B0EC3B8D54; Mon, 8 Dec 2025 01:57:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=54.243.244.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1765159072; cv=none; b=UHNxwhE6kG52tB/b40aqLRi9Pd+aLxkX4TkgEtfBN8qGlJ38KUUGdgUP7uWSCAmY8DZw5mPYzlziV+EUYXqxuRMW2jYRAu7ThACqkQRpXkwvAbRCS9c+qTbHLDOfQNrJkwbdl9RWBBVbwox+pRWs7gAnY0kSyZsCZk6Djw2MwL0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1765159072; c=relaxed/simple; bh=R3heeE9hR3co2ACiydh9o+HhTZMeJ1YRf5V1MBmv3DU=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References; b=EAEYMK5h20HdEXdndnvsRzDKEqJi2rLJVCrS7b1NUXI5hvSJIP4ay+TQv15vHJ/pvnvqkgsLgZzdCmr/vLWdCCbQvLReWROD24RGZaBj07OqOySmLF0hjayciCEdyQcoVl9zNdQXE1c21kQox5UAGH2L2MwufUclKje/ioitrfk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=airkyi.com; spf=pass smtp.mailfrom=airkyi.com; dkim=pass (1024-bit key) header.d=airkyi.com header.i=@airkyi.com header.b=etkU/hoL; arc=none smtp.client-ip=54.243.244.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=airkyi.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=airkyi.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=airkyi.com header.i=@airkyi.com header.b="etkU/hoL" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=airkyi.com; s=altu2504; t=1765159065; bh=frpOn7dhVQ6Nxp3VUESgJ6aTlZPf3p22en66tauvZ1I=; h=From:To:Subject:Date:Message-Id; b=etkU/hoLNjMIAqbH9zBb2l9m6MJRsiXOrkJm1dkCYoEcX+2SUg1kW2qdq3RfhKnsm ilLZ8keDkjehK90On5f27vvOUTuyknQm0ueKAQ0/NsU28+o2W5KNBBoJ75LA6Genav UkzOsTqDz2CVppRQjDvonKovlwAO5Y9QFLsAfYgY= X-QQ-mid: esmtpsz19t1765158936t3f560edf X-QQ-Originating-IP: aZWMZCT3sbVio8bY6XdM9k+TQYCKjig5q76fW2P52OA= Received: from DESKTOP-8BT1A2O.localdomain ( [58.22.7.114]) by bizesmtp.qq.com (ESMTP) with id ; Mon, 08 Dec 2025 09:55:33 +0800 (CST) X-QQ-SSF: 0000000000000000000000000000000 X-QQ-GoodBg: 0 X-BIZMAIL-ID: 17018748767333659618 From: Chaoyi Chen To: Heikki Krogerus , Greg Kroah-Hartman , Dmitry Baryshkov , Peter Chen , Luca Ceresoli , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Vinod Koul , Kishon Vijay Abraham I , Heiko Stuebner , Sandy Huang , Andy Yan , Yubing Zhang , Frank Wang , Andrzej Hajda , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Simona Vetter , Amit Sunil Dhamne , Chaoyi Chen , Dragan Simic , Johan Jonker , Diederik de Haas , Peter Robinson Cc: linux-usb@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-phy@lists.infradead.org, linux-arm-kernel@lists.infradead.org, linux-rockchip@lists.infradead.org, dri-devel@lists.freedesktop.org Subject: [PATCH v13 06/11] phy: rockchip: phy-rockchip-typec: Add typec_mux/typec_switch support Date: Mon, 8 Dec 2025 09:54:55 +0800 Message-Id: <20251208015500.94-7-kernel@airkyi.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20251208015500.94-1-kernel@airkyi.com> References: <20251208015500.94-1-kernel@airkyi.com> X-QQ-SENDSIZE: 520 Feedback-ID: esmtpsz:airkyi.com:qybglogicsvrgz:qybglogicsvrgz6b-0 X-QQ-XMAILINFO: MZBJ53NGE19pgEdrPRZvITJZcEPbEwbSzqY398fSnVJ1SkKmwQsqwKX4 NDOhqszIkCoii/BfywTFiSruNsgk6p2O5XyYVF3jEZN/lDP5TD1US48sR+umRJSmdcPDvYZ TPV4ms4FSXxkAT+ZoLUBaOpw2TjZg77Ir7G19wcLHsar9sDC9/K0ZFn6qk9fZ2qWMaaSR3R cFLalciAi/HKsV1QIyiALyyLbBaExwaYU9pB2uqkb9UeGZXDNlYYalVozZ5SiZc1mp3QtBz DMSeo5nc9nUIchNpOViqEtT9S1jCVqeg4WRut4zHqm7JVj155S1GVWZHdfm/wC9XZHie+T9 D6c40/verDKRsOWaUaMh/Cyo45YrLNnvdGYijl9BFf2cSDxRbHP1M2tYMhLV1I2uh5PKas/ hnfQExNEOVuU/hkoXR0dSfb0KNZNkiKYvNfXGCh6035M0k2LRfo4AuKrvl3jVkLaRXxciK8 c6Vo3kNZax4H6XHPWNbjY0kkaiE/kAPNL4tVVOpj97Yv3rycWqqzMW6d9D7ozhk1SjtAMFL MyZnb9Bqy5PuPfCiIhsc+Fk0TBA8iDIQUDnYSGhk0xcJ/QCh2v/AxGzJvQG/3MRQNhSvslW LJZVOlsfdNNkjZxAx5xnZSkjdg2G06o3SuLrtyxbXSt3uBwJbDbwiELy/WoCOrZ0Q0ImCzn u/X/tB5CGFdNPOeMcdpl1oT6YNLEwib3yAWjxF17rpwz7T3RitgHiWkAb9Mvy8KB6VGIwD7 umtTmhqCb6TB1b2+hhndyc5XmCXL1M6V/52esD7wbHagcCuusTv+h0z4yLu97/kCkFv93xp KZme5tmEja2UOOL0AtNo5oUsQrHrIdzksOuIYG6RM8FepA8l7Xiy6NUNcOCg9hr+4ojOOB7 HWg2ZVtFp9NPXnsnvSq6K0w+NJc+0h1JfN0/4X/NZNnwm6iTV1rkFitN77fui5MtqWNpeo4 mnJ2ukOpJ0xtd+PdQa3CK1cm2W6xlEICoyuZV4A5vU4BABW261DPE/u1EHF6tCGtKYHfoN8 e6MRQjZT2AXXJ/objKmHRX/k1Lrnk= X-QQ-XMRINFO: Nq+8W0+stu50PRdwbJxPCL0= X-QQ-RECHKSPAM: 0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" From: Chaoyi Chen This patch add support for Type-C Port Controller Manager. Each PHY will register typec_mux and typec_switch when external Type-C controller is present. Type-C events are handled by TCPM without extcon. The extcon device should still be supported. Signed-off-by: Chaoyi Chen --- (no changes since v7) Changes in v6: - Fix depend in Kconfig. - Check DP svid in tcphy_typec_mux_set(). - Remove mode setting in tcphy_orien_sw_set(). (no changes since v5) Changes in v4: - Remove notify DP HPD state by USB/DP PHY. (no changes since v3) Changes in v2: - Fix compile error when CONFIG_TYPEC is not enabled. - Notify DP HPD state by USB/DP PHY. drivers/phy/rockchip/Kconfig | 1 + drivers/phy/rockchip/phy-rockchip-typec.c | 368 +++++++++++++++++++++- 2 files changed, 353 insertions(+), 16 deletions(-) diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index 14698571b607..db4adc7c53da 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -119,6 +119,7 @@ config PHY_ROCKCHIP_SNPS_PCIE3 config PHY_ROCKCHIP_TYPEC tristate "Rockchip TYPEC PHY Driver" depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST) + depends on TYPEC || TYPEC=3Dn select EXTCON select GENERIC_PHY select RESET_CONTROLLER diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockch= ip/phy-rockchip-typec.c index d9701b6106d5..1f5b4142cbe4 100644 --- a/drivers/phy/rockchip/phy-rockchip-typec.c +++ b/drivers/phy/rockchip/phy-rockchip-typec.c @@ -54,6 +54,8 @@ =20 #include #include +#include +#include =20 #define CMN_SSM_BANDGAP (0x21 << 2) #define CMN_SSM_BIAS (0x22 << 2) @@ -286,12 +288,23 @@ #define RX_DIAG_SC2C_DELAY (0x81e1 << 2) =20 #define PMA_LANE_CFG (0xc000 << 2) +#define PMA_LANE3_DP_LANE_SEL(x) (((x) & 0x3) << 14) +#define PMA_LANE3_INTERFACE_SEL(x) (((x) & 0x1) << 12) +#define PMA_LANE2_DP_LANE_SEL(x) (((x) & 0x3) << 10) +#define PMA_LANE2_INTERFACE_SEL(x) (((x) & 0x1) << 8) +#define PMA_LANE1_DP_LANE_SEL(x) (((x) & 0x3) << 6) +#define PMA_LANE1_INTERFACE_SEL(x) (((x) & 0x1) << 4) +#define PMA_LANE0_DP_LANE_SEL(x) (((x) & 0x3) << 2) +#define PMA_LANE0_INTERFACE_SEL(x) (((x) & 0x1) << 0) #define PIPE_CMN_CTRL1 (0xc001 << 2) #define PIPE_CMN_CTRL2 (0xc002 << 2) #define PIPE_COM_LOCK_CFG1 (0xc003 << 2) #define PIPE_COM_LOCK_CFG2 (0xc004 << 2) #define PIPE_RCV_DET_INH (0xc005 << 2) #define DP_MODE_CTL (0xc008 << 2) +#define PHY_DP_POWER_STATE_ACK_MASK GENMASK(7, 4) +#define PHY_DP_POWER_STATE_ACK_SHIFT 4 +#define PHY_DP_POWER_STATE_MASK GENMASK(3, 0) #define DP_CLK_CTL (0xc009 << 2) #define STS (0xc00F << 2) #define PHY_ISO_CMN_CTRL (0xc010 << 2) @@ -327,8 +340,15 @@ =20 #define DP_MODE_A0 BIT(4) #define DP_MODE_A2 BIT(6) -#define DP_MODE_ENTER_A0 0xc101 -#define DP_MODE_ENTER_A2 0xc104 + +#define DP_MODE_MASK 0xf +#define DP_MODE_ENTER_A0 BIT(0) +#define DP_MODE_ENTER_A2 BIT(2) +#define DP_MODE_ENTER_A3 BIT(3) +#define DP_MODE_A0_ACK BIT(4) +#define DP_MODE_A2_ACK BIT(6) +#define DP_MODE_A3_ACK BIT(7) +#define DP_LINK_RESET_DEASSERTED BIT(8) =20 #define PHY_MODE_SET_TIMEOUT 100000 =20 @@ -340,6 +360,31 @@ #define MODE_DFP_USB BIT(1) #define MODE_DFP_DP BIT(2) =20 +enum phy_dp_lane_num { + PHY_DP_LANE_0 =3D 0, + PHY_DP_LANE_1, + PHY_DP_LANE_2, + PHY_DP_LANE_3, +}; + +enum phy_pma_if { + PMA_IF_PIPE_PCS =3D 0, + PMA_IF_PHY_DP, +}; + +enum phy_typec_role { + TYPEC_PHY_USB =3D 0, + TYPEC_PHY_DP, + TYPEC_PHY_MAX, +}; + +enum phy_dp_power_state { + PHY_DP_POWER_STATE_A0 =3D 0, + PHY_DP_POWER_STATE_A1, + PHY_DP_POWER_STATE_A2, + PHY_DP_POWER_STATE_A3, +}; + struct usb3phy_reg { u32 offset; u32 enable_bit; @@ -372,18 +417,22 @@ struct rockchip_typec_phy { struct device *dev; void __iomem *base; struct extcon_dev *extcon; + struct typec_mux_dev *mux; + struct typec_switch_dev *sw; struct regmap *grf_regs; struct clk *clk_core; struct clk *clk_ref; struct reset_control *uphy_rst; struct reset_control *pipe_rst; struct reset_control *tcphy_rst; + struct phy *phys[TYPEC_PHY_MAX]; const struct rockchip_usb3phy_port_cfg *port_cfgs; /* mutex to protect access to individual PHYs */ struct mutex lock; =20 bool flip; u8 mode; + u8 new_mode; }; =20 struct phy_reg { @@ -454,6 +503,99 @@ static const struct rockchip_usb3phy_port_cfg rk3399_u= sb3phy_port_cfgs[] =3D { { /* sentinel */ } }; =20 +static int tcphy_cfg_usb3_to_usb2_only(struct rockchip_typec_phy *tcphy, + bool value); + +static int tcphy_dp_set_power_state(struct rockchip_typec_phy *tcphy, + enum phy_dp_power_state state) +{ + u32 ack, reg, sts =3D BIT(state); + int ret; + + /* + * Power state changes must not be requested until after the cmn_ready + * signal has gone active. + */ + reg =3D readl(tcphy->base + PMA_CMN_CTRL1); + if (!(reg & CMN_READY)) { + dev_err(tcphy->dev, "cmn_ready in the inactive state\n"); + return -EINVAL; + } + + reg =3D readl(tcphy->base + DP_MODE_CTL); + reg &=3D ~PHY_DP_POWER_STATE_MASK; + reg |=3D sts; + writel(reg, tcphy->base + DP_MODE_CTL); + + ret =3D readl_poll_timeout(tcphy->base + DP_MODE_CTL, + ack, (((ack & PHY_DP_POWER_STATE_ACK_MASK) >> + PHY_DP_POWER_STATE_ACK_SHIFT) =3D=3D sts), 10, + PHY_MODE_SET_TIMEOUT); + if (ret < 0) { + dev_err(tcphy->dev, "failed to enter power state %d\n", state); + return ret; + } + + return 0; +} + +/* + * For the TypeC PHY, the 4 lanes are mapping to the USB TypeC receptacle = pins + * as follows: + * ------------------------------------------------------------------- + * PHY Lanes/Module Pins TypeC Receptacle Pins + * ------------------------------------------------------------------- + * Lane0 (tx_p/m_ln_0) TX1+/TX1- (pins A2/A3) + * Lane1 (tx_rx_p/m_ln_1) RX1+/RX1- (pins B11/B10) + * Lane2 (tx_rx_p/m_ln_2) RX2+/RX2- (pins A11/A10) + * Lane3 (tx_p/m_ln_3) TX2+/TX2- (pins B2/B3) + * ------------------------------------------------------------------- + * + * USB and DP lanes mapping to TypeC PHY lanes for each of pin assignment + * options (normal connector orientation) described in the VESA DisplayPort + * Alt Mode on USB TypeC Standard as follows: + * + * ---------------------------------------------------------------------- + * PHY Lanes A B C D E F + * ---------------------------------------------------------------------- + * 0 ML1 SSTX ML2 SSTX ML2 SSTX + * 1 ML3 SSRX ML3 SSRX ML3 SSRX + * 2 ML2 ML1 ML0 ML0 ML0 ML0 + * 3 ML0 ML0 ML1 ML1 ML1 ML1 + * ---------------------------------------------------------------------- + */ +static void tcphy_set_lane_mapping(struct rockchip_typec_phy *tcphy, u8 mo= de) +{ + /* + * The PMA_LANE_CFG register is used to select whether a PMA lane + * is mapped for USB or PHY DP. The PMA_LANE_CFG register is + * configured based on a normal connector orientation. Logic in the + * PHY automatically handles the flipped connector case based on the + * setting of orientation of TypeC PHY. + */ + if (mode =3D=3D MODE_DFP_DP) { + /* This maps to VESA DP Alt Mode pin assignments C and E. */ + writel(PMA_LANE3_DP_LANE_SEL(PHY_DP_LANE_1) | + PMA_LANE3_INTERFACE_SEL(PMA_IF_PHY_DP) | + PMA_LANE2_DP_LANE_SEL(PHY_DP_LANE_0) | + PMA_LANE2_INTERFACE_SEL(PMA_IF_PHY_DP) | + PMA_LANE1_DP_LANE_SEL(PHY_DP_LANE_3) | + PMA_LANE1_INTERFACE_SEL(PMA_IF_PHY_DP) | + PMA_LANE0_DP_LANE_SEL(PHY_DP_LANE_2) | + PMA_LANE0_INTERFACE_SEL(PMA_IF_PHY_DP), + tcphy->base + PMA_LANE_CFG); + } else { + /* This maps to VESA DP Alt Mode pin assignments D and F. */ + writel(PMA_LANE3_DP_LANE_SEL(PHY_DP_LANE_1) | + PMA_LANE3_INTERFACE_SEL(PMA_IF_PHY_DP) | + PMA_LANE2_DP_LANE_SEL(PHY_DP_LANE_0) | + PMA_LANE2_INTERFACE_SEL(PMA_IF_PHY_DP) | + PMA_LANE1_INTERFACE_SEL(PMA_IF_PIPE_PCS) | + PMA_LANE0_INTERFACE_SEL(PMA_IF_PIPE_PCS), + tcphy->base + PMA_LANE_CFG); + } +} + static void tcphy_cfg_24m(struct rockchip_typec_phy *tcphy) { u32 i, rdata; @@ -743,8 +885,10 @@ static int tcphy_phy_init(struct rockchip_typec_phy *t= cphy, u8 mode) tcphy_dp_aux_set_flip(tcphy); =20 tcphy_cfg_24m(tcphy); + tcphy_set_lane_mapping(tcphy, mode); =20 if (mode =3D=3D MODE_DFP_DP) { + tcphy_cfg_usb3_to_usb2_only(tcphy, true); tcphy_cfg_dp_pll(tcphy); for (i =3D 0; i < 4; i++) tcphy_dp_cfg_lane(tcphy, i); @@ -768,7 +912,10 @@ static int tcphy_phy_init(struct rockchip_typec_phy *t= cphy, u8 mode) writel(PIN_ASSIGN_D_F, tcphy->base + PMA_LANE_CFG); } =20 - writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL); + val =3D readl(tcphy->base + DP_MODE_CTL); + val &=3D ~DP_MODE_MASK; + val |=3D DP_MODE_ENTER_A2 | DP_LINK_RESET_DEASSERTED; + writel(val, tcphy->base + DP_MODE_CTL); =20 reset_control_deassert(tcphy->uphy_rst); =20 @@ -811,8 +958,9 @@ static int tcphy_get_mode(struct rockchip_typec_phy *tc= phy) u8 mode; int ret, ufp, dp; =20 + /* If extcon not exist, try to use tcpm mode */ if (!edev) - return MODE_DFP_USB; + return tcphy->new_mode; =20 ufp =3D extcon_get_state(edev, EXTCON_USB); dp =3D extcon_get_state(edev, EXTCON_DISP_DP); @@ -850,6 +998,71 @@ static int tcphy_get_mode(struct rockchip_typec_phy *t= cphy) return mode; } =20 +#if IS_ENABLED(CONFIG_TYPEC) +static int tcphy_orien_sw_set(struct typec_switch_dev *sw, + enum typec_orientation orien) +{ + struct rockchip_typec_phy *tcphy =3D typec_switch_get_drvdata(sw); + + mutex_lock(&tcphy->lock); + + if (orien =3D=3D TYPEC_ORIENTATION_NONE) { + tcphy->new_mode =3D MODE_DISCONNECT; + goto unlock_ret; + } + + tcphy->flip =3D (orien =3D=3D TYPEC_ORIENTATION_REVERSE) ? true : false; + +unlock_ret: + mutex_unlock(&tcphy->lock); + return 0; +} + +static void udphy_orien_switch_unregister(void *data) +{ + struct rockchip_typec_phy *tcphy =3D data; + + typec_switch_unregister(tcphy->sw); +} + +static int tcphy_setup_orien_switch(struct rockchip_typec_phy *tcphy) +{ + struct typec_switch_desc sw_desc =3D { }; + struct device_node *np; + int ret =3D 0; + + np =3D of_get_child_by_name(tcphy->dev->of_node, "usb3-port"); + if (!np) + return 0; + + if (!of_property_read_bool(np, "orientation-switch")) + goto put_np; + + sw_desc.drvdata =3D tcphy; + sw_desc.fwnode =3D device_get_named_child_node(tcphy->dev, "usb3-port"); + sw_desc.set =3D tcphy_orien_sw_set; + + tcphy->sw =3D typec_switch_register(tcphy->dev, &sw_desc); + if (IS_ERR(tcphy->sw)) { + dev_err(tcphy->dev, "Error register typec orientation switch: %ld\n", + PTR_ERR(tcphy->sw)); + ret =3D PTR_ERR(tcphy->sw); + goto put_np; + } + + ret =3D devm_add_action_or_reset(tcphy->dev, udphy_orien_switch_unregiste= r, tcphy); + +put_np: + of_node_put(np); + return ret; +} +#else +static int tcphy_setup_orien_switch(struct rockchip_typec_phy *tcphy) +{ + return 0; +} +#endif + static int tcphy_cfg_usb3_to_usb2_only(struct rockchip_typec_phy *tcphy, bool value) { @@ -989,14 +1202,9 @@ static int rockchip_dp_phy_power_on(struct phy *phy) =20 tcphy_dp_aux_calibration(tcphy); =20 - writel(DP_MODE_ENTER_A0, tcphy->base + DP_MODE_CTL); - - ret =3D readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL, - val, val & DP_MODE_A0, 1000, - PHY_MODE_SET_TIMEOUT); - if (ret < 0) { - writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL); - dev_err(tcphy->dev, "failed to wait TCPHY enter A0\n"); + ret =3D tcphy_dp_set_power_state(tcphy, PHY_DP_POWER_STATE_A0); + if (ret) { + dev_err(tcphy->dev, "failed to enter A0 power state\n"); goto power_on_finish; } =20 @@ -1013,6 +1221,7 @@ static int rockchip_dp_phy_power_on(struct phy *phy) static int rockchip_dp_phy_power_off(struct phy *phy) { struct rockchip_typec_phy *tcphy =3D phy_get_drvdata(phy); + int ret; =20 mutex_lock(&tcphy->lock); =20 @@ -1021,7 +1230,11 @@ static int rockchip_dp_phy_power_off(struct phy *phy) =20 tcphy->mode &=3D ~MODE_DFP_DP; =20 - writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL); + ret =3D tcphy_dp_set_power_state(tcphy, PHY_DP_POWER_STATE_A2); + if (ret) { + dev_err(tcphy->dev, "failed to enter A2 power state\n"); + goto unlock; + } =20 if (tcphy->mode =3D=3D MODE_DISCONNECT) tcphy_phy_deinit(tcphy); @@ -1037,6 +1250,93 @@ static const struct phy_ops rockchip_dp_phy_ops =3D { .owner =3D THIS_MODULE, }; =20 +#if IS_ENABLED(CONFIG_TYPEC) +static int tcphy_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux= _state *state) +{ + struct rockchip_typec_phy *tcphy =3D typec_mux_get_drvdata(mux); + struct typec_displayport_data *data; + int hpd =3D 0; + + mutex_lock(&tcphy->lock); + + switch (state->mode) { + case TYPEC_STATE_SAFE: + fallthrough; + case TYPEC_STATE_USB: + tcphy->new_mode =3D MODE_DFP_USB; + phy_set_bus_width(tcphy->phys[TYPEC_PHY_DP], 0); + break; + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + if (state->alt->svid !=3D USB_TYPEC_DP_SID) + break; + tcphy->new_mode =3D MODE_DFP_DP; + data =3D state->data; + hpd =3D !!(data->status & DP_STATUS_HPD_STATE); + phy_set_bus_width(tcphy->phys[TYPEC_PHY_DP], hpd ? 4 : 0); + break; + case TYPEC_DP_STATE_D: + if (state->alt->svid !=3D USB_TYPEC_DP_SID) + break; + tcphy->new_mode =3D MODE_DFP_DP | MODE_DFP_USB; + data =3D state->data; + hpd =3D !!(data->status & DP_STATUS_HPD_STATE); + phy_set_bus_width(tcphy->phys[TYPEC_PHY_DP], hpd ? 2 : 0); + break; + default: + break; + } + + mutex_unlock(&tcphy->lock); + + return 0; +} + +static void tcphy_typec_mux_unregister(void *data) +{ + struct rockchip_typec_phy *tcphy =3D data; + + typec_mux_unregister(tcphy->mux); +} + +static int tcphy_setup_typec_mux(struct rockchip_typec_phy *tcphy) +{ + struct typec_mux_desc mux_desc =3D {}; + struct device_node *np; + int ret =3D 0; + + np =3D of_get_child_by_name(tcphy->dev->of_node, "dp-port"); + if (!np) + return 0; + + if (!of_property_read_bool(np, "mode-switch")) + goto put_np; + + mux_desc.drvdata =3D tcphy; + mux_desc.fwnode =3D device_get_named_child_node(tcphy->dev, "dp-port"); + mux_desc.set =3D tcphy_typec_mux_set; + + tcphy->mux =3D typec_mux_register(tcphy->dev, &mux_desc); + if (IS_ERR(tcphy->mux)) { + dev_err(tcphy->dev, "Error register typec mux: %ld\n", + PTR_ERR(tcphy->mux)); + ret =3D PTR_ERR(tcphy->mux); + goto put_np; + } + + ret =3D devm_add_action_or_reset(tcphy->dev, tcphy_typec_mux_unregister, = tcphy); + +put_np: + of_node_put(np); + return ret; +} +#else +static int tcphy_setup_typec_mux(struct rockchip_typec_phy *tcphy) +{ + return 0; +} +#endif + static int tcphy_parse_dt(struct rockchip_typec_phy *tcphy, struct device *dev) { @@ -1095,6 +1395,25 @@ static void typec_phy_pre_init(struct rockchip_typec= _phy *tcphy) tcphy->mode =3D MODE_DISCONNECT; } =20 +static int typec_dp_lane_get(struct rockchip_typec_phy *tcphy) +{ + int dp_lanes; + + switch (tcphy->new_mode) { + case MODE_DFP_DP: + dp_lanes =3D 4; + break; + case MODE_DFP_DP | MODE_DFP_USB: + dp_lanes =3D 2; + break; + default: + dp_lanes =3D 0; + break; + } + + return dp_lanes; +} + static int rockchip_typec_phy_probe(struct platform_device *pdev) { struct device *dev =3D &pdev->dev; @@ -1142,6 +1461,7 @@ static int rockchip_typec_phy_probe(struct platform_d= evice *pdev) return ret; =20 tcphy->dev =3D dev; + tcphy->new_mode =3D MODE_DFP_USB; platform_set_drvdata(pdev, tcphy); mutex_init(&tcphy->lock); =20 @@ -1151,6 +1471,7 @@ static int rockchip_typec_phy_probe(struct platform_d= evice *pdev) if (IS_ERR(tcphy->extcon)) { if (PTR_ERR(tcphy->extcon) =3D=3D -ENODEV) { tcphy->extcon =3D NULL; + dev_info(dev, "extcon not exist, try to use typec mux\n"); } else { if (PTR_ERR(tcphy->extcon) !=3D -EPROBE_DEFER) dev_err(dev, "Invalid or missing extcon\n"); @@ -1158,19 +1479,34 @@ static int rockchip_typec_phy_probe(struct platform= _device *pdev) } } =20 + ret =3D tcphy_setup_orien_switch(tcphy); + if (ret) + return ret; + + ret =3D tcphy_setup_typec_mux(tcphy); + if (ret) + return ret; + pm_runtime_enable(dev); =20 for_each_available_child_of_node(np, child_np) { struct phy *phy; =20 - if (of_node_name_eq(child_np, "dp-port")) + if (of_node_name_eq(child_np, "dp-port")) { phy =3D devm_phy_create(dev, child_np, &rockchip_dp_phy_ops); - else if (of_node_name_eq(child_np, "usb3-port")) + if (!IS_ERR(phy)) { + tcphy->phys[TYPEC_PHY_DP] =3D phy; + phy_set_bus_width(phy, typec_dp_lane_get(tcphy)); + } + } else if (of_node_name_eq(child_np, "usb3-port")) { phy =3D devm_phy_create(dev, child_np, &rockchip_usb3_phy_ops); - else + if (!IS_ERR(phy)) + tcphy->phys[TYPEC_PHY_USB] =3D phy; + } else { continue; + } =20 if (IS_ERR(phy)) { dev_err(dev, "failed to create phy: %pOFn\n", --=20 2.51.1