From 5efa70a0f113094999aabe374dfc7dcd5fad47ee Mon Sep 17 00:00:00 2001 From: Algea Cao Date: Mon, 13 Dec 2021 18:00:53 +0800 Subject: [PATCH] drm/rockchip: dw_hdmi: Support hdmi split mode Signed-off-by: Algea Cao Change-Id: I71c5785ecfb9480f9569d3c55dd634579a5176fb --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 122 +++++++- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 291 +++++++++++++------ include/drm/bridge/dw_hdmi.h | 8 + 3 files changed, 317 insertions(+), 104 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index eb9ce9e37ebf..dc88c1f655f9 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -655,7 +655,6 @@ static void dw_hdmi_i2c_init(struct dw_hdmi_qp *hdmi) { /* Software reset */ hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); - hdmi_writel(hdmi, 0x00, I2CM_CONTROL0); hdmi_writel(hdmi, 0x085c085c, I2CM_FM_SCL_CONFIG0); @@ -1380,7 +1379,7 @@ hdmi_get_tmdsclock(struct dw_hdmi_qp *hdmi, unsigned long mpixelclock) static int dw_hdmi_qp_setup(struct dw_hdmi_qp *hdmi, const struct drm_connector *connector, - const struct drm_display_mode *mode) + struct drm_display_mode *mode) { int ret; void *data = hdmi->plat_data->phy_data; @@ -1464,6 +1463,8 @@ static int dw_hdmi_qp_setup(struct dw_hdmi_qp *hdmi, hdmi->hdmi_data.video_mode.mdataenablepolarity = true; vmode->previous_pixelclock = vmode->mpixelclock; + if (hdmi->plat_data->split_mode) + mode->crtc_clock /= 2; vmode->mpixelclock = mode->crtc_clock * 1000; if ((mode->flags & DRM_MODE_FLAG_3D_MASK) == DRM_MODE_FLAG_3D_FRAME_PACKING) vmode->mpixelclock *= 2; @@ -1528,12 +1529,30 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force) { struct dw_hdmi_qp *hdmi = container_of(connector, struct dw_hdmi_qp, connector); + struct dw_hdmi_qp *secondary = NULL; + enum drm_connector_status result, result_secondary; mutex_lock(&hdmi->mutex); hdmi->force = DRM_FORCE_UNSPECIFIED; mutex_unlock(&hdmi->mutex); - return hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + if (hdmi->plat_data->left) + secondary = hdmi->plat_data->left; + else if (hdmi->plat_data->right) + secondary = hdmi->plat_data->right; + + result = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + + if (secondary) { + result_secondary = secondary->phy.ops->read_hpd(secondary, secondary->phy.data); + if (result == connector_status_connected && + result_secondary == connector_status_connected) + result = connector_status_connected; + else + result = connector_status_disconnected; + } + + return result; } static int @@ -1594,11 +1613,43 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector) hdmi->plat_data->get_edid_dsc_info(data, edid); ret = drm_add_edid_modes(connector, edid); dw_hdmi_update_hdr_property(connector); + if (ret > 0 && hdmi->plat_data->split_mode) { + struct dw_hdmi_qp *secondary = NULL; + void *secondary_data; + + if (hdmi->plat_data->left) + secondary = hdmi->plat_data->left; + else if (hdmi->plat_data->right) + secondary = hdmi->plat_data->right; + + if (!secondary) + return -ENOMEM; + secondary_data = secondary->plat_data->phy_data; + + list_for_each_entry(mode, &connector->probed_modes, head) + hdmi->plat_data->convert_to_split_mode(mode); + + secondary->sink_is_hdmi = drm_detect_hdmi_monitor(edid); + secondary->sink_has_audio = drm_detect_monitor_audio(edid); + cec_notifier_set_phys_addr_from_edid(secondary->cec_notifier, edid); + if (secondary->plat_data->get_edid_dsc_info) + secondary->plat_data->get_edid_dsc_info(secondary_data, edid); + } kfree(edid); } else { hdmi->sink_is_hdmi = true; hdmi->sink_has_audio = true; + if (hdmi->plat_data->split_mode) { + if (hdmi->plat_data->left) { + hdmi->plat_data->left->sink_is_hdmi = true; + hdmi->plat_data->left->sink_has_audio = true; + } else if (hdmi->plat_data->right) { + hdmi->plat_data->right->sink_is_hdmi = true; + hdmi->plat_data->right->sink_has_audio = true; + } + } + for (i = 0; i < ARRAY_SIZE(dw_hdmi_default_modes); i++) { const struct drm_display_mode *ptr = &dw_hdmi_default_modes[i]; @@ -1614,6 +1665,12 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector) ret++; } } + if (ret > 0 && hdmi->plat_data->split_mode) { + struct drm_display_mode *mode; + + list_for_each_entry(mode, &connector->probed_modes, head) + hdmi->plat_data->convert_to_split_mode(mode); + } info->edid_hdmi_dc_modes = 0; info->hdmi.y420_dc_modes = 0; info->color_formats = 0; @@ -1774,6 +1831,10 @@ static int dw_hdmi_connector_atomic_check(struct drm_connector *connector, hdmi->plat_data->get_output_bus_format(data); mode = &crtc_state->mode; + if (hdmi->plat_data->split_mode) { + hdmi->plat_data->convert_to_origin_mode(mode); + mode->crtc_clock /= 2; + } memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode)); vmode->mpixelclock = mode->crtc_clock * 1000; vmode->previous_pixelclock = mode->clock; @@ -1850,6 +1911,9 @@ static int dw_hdmi_qp_bridge_attach(struct drm_bridge *bridge, struct cec_connector_info conn_info; struct cec_notifier *notifier; + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return 0; + connector->interlace_allowed = 1; connector->polled = DRM_CONNECTOR_POLL_HPD; @@ -1895,12 +1959,20 @@ dw_hdmi_qp_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_mode *mode) { int i; + struct dw_hdmi_qp *hdmi = bridge->driver_private; + const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; + struct drm_display_mode m; - if (mode->clock > HDMI20_MAX_TMDSCLK_KHZ) + drm_mode_copy(&m, mode); + + if (pdata->split_mode) + pdata->convert_to_origin_mode(&m); + + if (m.clock > HDMI20_MAX_TMDSCLK_KHZ) return MODE_OK; for (i = 0; i < ARRAY_SIZE(supported_freq); i++) - if (supported_freq[i] == mode->clock) + if (supported_freq[i] == m.clock) break; if (i == ARRAY_SIZE(supported_freq)) @@ -1919,6 +1991,8 @@ static void dw_hdmi_qp_bridge_mode_set(struct drm_bridge *bridge, /* Store the display mode for plugin/DKMS poweron events */ memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode)); + if (hdmi->plat_data->split_mode) + hdmi->plat_data->convert_to_origin_mode(&hdmi->previous_mode); mutex_unlock(&hdmi->mutex); } @@ -2393,13 +2467,15 @@ static void __dw_hdmi_remove(struct dw_hdmi_qp *hdmi) if (hdmi->earc_irq) disable_irq(hdmi->earc_irq); - dw_hdmi_destroy_properties(hdmi); - hdmi->connector.funcs->destroy(&hdmi->connector); + if (!hdmi->plat_data->first_screen) { + dw_hdmi_destroy_properties(hdmi); + hdmi->connector.funcs->destroy(&hdmi->connector); + } if (hdmi->audio && !IS_ERR(hdmi->audio)) platform_device_unregister(hdmi->audio); - if (hdmi->bridge.encoder) + if (hdmi->bridge.encoder && !hdmi->plat_data->first_screen) hdmi->bridge.encoder->funcs->destroy(hdmi->bridge.encoder); if (!IS_ERR(hdmi->cec)) platform_device_unregister(hdmi->cec); @@ -2423,14 +2499,32 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, if (IS_ERR(hdmi)) return hdmi; - ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0); - if (ret) { - __dw_hdmi_remove(hdmi); - dev_err(hdmi->dev, "Failed to initialize bridge with drm\n"); - return ERR_PTR(ret); + if (!plat_data->first_screen) { + ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0); + if (ret) { + __dw_hdmi_remove(hdmi); + dev_err(hdmi->dev, "Failed to initialize bridge with drm\n"); + return ERR_PTR(ret); + } + + plat_data->connector = &hdmi->connector; } - plat_data->connector = &hdmi->connector; + if (plat_data->split_mode && !hdmi->plat_data->first_screen) { + struct dw_hdmi_qp *secondary = NULL; + + if (hdmi->plat_data->left) + secondary = hdmi->plat_data->left; + else if (hdmi->plat_data->right) + secondary = hdmi->plat_data->right; + + if (!secondary) + return ERR_PTR(-ENOMEM); + ret = drm_bridge_attach(encoder, &secondary->bridge, &hdmi->bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ERR_PTR(ret); + } return hdmi; } diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index eb218e96553c..a39ca53713e0 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -132,6 +132,7 @@ struct rockchip_hdmi_chip_data { int ddc_en_reg; u32 lcdsel_big; u32 lcdsel_lit; + bool split_mode; }; enum hdmi_frl_rate_per_lane { @@ -147,7 +148,9 @@ struct rockchip_hdmi { struct regmap *regmap; struct regmap *vo1_regmap; struct drm_encoder encoder; + struct drm_device *drm_dev; const struct rockchip_hdmi_chip_data *chip_data; + struct dw_hdmi_plat_data *plat_data; struct clk *aud_clk; struct clk *phyref_clk; struct clk *grf_clk; @@ -171,6 +174,7 @@ struct rockchip_hdmi { u8 id; bool hpd_stat; bool is_hdmi_qp; + bool user_split_mode; unsigned long bus_format; unsigned long output_bus_format; @@ -187,6 +191,7 @@ struct rockchip_hdmi { struct drm_property *next_hdr_sink_data_property; struct drm_property *output_hdmi_dvi; struct drm_property *output_type_capacity; + struct drm_property *user_split_mode_prop; struct drm_property_blob *hdr_panel_blob_ptr; struct drm_property_blob *next_hdr_data_ptr; @@ -445,34 +450,6 @@ enum COLUMN_INDEX_BPC { #define PPS_BPP_LEN 4 #define PPS_BPC_LEN 2 -/* From DSC_v1.11 spec, rc_parameter_Set syntax element typically constant */ -static const u16 rc_buf_thresh[] = { - 0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, - 0x69, 0x70, 0x77, 0x79, 0x7b, 0x7d, 0x7e, -}; - -struct rc_parameters { - u16 initial_xmit_delay; - u16 initial_dec_delay; - u8 initial_scale_value; - u16 scale_increment_interval; - u16 scale_decrement_interval; - u8 first_line_bpg_offset; - u16 nfl_bpg_offset; - u16 slice_bpg_offset; - u16 initial_offset; - u16 final_offset; - u8 flatness_min_qp; - u8 flatness_max_qp; - u16 rc_model_size; - u8 rc_edge_factor; - u8 rc_quant_incr_limit0; - u8 rc_quant_incr_limit1; - u8 rc_tgt_offset_hi; - u8 rc_tgt_offset_lo; - struct drm_dsc_rc_range_parameters rc_range_params[DSC_NUM_BUF_RANGES]; -}; - struct pps_data { u32 pic_width; u32 pic_height; @@ -779,15 +756,39 @@ hdmi_get_tmdsclock(struct rockchip_hdmi *hdmi, unsigned long pixelclock) return tmdsclock; } +static int rockchip_hdmi_match_by_id(struct device *dev, const void *data) +{ + struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); + const unsigned int *id = data; + + return hdmi->id == *id; +} + +static struct rockchip_hdmi * +rockchip_hdmi_find_by_id(struct device_driver *drv, unsigned int id) +{ + struct device *dev; + + dev = driver_find_device(drv, NULL, &id, rockchip_hdmi_match_by_id); + if (!dev) + return NULL; + + return dev_get_drvdata(dev); +} + static void hdmi_select_link_config(struct rockchip_hdmi *hdmi, struct drm_crtc_state *crtc_state, unsigned int tmdsclk) { - struct drm_display_mode *mode = &crtc_state->mode; + struct drm_display_mode mode; int max_lanes, max_rate_per_lane; int max_dsc_lanes, max_dsc_rate_per_lane; unsigned long max_frl_rate; + drm_mode_copy(&mode, &crtc_state->mode); + if (hdmi->plat_data->split_mode) + drm_mode_convert_to_origin_mode(&mode); + max_lanes = hdmi->max_lanes; max_rate_per_lane = hdmi->max_frl_rate_per_lane; max_frl_rate = max_lanes * max_rate_per_lane * 1000000; @@ -796,7 +797,7 @@ static void hdmi_select_link_config(struct rockchip_hdmi *hdmi, hdmi->link_cfg.frl_lanes = max_lanes; hdmi->link_cfg.rate_per_lane = max_rate_per_lane; - if (!max_frl_rate || (tmdsclk < HDMI20_MAX_RATE && mode->clock < HDMI20_MAX_RATE)) { + if (!max_frl_rate || (tmdsclk < HDMI20_MAX_RATE && mode.clock < HDMI20_MAX_RATE)) { dev_info(hdmi->dev, "use tmds mode\n"); hdmi->link_cfg.frl_mode = false; return; @@ -811,7 +812,7 @@ static void hdmi_select_link_config(struct rockchip_hdmi *hdmi, max_dsc_rate_per_lane = hdmi->dsc_cap.max_frl_rate_per_lane; - if (mode->clock >= HDMI_8K60_RATE && + if (mode.clock >= HDMI_8K60_RATE && !hdmi_bus_fmt_is_yuv420(hdmi->bus_format) && !hdmi_bus_fmt_is_yuv422(hdmi->bus_format)) { hdmi->link_cfg.dsc_mode = true; @@ -1155,7 +1156,7 @@ static void repo_hpd_event(struct work_struct *p_work) struct rockchip_hdmi *hdmi = container_of(p_work, struct rockchip_hdmi, work.work); bool change; - change = drm_helper_hpd_irq_event(hdmi->encoder.dev); + change = drm_helper_hpd_irq_event(hdmi->drm_dev); if (change) { dev_dbg(hdmi->dev, "hpd stat changed:%d\n", hdmi->hpd_stat); dw_hdmi_qp_cec_set_hpd(hdmi->hdmi_qp, hdmi->hpd_stat, change); @@ -1477,10 +1478,14 @@ static void dw_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder) struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc->state); if (crtc->state->active_changed) { - if (!hdmi->id) - s->output_if &= ~VOP_OUTPUT_IF_HDMI0; - else - s->output_if &= ~VOP_OUTPUT_IF_HDMI1; + if (hdmi->plat_data->split_mode) { + s->output_if &= ~(VOP_OUTPUT_IF_HDMI0 | VOP_OUTPUT_IF_HDMI1); + } else { + if (!hdmi->id) + s->output_if &= ~VOP_OUTPUT_IF_HDMI1; + else + s->output_if &= ~VOP_OUTPUT_IF_HDMI0; + } } /* * when plug out hdmi it will be switch cvbs and then phy bus width @@ -1652,16 +1657,25 @@ dw_hdmi_rockchip_select_output(struct drm_connector_state *conn_state, unsigned int *eotf) { struct drm_display_info *info = &conn_state->connector->display_info; - struct drm_display_mode *mode = &crtc_state->mode; + struct drm_display_mode mode; struct hdr_output_metadata *hdr_metadata; - u32 vic = drm_match_cea_mode(mode); - unsigned long tmdsclock, pixclock = mode->crtc_clock; + u32 vic; + unsigned long tmdsclock, pixclock; unsigned int color_depth; bool support_dc = false; bool sink_is_hdmi = true; u32 max_tmds_clock = info->max_tmds_clock; int output_eotf; + drm_mode_copy(&mode, &crtc_state->mode); + pixclock = mode.crtc_clock; + if (hdmi->plat_data->split_mode) { + drm_mode_convert_to_origin_mode(&mode); + pixclock /= 2; + } + + vic = drm_match_cea_mode(&mode); + if (!hdmi->is_hdmi_qp) sink_is_hdmi = dw_hdmi_get_output_whether_hdmi(hdmi->hdmi); @@ -1674,13 +1688,13 @@ dw_hdmi_rockchip_select_output(struct drm_connector_state *conn_state, else if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) *color_format = RK_IF_FORMAT_YCBCR422; else if (conn_state->connector->ycbcr_420_allowed && - drm_mode_is_420(info, mode) && + drm_mode_is_420(info, &mode) && (pixclock >= 594000 && !hdmi->is_hdmi_qp)) *color_format = RK_IF_FORMAT_YCBCR420; break; case RK_IF_FORMAT_YCBCR_LQ: if (conn_state->connector->ycbcr_420_allowed && - drm_mode_is_420(info, mode) && pixclock >= 594000) + drm_mode_is_420(info, &mode) && pixclock >= 594000) *color_format = RK_IF_FORMAT_YCBCR420; else if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422) *color_format = RK_IF_FORMAT_YCBCR422; @@ -1689,7 +1703,7 @@ dw_hdmi_rockchip_select_output(struct drm_connector_state *conn_state, break; case RK_IF_FORMAT_YCBCR420: if (conn_state->connector->ycbcr_420_allowed && - drm_mode_is_420(info, mode) && pixclock >= 594000) + drm_mode_is_420(info, &mode) && pixclock >= 594000) *color_format = RK_IF_FORMAT_YCBCR420; break; case RK_IF_FORMAT_YCBCR422: @@ -1759,7 +1773,7 @@ dw_hdmi_rockchip_select_output(struct drm_connector_state *conn_state, *color_format = RK_IF_FORMAT_YCBCR422; if (hdmi->is_hdmi_qp) { if (info->color_formats & DRM_COLOR_FORMAT_YCRCB420) { - if (mode->clock >= 340000) + if (mode.clock >= 340000) *color_format = RK_IF_FORMAT_YCBCR420; else *color_format = RK_IF_FORMAT_RGB; @@ -1769,9 +1783,9 @@ dw_hdmi_rockchip_select_output(struct drm_connector_state *conn_state, } } - if (mode->flags & DRM_MODE_FLAG_DBLCLK) + if (mode.flags & DRM_MODE_FLAG_DBLCLK) pixclock *= 2; - if ((mode->flags & DRM_MODE_FLAG_3D_MASK) == + if ((mode.flags & DRM_MODE_FLAG_3D_MASK) == DRM_MODE_FLAG_3D_FRAME_PACKING) pixclock *= 2; @@ -1793,16 +1807,16 @@ dw_hdmi_rockchip_select_output(struct drm_connector_state *conn_state, if (max_tmds_clock >= 594000) { color_depth = 8; } else if (max_tmds_clock > 340000) { - if (drm_mode_is_420(info, mode) || tmdsclock >= 594000) + if (drm_mode_is_420(info, &mode) || tmdsclock >= 594000) *color_format = RK_IF_FORMAT_YCBCR420; } else { color_depth = 8; - if (drm_mode_is_420(info, mode) || tmdsclock >= 594000) + if (drm_mode_is_420(info, &mode) || tmdsclock >= 594000) *color_format = RK_IF_FORMAT_YCBCR420; } } - if (mode->clock >= 340000 && hdmi->is_hdmi_qp) + if (mode.clock >= 340000 && hdmi->is_hdmi_qp) *color_format = RK_IF_FORMAT_YCBCR420; if (*color_format == RK_IF_FORMAT_YCBCR420) { @@ -1879,10 +1893,21 @@ dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder, struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); unsigned int colorformat, bus_width, tmdsclk; + struct drm_display_mode mode; unsigned int output_mode; unsigned long bus_format; int color_depth; + bool secondary = false; + /* + * There are two hdmi but only one encoder in split mode, + * so we need to check twice. + */ +secondary: + drm_mode_copy(&mode, &crtc_state->mode); + + if (hdmi->plat_data->split_mode) + drm_mode_convert_to_origin_mode(&mode); dw_hdmi_rockchip_select_output(conn_state, crtc_state, hdmi, &colorformat, &output_mode, &bus_format, &bus_width, @@ -1913,8 +1938,7 @@ dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder, bus_width |= HDMI_FRL_MODE; } else { gpiod_set_value(hdmi->enable_gpio, 1); - bus_width = hdmi_get_tmdsclock(hdmi, - crtc_state->mode.clock * 10); + bus_width = hdmi_get_tmdsclock(hdmi, mode.clock * 10); if (hdmi_bus_fmt_is_yuv420(hdmi->output_bus_format)) bus_width /= 2; @@ -1931,10 +1955,17 @@ dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder, s->output_type = DRM_MODE_CONNECTOR_HDMIA; s->tv_state = &conn_state->tv; - if (!hdmi->id) - s->output_if |= VOP_OUTPUT_IF_HDMI0; - else - s->output_if |= VOP_OUTPUT_IF_HDMI1; + if (hdmi->plat_data->split_mode) { + s->output_flags |= ROCKCHIP_OUTPUT_DUAL_CHANNEL_LEFT_RIGHT_MODE; + if (hdmi->plat_data->right && hdmi->id) + s->output_flags |= ROCKCHIP_OUTPUT_DATA_SWAP; + s->output_if |= VOP_OUTPUT_IF_HDMI0 | VOP_OUTPUT_IF_HDMI1; + } else { + if (!hdmi->id) + s->output_if |= VOP_OUTPUT_IF_HDMI0; + else + s->output_if |= VOP_OUTPUT_IF_HDMI1; + } s->output_mode = output_mode; hdmi->bus_format = s->bus_format; @@ -1948,6 +1979,12 @@ dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder, else s->color_space = V4L2_COLORSPACE_SMPTE170M; + if (hdmi->plat_data->split_mode && !secondary) { + hdmi = rockchip_hdmi_find_by_id(hdmi->dev->driver, !hdmi->id); + secondary = true; + goto secondary; + } + return 0; } @@ -2217,6 +2254,14 @@ dw_hdmi_rockchip_attach_properties(struct drm_connector *connector, drm_object_attach_property(&connector->base, prop, 0); } + prop = drm_property_create_bool(connector->dev, DRM_MODE_PROP_IMMUTABLE, + "USER_SPLIT_MODE"); + if (prop) { + hdmi->user_split_mode_prop = prop; + drm_object_attach_property(&connector->base, prop, + hdmi->user_split_mode ? 1 : 0); + } + if (!hdmi->is_hdmi_qp) { prop = drm_property_create_enum(connector->dev, 0, "output_hdmi_dvi", @@ -2315,6 +2360,12 @@ dw_hdmi_rockchip_destroy_properties(struct drm_connector *connector, hdmi->output_type_capacity); hdmi->output_type_capacity = NULL; } + + if (hdmi->user_split_mode_prop) { + drm_property_destroy(connector->dev, + hdmi->user_split_mode_prop); + hdmi->user_split_mode_prop = NULL; + } } static int @@ -2429,6 +2480,9 @@ dw_hdmi_rockchip_get_property(struct drm_connector *connector, } else if (property == hdmi->output_type_capacity) { *val = dw_hdmi_get_output_type_cap(hdmi->hdmi); return 0; + } else if (property == hdmi->user_split_mode_prop) { + *val = hdmi->user_split_mode; + return 0; } DRM_ERROR("Unknown property [PROP:%d:%s]\n", @@ -2779,6 +2833,7 @@ static const struct dw_hdmi_qp_phy_ops rk3588_hdmi_phy_ops = { struct rockchip_hdmi_chip_data rk3588_hdmi_chip_data = { .lcdsel_grf_reg = -1, .ddc_en_reg = RK3588_GRF_VO1_CON3, + .split_mode = true, }; static const struct dw_hdmi_plat_data rk3588_hdmi_drv_data = { @@ -2822,33 +2877,22 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); - struct dw_hdmi_plat_data *plat_data; - const struct of_device_id *match; struct drm_device *drm = data; struct drm_encoder *encoder; struct rockchip_hdmi *hdmi; - int ret, id; + struct dw_hdmi_plat_data *plat_data; + int ret; u32 val; if (!pdev->dev.of_node) return -ENODEV; - hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); + hdmi = platform_get_drvdata(pdev); if (!hdmi) return -ENOMEM; - match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node); - plat_data = devm_kmemdup(&pdev->dev, match->data, - sizeof(*plat_data), GFP_KERNEL); - if (!plat_data) - return -ENOMEM; - - id = of_alias_get_id(dev->of_node, "hdmi"); - if (id < 0) - id = 0; - hdmi->id = id; - hdmi->dev = &pdev->dev; - hdmi->chip_data = plat_data->phy_data; + plat_data = hdmi->plat_data; + hdmi->drm_dev = drm; plat_data->phy_data = hdmi; plat_data->get_input_bus_format = @@ -2875,20 +2919,54 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, dw_hdmi_rockchip_get_next_hdr_data; plat_data->get_link_cfg = dw_hdmi_rockchip_get_link_cfg; plat_data->set_grf_cfg = rk3588_set_grf_cfg; + plat_data->convert_to_split_mode = drm_mode_convert_to_split_mode; + plat_data->convert_to_origin_mode = drm_mode_convert_to_origin_mode; plat_data->property_ops = &dw_hdmi_rockchip_property_ops; - encoder = &hdmi->encoder; + if (hdmi->chip_data->split_mode) { + struct rockchip_hdmi *secondary = + rockchip_hdmi_find_by_id(dev->driver, !hdmi->id); - encoder->possible_crtcs = rockchip_drm_of_find_possible_crtcs(drm, dev->of_node); - /* - * If we failed to find the CRTC(s) which this encoder is - * supposed to be connected to, it's because the CRTC has - * not been registered yet. Defer probing, and hope that - * the required CRTC is added later. - */ - if (encoder->possible_crtcs == 0) - return -EPROBE_DEFER; + if (!secondary) + return -EPROBE_DEFER; + + /* + * hdmi can only attach bridge and init encoder/connector in the + * last bind hdmi in split mode, or hdmi->hdmi_qp will not be initialized + * and plat_data->left/right will be null pointer. we must check if split + * mode is on and determine the sequence of hdmi bind. + */ + if (device_property_read_bool(dev, "split-mode") || + device_property_read_bool(secondary->dev, "split-mode")) { + plat_data->split_mode = true; + secondary->plat_data->split_mode = true; + if (!secondary->plat_data->first_screen) + plat_data->first_screen = true; + } + + if (device_property_read_bool(dev, "user-split-mode") || + device_property_read_bool(secondary->dev, "user-split-mode")) { + hdmi->user_split_mode = true; + secondary->user_split_mode = true; + } + } + + if (!plat_data->first_screen) { + encoder = &hdmi->encoder; + encoder->possible_crtcs = rockchip_drm_of_find_possible_crtcs(drm, dev->of_node); + /* + * If we failed to find the CRTC(s) which this encoder is + * supposed to be connected to, it's because the CRTC has + * not been registered yet. Defer probing, and hope that + * the required CRTC is added later. + */ + if (encoder->possible_crtcs == 0) + return -EPROBE_DEFER; + + drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs); + drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + } if (!plat_data->max_tmdsclk) hdmi->max_tmdsclk = 594000; @@ -3036,17 +3114,12 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, } } - drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs); - drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); - - platform_set_drvdata(pdev, hdmi); - if (hdmi->is_hdmi_qp) { - hdmi->hdmi_qp = dw_hdmi_qp_bind(pdev, encoder, plat_data); + hdmi->hdmi_qp = dw_hdmi_qp_bind(pdev, &hdmi->encoder, plat_data); if (IS_ERR(hdmi->hdmi_qp)) { ret = PTR_ERR(hdmi->hdmi_qp); - drm_encoder_cleanup(encoder); + drm_encoder_cleanup(&hdmi->encoder); } if (plat_data->connector) { @@ -3055,10 +3128,23 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, rockchip_drm_register_sub_dev(&hdmi->sub_dev); } + if (plat_data->split_mode) { + struct rockchip_hdmi *secondary = + rockchip_hdmi_find_by_id(dev->driver, !hdmi->id); + + if (device_property_read_bool(dev, "split-mode")) { + plat_data->right = secondary->hdmi_qp; + secondary->plat_data->left = hdmi->hdmi_qp; + } else { + plat_data->left = secondary->hdmi_qp; + secondary->plat_data->right = hdmi->hdmi_qp; + } + } + return ret; } - hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data); + hdmi->hdmi = dw_hdmi_bind(pdev, &hdmi->encoder, plat_data); /* * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(), @@ -3066,7 +3152,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, */ if (IS_ERR(hdmi->hdmi)) { ret = PTR_ERR(hdmi->hdmi); - drm_encoder_cleanup(encoder); + drm_encoder_cleanup(&hdmi->encoder); clk_disable_unprepare(hdmi->aud_clk); clk_disable_unprepare(hdmi->phyref_clk); clk_disable_unprepare(hdmi->hclk_vop); @@ -3121,6 +3207,31 @@ static const struct component_ops dw_hdmi_rockchip_ops = { static int dw_hdmi_rockchip_probe(struct platform_device *pdev) { + struct rockchip_hdmi *hdmi; + const struct of_device_id *match; + struct dw_hdmi_plat_data *plat_data; + int id; + + hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + id = of_alias_get_id(pdev->dev.of_node, "hdmi"); + if (id < 0) + id = 0; + hdmi->id = id; + hdmi->dev = &pdev->dev; + + match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node); + plat_data = devm_kmemdup(&pdev->dev, match->data, + sizeof(*plat_data), GFP_KERNEL); + if (!plat_data) + return -ENOMEM; + + hdmi->plat_data = plat_data; + hdmi->chip_data = plat_data->phy_data; + + platform_set_drvdata(pdev, hdmi); pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); @@ -3202,7 +3313,7 @@ static int dw_hdmi_rockchip_resume(struct device *dev) } dw_hdmi_qp_resume(dev, hdmi->hdmi_qp); - drm_helper_hpd_irq_event(hdmi->encoder.dev); + drm_helper_hpd_irq_event(hdmi->drm_dev); } else { dw_hdmi_resume(hdmi->hdmi); } diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index b1057b6246fd..45e76bd91bf8 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -205,6 +205,12 @@ struct dw_hdmi_plat_data { unsigned int phy_force_vendor; const struct dw_hdmi_audio_tmds_n *tmds_n_table; + /* split mode */ + bool split_mode; + bool first_screen; + struct dw_hdmi_qp *left; + struct dw_hdmi_qp *right; + /* Synopsys PHY support */ const struct dw_hdmi_mpll_config *mpll_cfg; const struct dw_hdmi_mpll_config *mpll_cfg_420; @@ -228,6 +234,8 @@ struct dw_hdmi_plat_data { struct drm_connector *connector); struct dw_hdmi_link_config *(*get_link_cfg)(void *data); void (*set_grf_cfg)(void *data); + void (*convert_to_split_mode)(struct drm_display_mode *mode); + void (*convert_to_origin_mode)(struct drm_display_mode *mode); /* Vendor Property support */ const struct dw_hdmi_property_ops *property_ops;