Release develop 260205

This commit is contained in:
hongyi
2026-02-05 10:48:51 +08:00
parent 95d169caeb
commit 218862ffdd
6 changed files with 855 additions and 274 deletions

View File

@@ -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>;
};
};
};

View File

@@ -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>;
};

View File

@@ -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;

View File

@@ -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

View File

@@ -25,6 +25,7 @@
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/extcon-provider.h>
#include <linux/extcon.h>
#include <linux/iopoll.h>
#include <linux/irq.h>
#include <linux/of_device.h>
@@ -47,9 +48,6 @@
#include <sound/hdmi-codec.h>
#include <uapi/linux/videodev2.h>
#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 01 0 Long HPD
Display removal 10 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;
}

View File

@@ -26,22 +26,26 @@
#include <linux/sysfs.h>
#include <linux/kobject.h>
/* 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 01 0 extcon_set_state_sync(true) Long HPD
Display removal 10 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)