diff --git a/arch/riscv/boot/dts/zhihe/a210-dev.dts b/arch/riscv/boot/dts/zhihe/a210-dev.dts index d167d53a9..1da12f7ac 100755 --- a/arch/riscv/boot/dts/zhihe/a210-dev.dts +++ b/arch/riscv/boot/dts/zhihe/a210-dev.dts @@ -254,14 +254,14 @@ }; &ao_gpio0 { - pwoer-5v-en-hog { + power-5v-en-hog { gpio-hog; gpios = <29 GPIO_ACTIVE_HIGH>; output-high; line-name = "power_5v_en"; }; - pwoer-3v3-en-hog { + power-3v3-en-hog { gpio-hog; gpios = <26 GPIO_ACTIVE_HIGH>; output-high; @@ -448,10 +448,10 @@ pins = "AOI2C1_SCL", "AOI2C1_SDA"; function = "aoi2c1"; bias-disable; - drive-strength = <7>; + drive-strength = <25>; input-enable; input-schmitt-enable; - slew-rate = <0>; + slew-rate = <1>; }; }; usbc0_int: usbc0-int { @@ -665,10 +665,10 @@ pins = "GPIO2_28", "GPIO2_29"; function = "i2c5"; bias-disable; - drive-strength = <7>; + drive-strength = <25>; input-enable; input-schmitt-enable; - slew-rate = <0>; + slew-rate = <1>; }; }; uart5_pins: uart5-1 { @@ -697,10 +697,10 @@ pins = "GPIO2_8", "GPIO2_9"; function = "i2c6"; bias-disable; - drive-strength = <7>; + drive-strength = <25>; input-enable; input-schmitt-enable; - slew-rate = <0>; + slew-rate = <1>; }; }; i2s1_pins: i2s1-0 { @@ -718,10 +718,10 @@ pins = "GPIO2_10", "GPIO2_11"; function = "i2c7"; bias-disable; - drive-strength = <7>; + drive-strength = <25>; input-enable; input-schmitt-enable; - slew-rate = <0>; + slew-rate = <1>; }; }; i2c4_pins: i2c4-2 { @@ -729,10 +729,10 @@ pins = "GPIO2_26", "GPIO2_27"; function = "i2c4"; bias-disable; - drive-strength = <7>; + drive-strength = <25>; input-enable; input-schmitt-enable; - slew-rate = <0>; + slew-rate = <1>; }; }; pwm1_pins: pwm1-1 { @@ -1089,6 +1089,13 @@ remote-endpoint = <&usbdp_phy0_orientation_switch>; }; }; + + port@1 { + reg = <1>; + dp_altmode_mux: endpoint { + remote-endpoint = <&usbdp_phy0_dp_altmode_mux>; + }; + }; }; }; }; @@ -1330,10 +1337,18 @@ }; }; +&dp0 { + force-hpd = <1>; + extcon = <&usb31_c10phy>; +}; + /* USB3.1/DP Combo PHY0 */ &usb31_c10phy { orientation-switch; + mode-switch; status = "okay"; + svid = <0xff01>; + #extcon-cells = <0>; port { #address-cells = <1>; @@ -1342,5 +1357,10 @@ reg = <0>; remote-endpoint = <&usbc0_orien_sw>; }; + + usbdp_phy0_dp_altmode_mux: endpoint@1 { + reg = <1>; + remote-endpoint = <&dp_altmode_mux>; + }; }; }; diff --git a/arch/riscv/boot/dts/zhihe/a210-evb.dts b/arch/riscv/boot/dts/zhihe/a210-evb.dts index bb9b7b048..561e3f48a 100644 --- a/arch/riscv/boot/dts/zhihe/a210-evb.dts +++ b/arch/riscv/boot/dts/zhihe/a210-evb.dts @@ -410,10 +410,10 @@ pins = "GPIO0_22", "GPIO0_23"; function = "i2c2"; bias-disable; - drive-strength = <7>; + drive-strength = <25>; input-enable; input-schmitt-enable; - slew-rate = <0>; + slew-rate = <1>; }; }; tdm_pins: tdm-0 { @@ -421,7 +421,7 @@ pins = "GPIO0_19", "GPIO0_20", "GPIO0_21"; function = "tdm"; bias-disable; - drive-strength = <15>; + drive-strength = <25>; input-enable; input-schmitt-enable; slew-rate = <0>; @@ -432,10 +432,10 @@ pins = "GPIO0_24", "GPIO0_25"; function = "i2c0"; bias-disable; - drive-strength = <7>; + drive-strength = <25>; input-enable; input-schmitt-enable; - slew-rate = <0>; + slew-rate = <1>; }; }; can0_pins: can0-0 { @@ -464,10 +464,10 @@ pins = "GPIO0_26", "GPIO0_27"; function = "i2c1"; bias-disable; - drive-strength = <7>; + drive-strength = <25>; input-enable; input-schmitt-enable; - slew-rate = <0>; + slew-rate = <1>; }; }; can1_pins: can1-0 { @@ -657,10 +657,10 @@ pins = "GPIO2_2", "GPIO2_3"; function = "i2c5"; bias-disable; - drive-strength = <7>; + drive-strength = <25>; input-enable; input-schmitt-enable; - slew-rate = <0>; + slew-rate = <1>; }; }; uart5_pins: uart5-0 { @@ -689,10 +689,10 @@ pins = "GPIO2_4", "GPIO2_5"; function = "i2c6"; bias-disable; - drive-strength = <7>; + drive-strength = <25>; input-enable; input-schmitt-enable; - slew-rate = <0>; + slew-rate = <1>; }; }; uart6_pins: uart6-0 { @@ -784,10 +784,10 @@ // pins = "GPIO2_6", "GPIO2_7"; // function = "i2c4"; // bias-disable; - // drive-strength = <7>; + // drive-strength = <25>; // input-enable; // input-schmitt-enable; - // slew-rate = <0>; + // slew-rate = <1>; // }; // }; i2s2_pins: i2s2-0 { @@ -826,10 +826,10 @@ pins = "GPIO2_10", "GPIO2_11"; function = "i2c7"; bias-disable; - drive-strength = <7>; + drive-strength = <25>; input-enable; input-schmitt-enable; - slew-rate = <0>; + slew-rate = <1>; }; }; spi1_pins: spi1-1 { @@ -859,10 +859,10 @@ pins = "GPIO2_26", "GPIO2_27"; function = "i2c4"; bias-disable; - drive-strength = <7>; + drive-strength = <25>; input-enable; input-schmitt-enable; - slew-rate = <0>; + slew-rate = <1>; }; }; pwm1_pins: pwm1-1 { @@ -881,10 +881,10 @@ pins = "GPIO2_24", "GPIO2_25"; function = "i2c3"; bias-disable; - drive-strength = <7>; + drive-strength = <25>; input-enable; input-schmitt-enable; - slew-rate = <0>; + slew-rate = <1>; }; }; hdmi_pins: hdmi-0 { @@ -1730,3 +1730,7 @@ opp-microvolt = <1000000>; }; }; + +&dp0 { + force-hpd = <1>; +}; diff --git a/arch/riscv/boot/dts/zhihe/a210-soc-peri.dtsi b/arch/riscv/boot/dts/zhihe/a210-soc-peri.dtsi index b2796361c..af2895b8b 100755 --- a/arch/riscv/boot/dts/zhihe/a210-soc-peri.dtsi +++ b/arch/riscv/boot/dts/zhihe/a210-soc-peri.dtsi @@ -203,8 +203,8 @@ <&clk_usb DPTX_PCLK_EN>; clock-names = "i2s", "ipi", "aux", "gtc", "pclk"; power-domains = <&power_vo>; - force-hpd = <1>; #sound-dai-cells = <1>; + phys = <&usbdp_phy0_dp>; ports { #address-cells = <1>; #size-cells = <0>; @@ -1397,6 +1397,16 @@ reset-names = "phy-rst"; #phy-cells = <0>; power-domains = <&power_usb>; + + usbdp_phy0_dp: dp-port { + #phy-cells = <0>; + status = "okay"; + }; + + usbdp_phy0_u3: u3-port { + #phy-cells = <0>; + status = "okay"; + }; }; usb20phy0: phy@08300000 { @@ -1460,7 +1470,7 @@ reg-io-width = <4>; dr_mode = "host"; power-domains = <&power_usb>; - phys = <&usb20phy2>, <&usb31_c10phy>; + phys = <&usb20phy2>, <&usbdp_phy0_u3>; phy-names = "usb2-phy", "usb3-phy"; //iommus = <&iommu DEVID_DIE0_USB3_0>; snps,usb3_lpm_capable; diff --git a/arch/riscv/configs/a210_evb_defconfig b/arch/riscv/configs/a210_evb_defconfig index bdcb9c0d6..cc395dc90 100644 --- a/arch/riscv/configs/a210_evb_defconfig +++ b/arch/riscv/configs/a210_evb_defconfig @@ -342,6 +342,7 @@ CONFIG_V4L_MEM2MEM_DRIVERS=y CONFIG_DRM=y CONFIG_DRM_LOAD_EDID_FIRMWARE=y CONFIG_DRM_DP_AUX_CHARDEV=y +CONFIG_TYPEC_DP_ALTMODE=m CONFIG_DRM_PANEL_SIMPLE=m CONFIG_DRM_PANEL_ILITEK_ILI9881C=m CONFIG_DRM_PANEL_JADARD_JD9365DA_H3=y diff --git a/drivers/gpu/drm/verisilicon/dw_dp.c b/drivers/gpu/drm/verisilicon/dw_dp.c index ece1c83a1..27ea02272 100644 --- a/drivers/gpu/drm/verisilicon/dw_dp.c +++ b/drivers/gpu/drm/verisilicon/dw_dp.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -47,9 +48,6 @@ #include #include -#define NO_PHY 1 -#define MAX_FREQ 540000 -#define LANES_NUM 4 #define RK_IF_PROP_COLOR_DEPTH "color_depth" #define RK_IF_PROP_COLOR_FORMAT "color_format" @@ -561,7 +559,9 @@ struct dw_dp { bool force_hpd; struct dw_dp_hotplug hotplug; struct mutex irq_lock; - struct extcon_dev *extcon; + struct extcon_dev *extcon; /* Extcon provider for audio/userspace */ + struct extcon_dev *phy_extcon; /* Extcon consumer from PHY */ + struct notifier_block phy_extcon_nb; /* Notifier for PHY extcon events */ struct drm_bridge bridge; struct drm_connector connector; @@ -696,15 +696,6 @@ static const struct dw_dp_output_format possible_output_fmts[] = { DPTX_VM_RGB_6BIT, 6, 18 }, }; -static unsigned long g_init_threshold = 0; -static unsigned long g_freqs = 0; -//void rockchip_drm_register_sub_dev(struct rockchip_drm_sub_dev *sub_dev) -//{ -// mutex_lock(&rockchip_drm_sub_dev_lock); -// list_add_tail(&sub_dev->list, &rockchip_drm_sub_dev_list); -// mutex_unlock(&rockchip_drm_sub_dev_lock); -//} - uint32_t rockchip_drm_of_find_possible_crtcs(struct drm_device *dev, struct device_node *port) { @@ -884,7 +875,7 @@ static int dw_dp_hdcp2_auth_check(struct dw_dp *dp) dp->hdcp.hdcp2_encrypted = true; - dev_err(dp->dev, "HDCP2 authentication succeed\n"); + dev_dbg(dp->dev, "HDCP2 authentication succeed\n"); return ret; } @@ -961,7 +952,7 @@ static int __maybe_unused dw_dp_hdcp_auth_check(struct dw_dp *dp) } else { regmap_write(dp->regmap, DPTX_HDCPAPIINTCLR, HDCP_ENGAGED); dp->hdcp.hdcp_encrypted = true; - dev_err(dp->dev, "HDCP authentication succeed\n"); + dev_dbg(dp->dev, "HDCP authentication succeed\n"); } return ret; @@ -1611,7 +1602,7 @@ static int dw_dp_connector_atomic_check(struct drm_connector *conn, dp_old_state = connector_to_dp_state(old_state); dp_new_state = connector_to_dp_state(new_state); - //dw_dp_hdcp_atomic_check(conn, state); + dw_dp_hdcp_atomic_check(conn, state); if (!new_state->crtc) return 0; @@ -1636,7 +1627,7 @@ static int dw_dp_connector_atomic_check(struct drm_connector *conn, if ((dp_old_state->bpc != dp_new_state->bpc) || (dp_old_state->color_format != dp_new_state->color_format)) { if ((dp_old_state->bpc == 0) && (dp_new_state->bpc == 0)) - dev_err(dp->dev, "still auto set color mode\n"); + dev_dbg(dp->dev, "still auto set color mode\n"); else crtc_state->mode_changed = true; } @@ -1758,18 +1749,12 @@ static int dw_dp_link_probe(struct dw_dp *dp) !!(dpcd & DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED); link->revision = link->dpcd[DP_DPCD_REV]; -#if NO_PHY - //link->rate = MAX_FREQ; - //link->lanes = LANES_NUM; - link->rate = drm_dp_max_link_rate(link->dpcd); - link->lanes = min_t(u8, LANES_NUM, drm_dp_max_lane_count(link->dpcd)); -#else link->rate = min_t(u32, min(dp->max_link_rate, dp->phy->attrs.max_link_rate * 100), drm_dp_max_link_rate(link->dpcd)); link->lanes = min_t(u8, phy_get_bus_width(dp->phy), drm_dp_max_lane_count(link->dpcd)); -#endif + link->caps.enhanced_framing = drm_dp_enhanced_frame_cap(link->dpcd); link->caps.tps3_supported = drm_dp_tps3_supported(link->dpcd); link->caps.tps4_supported = drm_dp_tps4_supported(link->dpcd); @@ -1786,9 +1771,7 @@ static int dw_dphy_set_voltage_pre_level(struct dw_dp *dp, int lane_id, int leve static int dw_dp_phy_update_vs_emph(struct dw_dp *dp, unsigned int rate, unsigned int lanes, struct drm_dp_link_train_set *train_set) { -#if !NO_PHY union phy_configure_opts phy_cfg; -#endif unsigned int *vs, *pe; u8 buf[4]; int i, ret; @@ -1796,19 +1779,11 @@ static int dw_dp_phy_update_vs_emph(struct dw_dp *dp, unsigned int rate, unsigne vs = train_set->voltage_swing; pe = train_set->pre_emphasis; -// unsigned int phy_ctl_val; -// regmap_read(dp->regmap, DPTX_PHYIF_CTRL, &phy_ctl_val); -// regmap_update_bits(dp->regmap, DPTX_PHYIF_CTRL, PHY_POWERDOWN, -// FIELD_PREP(PHY_POWERDOWN, 0x3)); -// for (i = 0; i < lanes; i++) { dw_dphy_set_voltage_sw_level(dp, i, vs[i]); dw_dphy_set_voltage_pre_level(dp, i, pe[i]); } -// regmap_update_bits(dp->regmap, DPTX_PHYIF_CTRL, PHY_POWERDOWN, -// FIELD_PREP(PHY_POWERDOWN, 0x0)); -#if !NO_PHY for (i = 0; i < lanes; i++) { phy_cfg.dp.voltage[i] = vs[i]; phy_cfg.dp.pre[i] = pe[i]; @@ -1821,9 +1796,9 @@ static int dw_dp_phy_update_vs_emph(struct dw_dp *dp, unsigned int rate, unsigne phy_cfg.dp.set_voltages = true; ret = phy_configure(dp->phy, &phy_cfg); - if (ret) + if (ret) { return ret; -#endif + } for (i = 0; i < lanes; i++) { buf[i] = (vs[i] << DP_TRAIN_VOLTAGE_SWING_SHIFT) | @@ -1866,7 +1841,7 @@ static int dw_dphy_set_rate(struct dw_dp *dp, unsigned int rate) val = 3; break; default: - dev_err(dp->dev, "%s, %d, Error, dp unsopport rate %u\n", __func__, __LINE__, rate); + dev_err(dp->dev, "Error, dp unsopport rate %u\n", rate); return -1; } @@ -1877,7 +1852,6 @@ static int dw_dphy_set_rate(struct dw_dp *dp, unsigned int rate) static int dw_dphy_set_voltage_sw_level(struct dw_dp *dp, int lane_id, int level) { if (level < 0 || level > 3) { - dev_err(dp->dev, "%s, %d, Error, dp sw unsopport level %d\n", __func__, __LINE__, level); return -1; } @@ -1897,7 +1871,7 @@ static int dw_dphy_set_voltage_sw_level(struct dw_dp *dp, int lane_id, int level static int dw_dphy_set_voltage_pre_level(struct dw_dp *dp, int lane_id, int level) { if (level < 0 || level > 3) { - dev_err(dp->dev, "%s, %d, Error, dp pre unsopport level%d\n", __func__, __LINE__, level); + dev_err(dp->dev, "Error, dp pre unsopport level%d\n", level); return -1; } @@ -1918,25 +1892,11 @@ static int dw_dp_phy_configure(struct dw_dp *dp, unsigned int rate, unsigned int lanes, bool ssc) { /* Move PHY to P3 */ - unsigned int phy_ctl_val = 0, count = 10; regmap_update_bits(dp->regmap, DPTX_PHYIF_CTRL, PHY_POWERDOWN, FIELD_PREP(PHY_POWERDOWN, 0x3)); - regmap_read(dp->regmap, DPTX_PHYIF_CTRL, &phy_ctl_val); - while((PHY_BUSY & phy_ctl_val) && count) { - regmap_read(dp->regmap, DPTX_PHYIF_CTRL, &phy_ctl_val); - count--; - dev_dbg(dp->dev, "dp wait phy busy 0x%lx\n", (PHY_BUSY & phy_ctl_val)); - udelay(100); - } - - if (PHY_BUSY & phy_ctl_val) { - dev_err(dp->dev, "%s, %d, Error, phy is busy\n", __func__, __LINE__); - } - dw_dphy_set_rate(dp, rate); -#if !NO_PHY union phy_configure_opts phy_cfg; int ret; phy_cfg.dp.lanes = lanes; @@ -1948,7 +1908,6 @@ static int dw_dp_phy_configure(struct dw_dp *dp, unsigned int rate, ret = phy_configure(dp->phy, &phy_cfg); if (ret) return ret; -#endif regmap_update_bits(dp->regmap, DPTX_PHYIF_CTRL, PHY_LANES, FIELD_PREP(PHY_LANES, lanes / 2)); @@ -2065,7 +2024,6 @@ static int dw_dp_link_train_set_pattern(struct dw_dp *dp, u32 pattern) ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, buf | pattern); if (ret < 0) { - dev_err(dp->dev, "%s, %d, dpct write error %d\n", __func__, __LINE__, ret); return ret; } @@ -2237,6 +2195,7 @@ static int dw_dp_link_downgrade(struct dw_dp *dp) { struct dw_dp_link *link = &dp->link; struct dw_dp_video *video = &dp->video; + u32 rate = link->rate; switch (link->rate) { case 162000: @@ -2252,9 +2211,11 @@ static int dw_dp_link_downgrade(struct dw_dp *dp) break; } + /*If the downgrade fails, retry using the previous rate. */ if (!dw_dp_bandwidth_ok(dp, &video->mode, video->bpp, link->lanes, - link->rate)) - return -E2BIG; + link->rate)) { + link->rate = rate; + } return 0; } @@ -2266,6 +2227,10 @@ static int dw_dp_link_train_full(struct dw_dp *dp) retry: dw_dp_link_train_init(&link->train); + + dev_dbg(dp->dev, "full-training link: %u lane%s at %u MHz\n", + link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100); + ret = dw_dp_link_configure(dp); if (ret < 0) { dev_err(dp->dev, "failed to configure DP link: %d\n", ret); @@ -2279,11 +2244,10 @@ retry: } if (!link->train.clock_recovered) { - dev_err(dp->dev, "clock recovery failed, downgrading link\n"); + dev_info(dp->dev, "clock recovery failed, downgrading link, retry\n"); ret = dw_dp_link_downgrade(dp); if (ret < 0) { - dev_err(dp->dev, "downgrade failed, goto out\n"); goto out; } else goto retry; @@ -2318,6 +2282,9 @@ static int dw_dp_link_train_fast(struct dw_dp *dp) dw_dp_link_train_init(&link->train); + dev_dbg(dp->dev, "fast-training link: %u lane%s at %u MHz\n", + link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100); + ret = dw_dp_link_configure(dp); if (ret < 0) { dev_err(dp->dev, "failed to configure DP link: %d\n", ret); @@ -2664,12 +2631,11 @@ static int dw_dp_video_enable(struct dw_dp *dp) u8 color_format = video->color_format; u8 bpc = video->bpc; u8 pixel_mode = video->pixel_mode; - u8 bpp = video->bpp, vic; - u32 init_threshold; + u8 bpp = video->bpp, init_threshold, vic; u32 hactive, hblank, h_sync_width, h_front_porch; u32 vactive, vblank, v_sync_width, v_front_porch; - u32 vstart = mode->vsync_end- mode->vdisplay; - u32 hstart = mode->hsync_end- mode->hdisplay; + u32 vstart = mode->vtotal - mode->vsync_start; + u32 hstart = mode->htotal - mode->hsync_start; u32 peak_stream_bandwidth, link_bandwidth; u32 average_bytes_per_tu, average_bytes_per_tu_frac; u32 ts, hblank_interval; @@ -2696,7 +2662,6 @@ static int dw_dp_video_enable(struct dw_dp *dp) regmap_write(dp->regmap, DPTX_VINPUT_POLARITY_CTRL, value); /* Configure DPTX_VIDEO_CONFIG1 register */ - hactive = mode->hdisplay; hblank = mode->htotal - mode->hdisplay; value = FIELD_PREP(HACTIVE, hactive) | FIELD_PREP(HBLANK, hblank); @@ -2716,7 +2681,6 @@ static int dw_dp_video_enable(struct dw_dp *dp) /* Configure DPTX_VIDEO_CONFIG2 register */ vblank = mode->vtotal - mode->vdisplay; vactive = mode->vdisplay; - regmap_write(dp->regmap, DPTX_VIDEO_CONFIG2, FIELD_PREP(VBLANK, vblank) | FIELD_PREP(VACTIVE, vactive)); @@ -2733,16 +2697,15 @@ static int dw_dp_video_enable(struct dw_dp *dp) regmap_write(dp->regmap, DPTX_VIDEO_CONFIG4, FIELD_PREP(V_SYNC_WIDTH, v_sync_width) | FIELD_PREP(V_FRONT_PORCH, v_front_porch)); + /* Configure DPTX_VIDEO_CONFIG5 register */ peak_stream_bandwidth = mode->clock * bpp / 8; link_bandwidth = (link->rate / 1000) * link->lanes; ts = peak_stream_bandwidth * 64 / link_bandwidth; average_bytes_per_tu = ts / 1000; - average_bytes_per_tu_frac = ts - average_bytes_per_tu * 1000; - average_bytes_per_tu_frac = average_bytes_per_tu_frac / 100; - + average_bytes_per_tu_frac = ts / 100 - average_bytes_per_tu * 10; if (pixel_mode == DPTX_MP_SINGLE_PIXEL) { - if (average_bytes_per_tu < 16) + if (average_bytes_per_tu < 6) init_threshold = 32; else if (hblank <= 80 && color_format != DRM_COLOR_FORMAT_YCBCR420) init_threshold = 12; @@ -2750,13 +2713,64 @@ static int dw_dp_video_enable(struct dw_dp *dp) init_threshold = 3; else init_threshold = 16; + } else { + u32 t1 = 0, t2 = 0, t3 = 0; + + switch (bpc) { + case 6: + t1 = (4 * 1000 / 9) * link->lanes; + break; + case 8: + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { + t1 = (1000 / 2) * link->lanes; + } else { + if (pixel_mode == DPTX_MP_DUAL_PIXEL) + t1 = (1000 / 3) * link->lanes; + else + t1 = (3000 / 16) * link->lanes; + } + break; + case 10: + if (color_format == DRM_COLOR_FORMAT_YCBCR422) + t1 = (2000 / 5) * link->lanes; + else + t1 = (4000 / 15) * link->lanes; + break; + case 12: + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { + if (pixel_mode == DPTX_MP_DUAL_PIXEL) + t1 = (1000 / 6) * link->lanes; + else + t1 = (1000 / 3) * link->lanes; + } else { + t1 = (2000 / 9) * link->lanes; + } + break; + case 16: + if (color_format != DRM_COLOR_FORMAT_YCBCR422 && + pixel_mode == DPTX_MP_DUAL_PIXEL) + t1 = (1000 / 6) * link->lanes; + else + t1 = (1000 / 4) * link->lanes; + break; + default: + return -EINVAL; + } + + if (color_format == DRM_COLOR_FORMAT_YCBCR420) + t2 = (link->rate / 4) * 1000 / (mode->clock / 2); + else + t2 = (link->rate / 4) * 1000 / mode->clock; + + if (average_bytes_per_tu_frac) + t3 = average_bytes_per_tu + 1; + else + t3 = average_bytes_per_tu; + init_threshold = t1 * t2 * t3 / (1000 * 1000); + if (init_threshold <= 16 || average_bytes_per_tu < 10) + init_threshold = 40; } - if (g_init_threshold) { - init_threshold = g_init_threshold; - } - - average_bytes_per_tu_frac &= 0xf; regmap_write(dp->regmap, DPTX_VIDEO_CONFIG5, FIELD_PREP(INIT_THRESHOLD_HI, init_threshold >> 6) | FIELD_PREP(AVERAGE_BYTES_PER_TU_FRAC, average_bytes_per_tu_frac) | @@ -2766,6 +2780,7 @@ static int dw_dp_video_enable(struct dw_dp *dp) /* Configure DPTX_VIDEO_HBLANK_INTERVAL register */ hblank_interval = hblank * (link->rate / 4) / mode->clock; regmap_write(dp->regmap, DPTX_VIDEO_HBLANK_INTERVAL, + FIELD_PREP(HBLANK_INTERVAL_EN, 1) | FIELD_PREP(HBLANK_INTERVAL, hblank_interval)); /* Video stream enable */ @@ -2807,6 +2822,63 @@ static irqreturn_t dw_dp_hpd_irq_handler(int irq, void *arg) return IRQ_HANDLED; } +/* + ┌────────────────────┬───────────┬─────────┬────────────────┐ + │ Scenario │ HPD_STATE │ IRQ_HPD │ DP Host Action │ + ├────────────────────┼───────────┼─────────┼────────────────┤ + │ Display insertion │ 0→1 │ 0 │ Long HPD │ + ├────────────────────┼───────────┼─────────┼────────────────┤ + │ Display removal │ 1→0 │ 0 │ Long HPD │ + ├────────────────────┼───────────┼─────────┼────────────────┤ + │ Link status change │ 1 │ 1 │ Short HPD │ + ├────────────────────┼───────────┼─────────┼────────────────┤ + │ EDID update │ 1 │ 1 │ Short HPD │ + ├────────────────────┼───────────┼─────────┼────────────────┤ + │ Resolution switch │ 1 │ 1 │ Short HPD │ + └────────────────────┴───────────┴─────────┴────────────────┘ +*/ +static int dw_dp_phy_extcon_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dw_dp *dp = container_of(nb, struct dw_dp, phy_extcon_nb); + bool hpd_state = !!event; + bool current_hpd; + + dev_dbg(dp->dev, "PHY extcon HPD notification: %s (event=%lu)\n", + hpd_state ? "connected" : "disconnected", event); + + mutex_lock(&dp->irq_lock); + + /* Get current HPD status */ + current_hpd = dp->hotplug.status; + + /* + * Distinguish between long HPD and short HPD: + * - If state changes → long HPD (plug/unplug) + * - If state unchanged and state=true → short HPD (IRQ pulse) + */ + if (current_hpd == hpd_state && hpd_state) { + /* State unchanged → short HPD */ + dp->hotplug.long_hpd = false; + dev_dbg(dp->dev, "Short HPD detected (IRQ pulse)\n"); + if (!current_hpd) + return NOTIFY_OK; + } else { + /* State changed → long HPD */ + dp->hotplug.long_hpd = true; + dp->hotplug.status = hpd_state; + dev_dbg(dp->dev, "Long HPD detected: %s\n", + hpd_state ? "plug" : "unplug"); + } + + mutex_unlock(&dp->irq_lock); + + /* Schedule HPD work queue */ + schedule_work(&dp->hpd_work); + + return NOTIFY_OK; +} + static void dw_dp_hpd_init(struct dw_dp *dp) { dp->hotplug.status = dw_dp_detect(dp); @@ -2841,13 +2913,6 @@ static void dw_dp_init(struct dw_dp *dp) regmap_update_bits(dp->regmap, DPTX_CCTL, DEFAULT_FAST_LINK_TRAIN_EN, FIELD_PREP(DEFAULT_FAST_LINK_TRAIN_EN, 0)); -#if 0 - regmap_update_bits(dp->regmap, DPTX_GENERAL_INTERRUPT_ENABLE, - VIDEO_FIFO_OVERFLOW_STREAM0, FIELD_PREP(VIDEO_FIFO_OVERFLOW_STREAM0, 1)); - - regmap_update_bits(dp->regmap, DPTX_GENERAL_INTERRUPT_ENABLE, - VIDEO_FIFO_UNDERFLOW_STREAM0, FIELD_PREP(VIDEO_FIFO_UNDERFLOW_STREAM0, 1)); -#endif dw_dp_hpd_init(dp); dw_dp_aux_init(dp); } @@ -2991,7 +3056,6 @@ static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux, case DP_AUX_I2C_READ: break; default: { - dev_err(dp->dev, "%s, %d, aux error\n", __func__, __LINE__); return -EINVAL; } } @@ -3114,13 +3178,9 @@ static void _dw_dp_loader_protect(struct dw_dp *dp, bool on) link->rate = 162000; break; } -#if !NO_PHY phy_power_on(dp->phy); -#endif } else { -#if !NO_PHY phy_power_off(dp->phy); -#endif } } @@ -3350,9 +3410,7 @@ static void dw_dp_link_disable(struct dw_dp *dp) dw_dp_phy_xmit_enable(dp, 0); -#if !NO_PHY phy_power_off(dp->phy); -#endif link->train.clock_recovered = false; link->train.channel_equalized = false; @@ -3362,21 +3420,19 @@ static int dw_dp_link_enable(struct dw_dp *dp) { int ret; -#if !NO_PHY ret = phy_power_on(dp->phy); if (ret) return ret; -#endif ret = dw_dp_link_power_up(dp); if (ret < 0) { - dev_err(dp->dev, "dw_dp_link_power_up failed: %d\n", ret); + dev_info(dp->dev, "dw_dp_link_power_up failed: %d\n", ret); return ret; } ret = dw_dp_link_train(dp); if (ret < 0) { - dev_err(dp->dev, "link training failed: %d\n", ret); + dev_info(dp->dev, "link training failed: %d\n", ret); return ret; } @@ -3409,7 +3465,7 @@ static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge, ret = dw_dp_link_enable(dp); if (ret < 0) { - dev_err(dp->dev, "failed to enable link: %d\n", ret); + dev_info(dp->dev, "failed to enable link: %d\n", ret); return; } @@ -3473,11 +3529,9 @@ static bool dw_dp_detect_dpcd(struct dw_dp *dp) u8 value; int ret; -#if !NO_PHY ret = phy_power_on(dp->phy); if (ret) goto fail_power_on; -#endif ret = drm_dp_dpcd_readb(&dp->aux, DP_DPCD_REV, &value); if (ret < 0) { goto fail_probe; @@ -3489,17 +3543,13 @@ static bool dw_dp_detect_dpcd(struct dw_dp *dp) goto fail_probe; } -#if !NO_PHY phy_power_off(dp->phy); -#endif return true; fail_probe: -#if !NO_PHY phy_power_off(dp->phy); fail_power_on: -#endif return false; } @@ -3540,17 +3590,13 @@ static struct edid *dw_dp_bridge_get_edid(struct drm_bridge *bridge, { struct dw_dp *dp = bridge_to_dp(bridge); struct edid *edid; -#if !NO_PHY int ret; ret = phy_power_on(dp->phy); if (ret) return NULL; -#endif edid = drm_get_edid(connector, &dp->aux.ddc); -#if !NO_PHY phy_power_off(dp->phy); -#endif return edid; } @@ -3972,18 +4018,6 @@ static irqreturn_t dw_dp_irq_handler(int irq, void *data) if (!value) return IRQ_NONE; -#if 0 - if (value & VIDEO_FIFO_OVERFLOW_STREAM0) { - regmap_write(dp->regmap, DPTX_GENERAL_INTERRUPT, - VIDEO_FIFO_OVERFLOW_STREAM0); - } - - if (value & VIDEO_FIFO_UNDERFLOW_STREAM0) { - regmap_write(dp->regmap, DPTX_GENERAL_INTERRUPT, - VIDEO_FIFO_UNDERFLOW_STREAM0); - } -#endif - if (value & HPD_EVENT) dw_dp_handle_hpd_event(dp); @@ -3996,8 +4030,6 @@ static irqreturn_t dw_dp_irq_handler(int irq, void *data) if (value & HDCP_EVENT) dw_dp_handle_hdcp_event(dp); - - return IRQ_HANDLED; } @@ -4355,87 +4387,13 @@ static int dw_dp_parse_dt(struct dw_dp *dp) return 0; } -static ssize_t dpinit_store(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - char kbuf[64]; - char *p, *token; - unsigned long val; - int ret; - struct dw_dp *dp = dev_get_drvdata(dev); - struct dw_dp_link *link = &dp->link; - - - /* 简单的长度检查,避免溢出 */ - if (count == 0) - return -EINVAL; - if (count >= sizeof(kbuf)) - return -EINVAL; - - /* buf 是 sysfs 传入的 kernel 空间缓存(已可读),复制并 null 终止以便分词 */ - memcpy(kbuf, buf, count); - kbuf[count] = '\0'; - - p = kbuf; - - /* 以空白字符或逗号分隔(支持 "100 4"、"100,4"、"0x64 0x4" 等格式) */ - token = strsep(&p, " \t\n,"); - if (!token || *token == '\0') { - dev_err(dev, "%s: missing threshold parameter\n", __func__); - return -EINVAL; - } - - ret = kstrtoul(token, 0, &val); /* base=0 支持 0x 前缀 */ - if (ret) { - dev_err(dev, "%s: invalid threshold '%s'\n", __func__, token); - return ret; - } - g_init_threshold = (unsigned int)val; - - /* 第二个参数可选:lanes */ - token = strsep(&p, " \t\n,"); - if (token && *token != '\0') { - ret = kstrtoul(token, 0, &val); - if (ret) { - dev_err(dev, "%s: invalid lanes '%s'\n", __func__, token); - return ret; - } - g_freqs = (unsigned int)val; - link->rate = g_freqs; - } else { - /* 如果未提供 lanes,则保持原值(或你可以改为返回错误以强制要求两个参数) */ - dev_info(dev, "%s: freq not provided, keep %u\n", __func__, link->rate); - } - - - dev_err(dev, "%s, %d, g_init_threshold = %lu, g_freq = %u\n", - __func__, __LINE__, g_init_threshold, link->rate); - - return count; -} - -static DEVICE_ATTR_WO(dpinit); - -int dw_dp_create_capabilities_sysfs(struct platform_device *pdev) -{ - device_create_file(&pdev->dev, &dev_attr_dpinit); - return 0; -} - -int dw_dp_remove_capabilities_sysfs(struct platform_device *pdev) -{ - device_remove_file(&pdev->dev, &dev_attr_dpinit); - return 0; -} - static int dw_dp_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct dw_dp *dp; void __iomem *base; int id, ret; + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); if (!dp) return -ENOMEM; @@ -4451,7 +4409,7 @@ static int dw_dp_probe(struct platform_device *pdev) ret = dw_dp_parse_dt(dp); if (ret) return dev_err_probe(dev, ret, "failed to parse DT\n"); - dp->force_hpd = 1; + mutex_init(&dp->irq_lock); INIT_WORK(&dp->hpd_work, dw_dp_hpd_work); init_completion(&dp->complete); @@ -4465,12 +4423,10 @@ static int dw_dp_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(dp->regmap), "failed to create regmap\n"); -#if !NO_PHY dp->phy = devm_of_phy_get(dev, dev->of_node, NULL); if (IS_ERR(dp->phy)) return dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n"); -#endif dp->i2s_clk = devm_clk_get(dev, "i2s"); if (IS_ERR(dp->i2s_clk)) return dev_err_probe(dev, PTR_ERR(dp->i2s_clk), @@ -4554,6 +4510,28 @@ static int dw_dp_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "failed to register extcon device\n"); + /* Try to get PHY's extcon for Type-C HPD notification */ + dp->phy_extcon = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(dp->phy_extcon)) { + if (PTR_ERR(dp->phy_extcon) == -EPROBE_DEFER) { + dev_err(dev, "PHY extcon not ready, deferring probe\n"); + return -EPROBE_DEFER; + } + /* PHY extcon not available, will use GPIO or internal HPD */ + dev_info(dev, "No PHY extcon found, using GPIO/internal HPD detection\n"); + dp->phy_extcon = NULL; + } else { + /* Register notifier to listen for PHY HPD events */ + dp->phy_extcon_nb.notifier_call = dw_dp_phy_extcon_notifier; + ret = devm_extcon_register_notifier(dev, dp->phy_extcon, + EXTCON_DISP_DP, + &dp->phy_extcon_nb); + if (ret) { + dev_err(dev, "Failed to register PHY extcon notifier: ret = %d \n", ret); + return ret; + } + } + //ret = dw_dp_register_audio_driver(dp); //if (ret) // return ret; @@ -4592,7 +4570,6 @@ static int dw_dp_probe(struct platform_device *pdev) secondary->split_mode = true; } - dw_dp_create_capabilities_sysfs(pdev); //dw_dp_hdcp_init(dp); return component_add(dev, &dw_dp_component_ops); @@ -4604,7 +4581,6 @@ static int dw_dp_remove(struct platform_device *pdev) component_del(dp->dev, &dw_dp_component_ops); cancel_work_sync(&dp->hpd_work); - dw_dp_remove_capabilities_sysfs(pdev); return 0; } diff --git a/drivers/phy/zhihe/phy-zhihe-snps-c10phy.c b/drivers/phy/zhihe/phy-zhihe-snps-c10phy.c index 9c4e7facc..e57245140 100644 --- a/drivers/phy/zhihe/phy-zhihe-snps-c10phy.c +++ b/drivers/phy/zhihe/phy-zhihe-snps-c10phy.c @@ -26,22 +26,26 @@ #include #include +/* PHY Lane Mux Selection */ +#define PHY_LANE_MUX_USB 0 +#define PHY_LANE_MUX_DP 1 + /* Base DWC3 Control Register */ #define DWC3_LCSR_TX_DEEMPH_2 0xd068 /* * DPTX_SYS Register Offsets - From soc sys databook */ -#define DPTX_CTRL 0x00 -#define DPTX_LSFR_CTRL 0x10 -#define DPTX_LSFR_SEED 0x14 -#define DPTX_MTN_LINK 0x20 -#define DPTX_AUX_CTRL 0x40 -#define DPTX_DBG0 0x100 -#define DPTX_DBG1 0x104 +#define DPTX_CTRL 0x00 +#define DPTX_LSFR_CTRL 0x10 +#define DPTX_LSFR_SEED 0x14 +#define DPTX_MTN_LINK 0x20 +#define DPTX_AUX_CTRL 0x40 +#define DPTX_DBG0 0x100 +#define DPTX_DBG1 0x104 /* DPTX_AUX_CTRL bits*/ -#define AUX_DP_DN_SWAP BIT(8) +#define AUX_DP_DN_SWAP BIT(8) /* * TCA Register Offsets - From Databook Section 3.2 (Page 36) @@ -53,7 +57,10 @@ #define TCA_CLK_RST_SUSPEND_CLK_EN BIT(0) #define TCA_INTR_EN 0x04 +#define TCA_INTR_EN_MASK GENMASK(1, 0) + #define TCA_INTR_STS 0x08 +#define TCA_INTR_STS_MASK GENMASK(15, 0) #define TCA_GCFG 0x10 #define TCA_GCFG_ROLE_HSTDEV BIT(4) @@ -68,13 +75,18 @@ #define TCA_TCPC_MUX_CONTRL GENMASK(1, 0) #define TCA_TCPC_MUX_CONTRL_NO_CONN 0 #define TCA_TCPC_MUX_CONTRL_USB_CONN 1 +#define TCA_TCPC_MUX_CONTRL_DP_CONN 2 +#define TCA_TCPC_MUX_CONTRL_USBDP_CONN 3 #define TCA_SYSMODE_CFG 0x18 #define TCA_SYSMODE_TCPC_DISABLE BIT(3) #define TCA_SYSMODE_TCPC_FLIP BIT(2) +#define TCA_SYSMODE_TCPC_CONN_MODE GENMASK(1, 0) +#define TCA_SYSMODE_TCPC_CONN_CE 0x02 +#define TCA_SYSMODE_TCPC_CONN_DF 0x03 #define TCA_CTRLSYNCMODE_CFG0 0x20 -#define TCA_CTRLSYNCMODE_CFG1 0x20 +#define TCA_CTRLSYNCMODE_CFG1 0x20 #define TCA_PSTATE 0x30 #define TCA_PSTATE_CM_STS BIT(4) @@ -88,6 +100,7 @@ #define TCA_GEN_TYPEC_FLIP_INVERT BIT(4) #define TCA_GEN_PHY_TYPEC_DISABLE BIT(3) #define TCA_GEN_PHY_TYPEC_FLIP BIT(2) +#define TCA_GEN_PHY_TYPEC_CONN GENMASK(1, 0) #define TCA_VBUS_CTRL 0x40 #define TCA_VBUS_STATUS 0x44 @@ -100,43 +113,99 @@ #define PHY_MODE_DP_CUSTOM BIT(1) #define PHY_MODE_USB3_DP (BIT(0) | BIT(1)) +enum { + UDPHY_MODE_NONE = 0, + UDPHY_MODE_USB = BIT(0), + UDPHY_MODE_DP = BIT(1), + UDPHY_MODE_DP_USB = BIT(1) | BIT(0), +}; + +static char *udphy_mode_str[] = { + "UDPHY_MODE_NONE", + "UDPHY_MODE_USB", + "UDPHY_MODE_DP", + "UDPHY_MODE_DP_USB", +}; + struct zhihe_c10phy_priv { struct device *dev; - struct phy *phy; + struct phy *phy; + struct phy *dp_phy; void __iomem *phy_ctrl_base; void __iomem *tca_base; void __iomem *sysreg_base; void __iomem *dptxsys_base; struct reset_control *c10phy_rst; - enum typec_orientation orientation; /* Type-C orientation */ + enum typec_orientation orientation; /* Type-C orientation */ + u32 lane_mux_sel[4]; + u32 dp_lane_sel[4]; /* GPIO for AUX control */ struct gpio_desc *aux_p_gpio; /* AUX_P pull-down control */ struct gpio_desc *aux_n_gpio; /* AUX_N pull-up control */ - enum usb_role usb_role; /* Current USB role: host or device */ + enum usb_role usb_role; /* Current USB role: host or device */ + struct typec_mux_dev *mux; + u8 mode; + bool mode_change; struct typec_switch_dev *sw; struct mutex lock; + struct extcon_dev *extcon; /* Extcon device for HPD notification */ +}; + +/* Supported extcon cables - must be static to persist after probe returns */ +static const unsigned int c10phy_extcon_cables[] = { + EXTCON_DISP_DP, + EXTCON_NONE, }; static void tca_blk_orientation_set(struct zhihe_c10phy_priv *priv, enum typec_orientation orientation); +static int udphy_set_mux(struct zhihe_c10phy_priv *priv); + static int tca_blk_typec_switch_set(struct typec_switch_dev *sw, enum typec_orientation orientation) { struct zhihe_c10phy_priv *priv = typec_switch_get_drvdata(sw); - if (priv->orientation == orientation) - return 0; - tca_blk_orientation_set(priv, orientation); return 0; } +static void configure_aux_gpio(struct zhihe_c10phy_priv *priv, enum typec_orientation orientation) +{ + if (orientation == TYPEC_ORIENTATION_REVERSE) { + /* Flipped orientation: swap the polarity */ + if (priv->aux_p_gpio) { + gpiod_set_value_cansleep(priv->aux_p_gpio, 1); + dev_dbg(priv->dev, "AUX_P GPIO set to high (pull-up) - flipped\n"); + } + + if (priv->aux_n_gpio) { + gpiod_set_value_cansleep(priv->aux_n_gpio, 0); + dev_dbg(priv->dev, "AUX_N GPIO set to low (pull-down) - flipped\n"); + } + } else { + /* Normal orientation */ + if (priv->aux_p_gpio) { + gpiod_set_value_cansleep(priv->aux_p_gpio, 0); + dev_dbg(priv->dev, "AUX_P GPIO set to low (pull-down) - normal\n"); + } + + if (priv->aux_n_gpio) { + gpiod_set_value_cansleep(priv->aux_n_gpio, 1); + dev_dbg(priv->dev, "AUX_N GPIO set to high (pull-up) - normal\n"); + } + } +} + static void tca_blk_orientation_set(struct zhihe_c10phy_priv *priv, enum typec_orientation orientation) { - u32 val; + u32 val, dptx_aux_ctrl; + + if (priv->orientation == orientation) + return; mutex_lock(&priv->lock); @@ -150,10 +219,11 @@ static void tca_blk_orientation_set(struct zhihe_c10phy_priv *priv, val = TCA_TCPC_VALID | TCA_TCPC_LOW_POWER_EN; writel(val, priv->tca_base + TCA_TCPC); - goto out; } + dptx_aux_ctrl = readl(priv->dptxsys_base + DPTX_AUX_CTRL); + /* use System Configuration Mode for TCA mux control. */ val = FIELD_PREP(TCA_GCFG_OP_MODE, TCA_GCFG_OP_MODE_SYSMODE); writel(val, priv->tca_base + TCA_GCFG); @@ -163,12 +233,18 @@ static void tca_blk_orientation_set(struct zhihe_c10phy_priv *priv, val |= TCA_SYSMODE_TCPC_DISABLE; writel(val, priv->tca_base + TCA_SYSMODE_CFG); - if (orientation == TYPEC_ORIENTATION_REVERSE) + if (orientation == TYPEC_ORIENTATION_REVERSE) { val |= TCA_SYSMODE_TCPC_FLIP; - else if (orientation == TYPEC_ORIENTATION_NORMAL) + dptx_aux_ctrl |= AUX_DP_DN_SWAP; + } else if (orientation == TYPEC_ORIENTATION_NORMAL) { val &= ~TCA_SYSMODE_TCPC_FLIP; + dptx_aux_ctrl &= ~AUX_DP_DN_SWAP; + } writel(val, priv->tca_base + TCA_SYSMODE_CFG); + writel(dptx_aux_ctrl, priv->dptxsys_base + DPTX_AUX_CTRL); + + configure_aux_gpio(priv, orientation); /* Enable TCA module */ val &= ~TCA_SYSMODE_TCPC_DISABLE; @@ -219,20 +295,399 @@ static int zhihe_setup_typec_switch(struct zhihe_c10phy_priv *priv) static void zhihe_switch_unregister(void *data) { struct zhihe_c10phy_priv *priv = data; + typec_switch_unregister(priv->sw); } static int c10phy_init(struct phy *phy) { + int ret = 0; struct zhihe_c10phy_priv *priv = phy_get_drvdata(phy); reset_control_deassert(priv->c10phy_rst); tca_blk_init(priv); + ret = udphy_set_mux(priv); reset_control_assert(priv->c10phy_rst); + return ret; +} + +/* + ┌────────────────────┬───────────┬─────────┬──────────────────────────────┬────────────────┐ + │ Scenario │ HPD_STATE │ IRQ_HPD │ PHY Action │ DP Host Action │ + ├────────────────────┼───────────┼─────────┼──────────────────────────────┼────────────────┤ + │ Display insertion │ 0→1 │ 0 │ extcon_set_state_sync(true) │ Long HPD │ + ├────────────────────┼───────────┼─────────┼──────────────────────────────┼────────────────┤ + │ Display removal │ 1→0 │ 0 │ extcon_set_state_sync(false) │ Long HPD │ + ├────────────────────┼───────────┼─────────┼──────────────────────────────┼────────────────┤ + │ Link status change │ 1 │ 1 │ extcon_sync() │ Short HPD │ + ├────────────────────┼───────────┼─────────┼──────────────────────────────┼────────────────┤ + │ EDID update │ 1 │ 1 │ extcon_sync() │ Short HPD │ + ├────────────────────┼───────────┼─────────┼──────────────────────────────┼────────────────┤ + │ Resolution switch │ 1 │ 1 │ extcon_sync() │ Short HPD │ + └────────────────────┴───────────┴─────────┴──────────────────────────────┴────────────────┘ +*/ +static int usbdp_typec_mux_set(struct typec_mux_dev *mux, + struct typec_mux_state *state) +{ + int ret; + struct zhihe_c10phy_priv *priv = typec_mux_get_drvdata(mux); + u8 mode; + struct device *dev = priv->dev; + + mutex_lock(&priv->lock); + + switch (state->mode) { + case TYPEC_DP_STATE_C: + fallthrough; + case TYPEC_DP_STATE_E: + mode = UDPHY_MODE_DP; + break; + case TYPEC_DP_STATE_D: + fallthrough; + default: + mode = UDPHY_MODE_DP_USB; + break; + } + + dev_info(dev, "change mux_mode %s to %s\n", udphy_mode_str[priv->mode], udphy_mode_str[mode]); + + if (mode != priv->mode) { + priv->mode = mode; + ret = udphy_set_mux(priv); + } + + /* Handling DP Alt Mode HPD events */ + if (state->alt && state->alt->svid == USB_TYPEC_DP_SID) { + struct typec_displayport_data *data = state->data; + bool last_hpd_state, current_hpd_state; + + if (!data) { + /* No data means DP disconnected */ + dev_info(dev, "DP disconnected (no data)\n"); + last_hpd_state = extcon_get_state(priv->extcon, EXTCON_DISP_DP); + if (!last_hpd_state) + extcon_sync(priv->extcon, EXTCON_DISP_DP); + else + extcon_set_state_sync(priv->extcon, EXTCON_DISP_DP, false); + goto out; + } + + dev_info(dev, "DP Status: 0x%x (HPD_STATE=%d, IRQ_HPD=%d)\n", + data->status, + !!(data->status & DP_STATUS_HPD_STATE), + !!(data->status & DP_STATUS_IRQ_HPD)); + + /* Get the last HPD status */ + last_hpd_state = extcon_get_state(priv->extcon, EXTCON_DISP_DP); + + /* Get the current HPD status*/ + current_hpd_state = !!(data->status & DP_STATUS_HPD_STATE); + + /* Prioritize processing IRQ_HPD (short pulse) */ + if (data->status & DP_STATUS_IRQ_HPD) { + if (current_hpd_state == last_hpd_state && current_hpd_state) { + /* + * A normal short HPD signal: HPD remains high + IRQ pulse. + * This is used to notify of link status changes, EDID updates, etc. + */ + dev_info(dev, "DP IRQ_HPD: Short pulse (Link status/EDID change)\n"); + extcon_sync(priv->extcon, EXTCON_DISP_DP); + } else { + /* + * Abnormal condition: HPD low level + IRQ pulse + * It could be a Partner firmware bug or a transitional state during the unplugging process. + */ + dev_warn(dev, "Abnormal: IRQ_HPD with current_hpd_state %d, last_hpd_state %d\n", current_hpd_state, last_hpd_state); + /* + * In this situation, we cannot determine whether it is a genuine disconnection or just jitter, + * because in a Type-C scenario, we cannot reread the hardware state; + * we can only wait for the Partner to send the next Attention VDM. + */ + } + goto out; + } + + /* Handling HPD status changes (long HPD: insertion/removal) */ + if (current_hpd_state != last_hpd_state) { + dev_info(dev, "DP HPD state change: %s -> %s\n", + last_hpd_state ? "connected" : "disconnected", + current_hpd_state ? "connected" : "disconnected"); + + /* Notify DP Host of HPD status change*/ + extcon_set_state_sync(priv->extcon, EXTCON_DISP_DP, current_hpd_state); + } else { + /* + * The state has not changed and there is no IRQ_HPD. + * This could be a duplicate Attention VDM, ignore it. + */ + dev_dbg(dev, "DP HPD state unchanged (%s), ignoring\n", current_hpd_state ? "connected" : "disconnected"); + } + } + +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int udphy_setup_typec_mux(struct zhihe_c10phy_priv *priv) +{ + struct typec_mux_desc mux_desc = {}; + + mux_desc.drvdata = priv; + mux_desc.fwnode = dev_fwnode(priv->dev); + mux_desc.set = usbdp_typec_mux_set; + + priv->mux = typec_mux_register(priv->dev, &mux_desc); + if (IS_ERR(priv->mux)) { + dev_err(priv->dev, "Error register typec mux: %ld\n", + PTR_ERR(priv->mux)); + return PTR_ERR(priv->mux); + } + return 0; } +static void udphy_typec_mux_unregister(void *data) +{ + struct zhihe_c10phy_priv *priv = data; + + typec_mux_unregister(priv->mux); +} + +static int zhihe_dp_phy_verify_link_rate(unsigned int link_rate) +{ + switch (link_rate) { + case 1620: + case 2700: + case 5400: + case 8100: + break; + default: + return -EINVAL; + } + + return 0; +} + +static u32 udphy_dp_get_max_link_rate(struct zhihe_c10phy_priv *priv, struct device_node *np) +{ + u32 max_link_rate; + int ret; + + ret = of_property_read_u32(np, "max-link-rate", &max_link_rate); + if (ret) + return 8100; + + ret = zhihe_dp_phy_verify_link_rate(max_link_rate); + if (ret) { + dev_warn(priv->dev, "invalid max-link-rate value:%d\n", max_link_rate); + max_link_rate = 8100; + } + + return max_link_rate; +} + +static int zhihe_dp_phy_verify_config(struct zhihe_c10phy_priv *priv, + struct phy_configure_opts_dp *dp) +{ + int ret = 0; + + /* If changing link rate was required, verify it's supported. */ + ret = zhihe_dp_phy_verify_link_rate(dp->link_rate); + if (ret) + return ret; + + /* Verify lane count. */ + switch (dp->lanes) { + case 1: + case 2: + case 4: + /* valid lane count. */ + break; + default: + return -EINVAL; + } + + return 0; +} + +static int zhihe_dp_phy_configure(struct phy *phy, + union phy_configure_opts *opts) +{ + struct zhihe_c10phy_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = zhihe_dp_phy_verify_config(priv, &opts->dp); + if (ret) { + return ret; + } + + return 0; +} + +static int udphy_dplane_get(struct zhihe_c10phy_priv *priv) +{ + int dp_lanes; + + switch (priv->mode) { + case UDPHY_MODE_DP: + dp_lanes = 4; + break; + case UDPHY_MODE_DP_USB: + dp_lanes = 2; + break; + case UDPHY_MODE_USB: + fallthrough; + default: + dp_lanes = 0; + break; + } + + return dp_lanes; +} + +static int udphy_sys_cfg(struct zhihe_c10phy_priv *priv) +{ + u32 val, gen_status; + + /* use System Configuration Mode for TCA mux control. */ + val = FIELD_PREP(TCA_GCFG_OP_MODE, TCA_GCFG_OP_MODE_SYSMODE); + writel(val, priv->tca_base + TCA_GCFG); + + /* read xbar cfg and write xbar val to sysmode reg*/ + gen_status = readl(priv->tca_base + TCA_GEN_STATUS); + val = readl(priv->tca_base + TCA_SYSMODE_CFG); + val &= ~(TCA_SYSMODE_TCPC_DISABLE | TCA_SYSMODE_TCPC_FLIP | TCA_SYSMODE_TCPC_CONN_MODE); + gen_status &= TCA_SYSMODE_TCPC_DISABLE | TCA_SYSMODE_TCPC_FLIP | TCA_SYSMODE_TCPC_CONN_MODE; + val |= gen_status; + writel(val, priv->tca_base + TCA_SYSMODE_CFG); + + /* set typec disable */ + val = readl(priv->tca_base + TCA_SYSMODE_CFG); + val |= TCA_SYSMODE_TCPC_DISABLE; + writel(val, priv->tca_base + TCA_SYSMODE_CFG); + usleep_range(10, 20); + + val = readl(priv->tca_base + TCA_SYSMODE_CFG); + if (priv->orientation == TYPEC_ORIENTATION_REVERSE) + val |= TCA_SYSMODE_TCPC_FLIP; + else + val &= ~TCA_SYSMODE_TCPC_FLIP; + + /* set conn mode */ + val &= ~TCA_SYSMODE_TCPC_CONN_MODE; + switch (priv->mode) { + case UDPHY_MODE_DP: + val |= TCA_SYSMODE_TCPC_CONN_CE; + break; + case UDPHY_MODE_DP_USB: + case UDPHY_MODE_USB: + default: + val |= TCA_SYSMODE_TCPC_CONN_DF; + break; + } + + writel(val, priv->tca_base + TCA_SYSMODE_CFG); + usleep_range(10, 20); + + /* set typec enable */ + val &= ~TCA_SYSMODE_TCPC_DISABLE; + + writel(val, priv->tca_base + TCA_SYSMODE_CFG); + + return 0; +} + +static int udphy_sync_cfg(struct zhihe_c10phy_priv *priv) +{ + u32 val; + + /* Use sync configuration mode for TCA mux control. */ + val = FIELD_PREP(TCA_GCFG_OP_MODE, TCA_GCFG_OP_MODE_SYNCMODE); + writel(val, priv->tca_base + TCA_GCFG); + + /*enable tca ack and timeout irq*/ + + writel(TCA_INTR_STS_MASK, priv->tca_base + TCA_INTR_STS); + writel(TCA_INTR_EN_MASK, priv->tca_base + TCA_INTR_EN); + + /*mode config */ + val = readl(priv->tca_base + TCA_TCPC); + /*default set to usb mode*/ + val &= ~TCA_TCPC_MUX_CONTRL; + val |= TCA_TCPC_MUX_CONTRL_USB_CONN; + val |= TCA_TCPC_VALID; + val &= ~TCA_TCPC_LOW_POWER_EN; + if (priv->orientation == TYPEC_ORIENTATION_REVERSE) + val |= TCA_TCPC_ORIENTATION_NORMAL; + else + val &= ~TCA_TCPC_ORIENTATION_NORMAL; + writel(val, priv->tca_base + TCA_TCPC); + usleep_range(10, 20); + + val = readl(priv->tca_base + TCA_TCPC); + val &= ~TCA_TCPC_LOW_POWER_EN; + val &= ~TCA_TCPC_MUX_CONTRL; + switch (priv->mode) { + case UDPHY_MODE_USB: + return 0; + case UDPHY_MODE_DP: + val |= TCA_TCPC_MUX_CONTRL_DP_CONN; + break; + case UDPHY_MODE_DP_USB: + default: + val |= TCA_TCPC_MUX_CONTRL_USBDP_CONN; + break; + } + + val |= TCA_TCPC_VALID; + writel(val, priv->tca_base + TCA_TCPC); + usleep_range(10, 20); + + return 0; +} + +static int udphy_set_mux(struct zhihe_c10phy_priv *priv) +{ + udphy_sys_cfg(priv); + usleep_range(10, 20); + udphy_sync_cfg(priv); + + return 0; +} + +static int zhihe_dp_phy_power_on(struct phy *phy) +{ + struct zhihe_c10phy_priv *priv = phy_get_drvdata(phy); + int ret = 0, dp_lanes; + + mutex_lock(&priv->lock); + + dp_lanes = udphy_dplane_get(priv); + phy_set_bus_width(phy, dp_lanes); + mutex_unlock(&priv->lock); + /* + * If data send by aux channel too fast after phy power on, + * the aux may be not ready which will cause aux error. Adding + * delay to avoid this issue. + */ + usleep_range(10000, 11000); + return ret; +} + +static int zhihe_dp_phy_power_off(struct phy *phy) +{ + return 0; +} + +static const struct phy_ops zhihe_dp_phy_ops = { + .power_on = zhihe_dp_phy_power_on, + .power_off = zhihe_dp_phy_power_off, + .configure = zhihe_dp_phy_configure, + .owner = THIS_MODULE, +}; + static int c10phy_exit(struct phy *phy) { struct zhihe_c10phy_priv *priv = phy_get_drvdata(phy); @@ -254,8 +709,7 @@ static const struct phy_ops c10phy_ops = { static ssize_t orientation_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct phy *phy = dev_get_drvdata(dev); - struct zhihe_c10phy_priv *priv = phy_get_drvdata(phy); + struct zhihe_c10phy_priv *priv = dev_get_drvdata(dev); const char *orientation_str; switch (priv->orientation) { @@ -280,8 +734,7 @@ static ssize_t orientation_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct phy *phy = dev_get_drvdata(dev); - struct zhihe_c10phy_priv *priv = phy_get_drvdata(phy); + struct zhihe_c10phy_priv *priv = dev_get_drvdata(dev); enum typec_orientation orientation; if (sysfs_streq(buf, "none")) @@ -309,21 +762,65 @@ static const struct attribute_group c10phy_attr_group = { .attrs = c10phy_attrs, }; -static int c10phy_probe(struct platform_device *pdev) +static int udphy_parse_lane_mux_data(struct zhihe_c10phy_priv *priv, struct device *dev) { - int ret = 0; + struct device_node *np = dev->of_node; + struct property *prop; + int ret, i, len, num_lanes; + + prop = of_find_property(np, "zhihe,dp-lane-mux", &len); + if (!prop) { + dev_dbg(dev, "failed to find dp lane mux, following dp alt mode\n"); + priv->mode = UDPHY_MODE_USB; + return 0; + } + + num_lanes = len / sizeof(u32); + + if (num_lanes != 2 && num_lanes != 4) { + dev_err(dev, "invalid number of lane mux\n"); + return -EINVAL; + } + + ret = of_property_read_u32_array(np, "zhihe,dp-lane-mux", priv->dp_lane_sel, num_lanes); + if (ret) { + dev_err(dev, "get dp lane mux failed\n"); + return -EINVAL; + } + + for (i = 0; i < num_lanes; i++) { + int j; + + if (priv->dp_lane_sel[i] > 3) { + dev_err(dev, "lane mux between 0 and 3, exceeding the range\n"); + return -EINVAL; + } + + priv->lane_mux_sel[priv->dp_lane_sel[i]] = PHY_LANE_MUX_DP; + + for (j = i + 1; j < num_lanes; j++) { + if (priv->dp_lane_sel[i] == priv->dp_lane_sel[j]) { + dev_err(dev, "set repeat lane mux value\n"); + return -EINVAL; + } + } + } + + priv->mode = UDPHY_MODE_DP; + if (num_lanes == 2) { + priv->mode |= UDPHY_MODE_USB; + if (priv->lane_mux_sel[0] == PHY_LANE_MUX_DP) + priv->orientation = TYPEC_ORIENTATION_REVERSE; + } + + return 0; +} + +static int c10phy_parse_dt(struct zhihe_c10phy_priv *priv, struct platform_device *pdev) +{ + int ret; struct device *dev = &pdev->dev; - struct zhihe_c10phy_priv *priv; - struct phy_provider *phy_provider; - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->dev = dev; - platform_set_drvdata(pdev, priv); - - priv->usb_role = USB_ROLE_HOST; /* Default to host mode, can be changed dynamically */ /* Get TCA register base */ priv->tca_base = devm_platform_ioremap_resource_byname(pdev, "tca"); if (IS_ERR(priv->tca_base)) @@ -370,12 +867,41 @@ static int c10phy_probe(struct platform_device *pdev) if (priv->aux_n_gpio) dev_info(dev, "AUX_N GPIO configured for pull-up control\n"); + ret = udphy_parse_lane_mux_data(priv, dev); + if (ret) + dev_err(dev, "Failed to get lane mux\n"); + + return ret; +} + +static int c10phy_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *child_np; + struct zhihe_c10phy_priv *priv; + struct phy_provider *phy_provider; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + platform_set_drvdata(pdev, priv); + + /* Default to host mode, can be changed dynamically */ + priv->usb_role = USB_ROLE_HOST; + + ret = c10phy_parse_dt(priv, pdev); + if (ret) + return ret; + /* Setup Type-C support if enabled */ if (device_property_present(dev, "orientation-switch")) { ret = zhihe_setup_typec_switch(priv); - if (ret) { + if (ret) return ret; - } ret = devm_add_action_or_reset(dev, zhihe_switch_unregister, priv); if (ret) @@ -386,14 +912,54 @@ static int c10phy_probe(struct platform_device *pdev) mutex_init(&priv->lock); - /* Create PHY */ - priv->phy = devm_phy_create(dev, NULL, &c10phy_ops); - if (IS_ERR(priv->phy)) - return dev_err_probe(dev, PTR_ERR(priv->phy), - "Couldn't create phy\n"); + /* Initialize extcon device for HPD notification to DP driver */ + priv->extcon = devm_extcon_dev_allocate(dev, c10phy_extcon_cables); - dev_set_drvdata(dev, priv->phy); - phy_set_drvdata(priv->phy, priv); + if (IS_ERR(priv->extcon)) { + dev_err(dev, "Failed to allocate extcon device: %ld\n", PTR_ERR(priv->extcon)); + return PTR_ERR(priv->extcon); + } + + ret = devm_extcon_dev_register(dev, priv->extcon); + if (ret) { + dev_err(dev, "Failed to register extcon device: %d\n", ret); + return ret; + } + + dev_info(dev, "Extcon device registered for HPD notification, %s\n", extcon_get_edev_name(priv->extcon)); + + if (device_property_present(dev, "svid")) { + ret = udphy_setup_typec_mux(priv); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, udphy_typec_mux_unregister, priv); + if (ret) + return ret; + } + + for_each_available_child_of_node(np, child_np) { + if (of_node_name_eq(child_np, "dp-port")) { + priv->dp_phy = devm_phy_create(dev, child_np, &zhihe_dp_phy_ops); + if (IS_ERR(priv->dp_phy)) { + dev_err(dev, "failed to create dp phy: %pOFn\n", child_np); + goto put_child; + } + + phy_set_bus_width(priv->dp_phy, udphy_dplane_get(priv)); + priv->dp_phy->attrs.max_link_rate = udphy_dp_get_max_link_rate(priv, child_np); + phy_set_drvdata(priv->dp_phy, priv); + } else if (of_node_name_eq(child_np, "u3-port")) { + priv->phy = devm_phy_create(dev, child_np, &c10phy_ops); + if (IS_ERR(priv->phy)) { + dev_err(dev, "failed to create usb phy: %pOFn\n", child_np); + goto put_child; + } + phy_set_drvdata(priv->phy, priv); + } else + continue; + } + dev_set_drvdata(dev, priv); /* Register PHY provider */ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); @@ -409,6 +975,10 @@ static int c10phy_probe(struct platform_device *pdev) } return 0; + +put_child: + of_node_put(child_np); + return ret; } static int c10phy_remove(struct platform_device *pdev)