Loading Documentation/devicetree/bindings/fb/mdss-dp.txt +50 −2 Original line number Diff line number Diff line Loading @@ -27,7 +27,46 @@ Required properties - qcom,aux-en-gpio: Specifies the aux-channel enable gpio. - qcom,aux-sel-gpio: Specifies the aux-channel select gpio. - qcom,usbplug-cc-gpio: Specifies the usbplug orientation gpio. - qcom,aux-cfg-settings: An array that specifies the DP AUX configuration settings. - qcom,aux-cfg0-settings: Specifies the DP AUX configuration 0 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg1-settings: Specifies the DP AUX configuration 1 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg2-settings: Specifies the DP AUX configuration 2 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg3-settings: Specifies the DP AUX configuration 3 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg4-settings: Specifies the DP AUX configuration 4 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg5-settings: Specifies the DP AUX configuration 5 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg6-settings: Specifies the DP AUX configuration 6 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg7-settings: Specifies the DP AUX configuration 7 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg8-settings: Specifies the DP AUX configuration 8 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg9-settings: Specifies the DP AUX configuration 9 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. Optional properties: - qcom,<type>-supply-entries: A node that lists the elements of the supply used by the Loading Loading @@ -87,7 +126,16 @@ Example: "core_aux_clk", "core_cfg_ahb_clk", "ctrl_link_clk", "ctrl_link_iface_clk", "ctrl_crypto_clk", "ctrl_pixel_clk"; qcom,aux-cfg-settings = [00 13 00 10 0a 26 0a 03 8b 03]; qcom,aux-cfg0-settings = [1c 00]; qcom,aux-cfg1-settings = [20 13 23 1d]; qcom,aux-cfg2-settings = [24 00]; qcom,aux-cfg3-settings = [28 00]; qcom,aux-cfg4-settings = [2c 0a]; qcom,aux-cfg5-settings = [30 26]; qcom,aux-cfg6-settings = [34 0a]; qcom,aux-cfg7-settings = [38 03]; qcom,aux-cfg8-settings = [3c bb]; qcom,aux-cfg9-settings = [40 03]; qcom,logical2physical-lane-map = [02 03 01 00]; qcom,phy-register-offset = <0x4>; qcom,max-pclk-frequency-khz = <593470>; Loading arch/arm/boot/dts/qcom/msm8998-mdss.dtsi +10 −1 Original line number Diff line number Diff line Loading @@ -502,7 +502,16 @@ qcom,msm_ext_disp = <&msm_ext_disp>; qcom,aux-cfg-settings = [00 13 00 10 0a 26 0a 03 8b 03]; qcom,aux-cfg0-settings = [1c 00]; qcom,aux-cfg1-settings = [20 13 23 1d]; qcom,aux-cfg2-settings = [24 00]; qcom,aux-cfg3-settings = [28 00]; qcom,aux-cfg4-settings = [2c 0a]; qcom,aux-cfg5-settings = [30 26]; qcom,aux-cfg6-settings = [34 0a]; qcom,aux-cfg7-settings = [38 03]; qcom,aux-cfg8-settings = [3c bb]; qcom,aux-cfg9-settings = [40 03]; qcom,logical2physical-lane-map = [02 03 01 00]; qcom,core-supply-entries { Loading arch/arm/boot/dts/qcom/sdm660-mdss.dtsi +10 −1 Original line number Diff line number Diff line Loading @@ -505,7 +505,16 @@ qcom,msm_ext_disp = <&msm_ext_disp>; qcom,aux-cfg-settings = [00 13 00 00 0a 28 0a 03 b7 03]; qcom,aux-cfg0-settings = [20 00]; qcom,aux-cfg1-settings = [24 13 23 1d]; qcom,aux-cfg2-settings = [28 00]; qcom,aux-cfg3-settings = [2c 00]; qcom,aux-cfg4-settings = [30 0a]; qcom,aux-cfg5-settings = [34 28]; qcom,aux-cfg6-settings = [38 0a]; qcom,aux-cfg7-settings = [3c 03]; qcom,aux-cfg8-settings = [40 b7]; qcom,aux-cfg9-settings = [44 03]; qcom,logical2physical-lane-map = [00 01 02 03]; qcom,phy-register-offset = <0x4>; qcom,max-pclk-frequency-khz = <300000>; Loading drivers/video/fbdev/msm/mdss_dp.c +294 −81 Original line number Diff line number Diff line Loading @@ -69,6 +69,11 @@ static int mdss_dp_process_phy_test_pattern_request( static int mdss_dp_send_audio_notification( struct mdss_dp_drv_pdata *dp, int val); static inline void mdss_dp_reset_sink_count(struct mdss_dp_drv_pdata *dp) { memset(&dp->sink_count, 0, sizeof(dp->sink_count)); } static inline void mdss_dp_reset_test_data(struct mdss_dp_drv_pdata *dp) { dp->test_data = (const struct dpcd_test_request){ 0 }; Loading Loading @@ -133,22 +138,77 @@ static int mdss_dp_is_clk_prefix(const char *clk_prefix, const char *clk_name) return !strncmp(clk_name, clk_prefix, strlen(clk_prefix)); } static int mdss_dp_parse_prop(struct platform_device *pdev, struct mdss_dp_drv_pdata *dp_drv) static void mdss_dp_reset_phy_config_indices(struct mdss_dp_drv_pdata *dp) { int len = 0, i = 0, rc = 0; int i = 0; for (i = 0; i < PHY_AUX_CFG_MAX; i++) dp->aux_cfg[i].current_index = 0; } static void mdss_dp_phy_aux_cfg_reset(struct mdss_dp_drv_pdata *dp) { int i = 0; for (i = 0; i < PHY_AUX_CFG_MAX; i++) dp->aux_cfg[i] = (const struct mdss_dp_phy_cfg){ 0 }; } static int mdss_dp_parse_aux_cfg(struct platform_device *pdev, struct mdss_dp_drv_pdata *dp) { int len = 0, i = 0, j = 0, config_count = 0; const char *data; int const minimum_config_count = 1; data = of_get_property(pdev->dev.of_node, "qcom,aux-cfg-settings", &len); if ((!data) || (len != AUX_CFG_LEN)) { pr_err("%s:%d, Unable to read DP AUX CFG settings", __func__, __LINE__); for (i = 0; i < PHY_AUX_CFG_MAX; i++) { const char *property = mdss_dp_get_phy_aux_config_property(i); data = of_get_property(pdev->dev.of_node, property, &len); if (!data) { pr_err("Unable to read %s\n", property); goto error; } config_count = len - 1; if ((config_count < minimum_config_count) || (config_count > MDSS_DP_MAX_PHY_CFG_VALUE_CNT)) { pr_err("Invalid config count (%d) configs for %s\n", config_count, property); goto error; } dp->aux_cfg[i].offset = data[0]; dp->aux_cfg[i].cfg_cnt = config_count; pr_debug("%s offset=0x%x, cfg_cnt=%d\n", property, dp->aux_cfg[i].offset, dp->aux_cfg[i].cfg_cnt); for (j = 1; j < len; j++) { dp->aux_cfg[i].lut[j - 1] = data[j]; pr_debug("%s lut[%d]=0x%x\n", property, i, dp->aux_cfg[i].lut[j - 1]); } } return 0; error: mdss_dp_phy_aux_cfg_reset(dp); return -EINVAL; } for (i = 0; i < len; i++) dp_drv->aux_cfg[i] = data[i]; static int mdss_dp_parse_prop(struct platform_device *pdev, struct mdss_dp_drv_pdata *dp_drv) { int len = 0, i = 0, rc = 0; const char *data; rc = mdss_dp_parse_aux_cfg(pdev, dp_drv); if (rc) return rc; data = of_get_property(pdev->dev.of_node, "qcom,logical2physical-lane-map", &len); Loading Loading @@ -958,6 +1018,12 @@ void mdss_dp_config_ctrl(struct mdss_dp_drv_pdata *dp) mdss_dp_configuration_ctrl(&dp->ctrl_io, data); } static inline void mdss_dp_ack_state(struct mdss_dp_drv_pdata *dp, int val) { if (dp && dp->ext_audio_data.intf_ops.notify) dp->ext_audio_data.intf_ops.notify(dp->ext_pdev, val); } static int mdss_dp_wait4video_ready(struct mdss_dp_drv_pdata *dp_drv) { int ret = 0; Loading Loading @@ -1230,12 +1296,6 @@ static int dp_init_panel_info(struct mdss_dp_drv_pdata *dp_drv, u32 vic) return 0; } /* dp_init_panel_info */ static inline void mdss_dp_ack_state(struct mdss_dp_drv_pdata *dp, int val) { if (dp && dp->ext_audio_data.intf_ops.notify) dp->ext_audio_data.intf_ops.notify(dp->ext_pdev, val); } /** * mdss_dp_get_lane_mapping() - returns lane mapping based on given orientation * @orientation: usb plug orientation Loading Loading @@ -1621,15 +1681,19 @@ int mdss_dp_on(struct mdss_panel_data *pdata) dp_drv = container_of(pdata, struct mdss_dp_drv_pdata, panel_data); if (dp_drv->power_on) { /* * Acknowledge the connection event if link training has already * been done. This will unblock the external display thread and * allow the driver to progress. For example, in the case of * video test pattern requests, to send the test response and * start transmitting the test pattern. * If the link already active, then nothing needs to be done here. * However, it is possible that the the power_on flag could be * set to true but we would still need to initialize the DP host. * An example of this use-case is when a multiport dongle is connected * and subsequently the downstream sink is disconnected. This would * only go through the IRQ HPD path where we tear down the link but * the power_on flag remains set to true. When the downstream sink * is subsequently connected again, we need to re-initialize DP * host */ mdss_dp_ack_state(dp_drv, true); if (dp_drv->power_on && (dp_drv->new_vic && (dp_drv->new_vic == dp_drv->vic))) { pr_debug("Link already setup, return\n"); return 0; } Loading @@ -1647,6 +1711,23 @@ int mdss_dp_on(struct mdss_panel_data *pdata) return mdss_dp_on_hpd(dp_drv); } static bool mdss_dp_is_ds_bridge(struct mdss_dp_drv_pdata *dp) { return dp->dpcd.downstream_port.dfp_present; } static bool mdss_dp_is_ds_bridge_sink_count_zero(struct mdss_dp_drv_pdata *dp) { return (mdss_dp_is_ds_bridge(dp) && (dp->sink_count.count == 0)); } static bool mdss_dp_is_ds_bridge_no_local_edid(struct mdss_dp_drv_pdata *dp) { return (mdss_dp_is_ds_bridge_sink_count_zero(dp) && !(dp->dpcd.flags & DPCD_PORT_0_EDID_PRESENTED)); } static int mdss_dp_off_irq(struct mdss_dp_drv_pdata *dp_drv) { if (!dp_drv->power_on) { Loading @@ -1664,10 +1745,16 @@ static int mdss_dp_off_irq(struct mdss_dp_drv_pdata *dp_drv) /* Make sure DP mainlink and audio engines are disabled */ wmb(); mdss_dp_ack_state(dp_drv, false); /* * If downstream device is a brige which no longer has any * downstream devices connected to it, then we should reset * the current panel info */ if (mdss_dp_is_ds_bridge_sink_count_zero(dp_drv)) dp_init_panel_info(dp_drv, HDMI_VFRMT_UNKNOWN); mutex_unlock(&dp_drv->train_mutex); complete_all(&dp_drv->irq_comp); pr_debug("end\n"); return 0; Loading @@ -1694,11 +1781,11 @@ static int mdss_dp_off_hpd(struct mdss_dp_drv_pdata *dp_drv) mdss_dp_host_deinit(dp_drv); dp_drv->power_on = false; dp_drv->sink_info_read = false; dp_init_panel_info(dp_drv, HDMI_VFRMT_UNKNOWN); mdss_dp_ack_state(dp_drv, false); mdss_dp_reset_test_data(dp_drv); mdss_dp_reset_sink_count(dp_drv); dp_drv->prev_sink_count = dp_drv->sink_count; mutex_unlock(&dp_drv->train_mutex); pr_debug("DP off done\n"); Loading Loading @@ -1737,8 +1824,9 @@ static int mdss_dp_send_audio_notification( if (mdss_dp_sink_audio_supp(dp) || dp->audio_test_req) { dp->audio_test_req = false; pr_debug("sending audio notification\n"); flags |= MSM_EXT_DISP_HPD_AUDIO; pr_debug("sending audio notification = %d, flags = %d\n", val, flags); if (dp->ext_audio_data.intf_ops.hpd) ret = dp->ext_audio_data.intf_ops.hpd(dp->ext_pdev, Loading @@ -1763,7 +1851,8 @@ static int mdss_dp_send_video_notification( goto end; } flags |= MSM_EXT_DISP_HPD_VIDEO; flags |= MSM_EXT_DISP_HPD_ASYNC_VIDEO; pr_debug("sending video notification = %d, flags = %d\n", val, flags); if (dp->ext_audio_data.intf_ops.hpd) ret = dp->ext_audio_data.intf_ops.hpd(dp->ext_pdev, Loading Loading @@ -1873,14 +1962,16 @@ static int mdss_dp_host_init(struct mdss_panel_data *pdata) mdss_dp_ctrl_reset(&dp_drv->ctrl_io); mdss_dp_phy_reset(&dp_drv->ctrl_io); mdss_dp_aux_reset(&dp_drv->ctrl_io); mdss_dp_aux_set_limits(&dp_drv->ctrl_io); mdss_dp_aux_ctrl(&dp_drv->ctrl_io, true); pr_debug("Ctrl_hw_rev =0x%x, phy hw_rev =0x%x\n", mdss_dp_get_ctrl_hw_version(&dp_drv->ctrl_io), mdss_dp_get_phy_hw_version(&dp_drv->phy_io)); mdss_dp_phy_aux_setup(&dp_drv->phy_io, dp_drv->aux_cfg, dp_drv->phy_reg_offset); mdss_dp_reset_phy_config_indices(dp_drv); mdss_dp_phy_aux_setup(dp_drv); mdss_dp_irq_enable(dp_drv); dp_drv->dp_initialized = true; Loading Loading @@ -1948,10 +2039,12 @@ static int mdss_dp_host_deinit(struct mdss_dp_drv_pdata *dp) static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp, enum notification_status status) { const int irq_comp_timeout = HZ * 2; int ret = 0; bool notify = false; bool connect; mutex_lock(&dp->pd_msg_mutex); pr_debug("beginning notification\n"); if (status == dp->hpd_notification_status) { pr_debug("No change in status %s --> %s\n", mdss_dp_notification_status_to_string(status), Loading @@ -1964,39 +2057,40 @@ static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp, case NOTIFY_CONNECT_IRQ_HPD: if (dp->hpd_notification_status != NOTIFY_DISCONNECT_IRQ_HPD) goto invalid_request; /* Follow the same programming as for NOTIFY_CONNECT */ mdss_dp_host_init(&dp->panel_data); mdss_dp_send_video_notification(dp, true); notify = true; connect = true; break; case NOTIFY_CONNECT: if ((dp->hpd_notification_status == NOTIFY_CONNECT_IRQ_HPD) || (dp->hpd_notification_status == NOTIFY_DISCONNECT_IRQ_HPD)) if (dp->hpd_notification_status == NOTIFY_CONNECT_IRQ_HPD) goto invalid_request; mdss_dp_host_init(&dp->panel_data); mdss_dp_send_video_notification(dp, true); notify = true; connect = true; break; case NOTIFY_DISCONNECT: mdss_dp_send_audio_notification(dp, false); mdss_dp_send_video_notification(dp, false); /* * Userspace triggers a disconnect event on boot up, this must * not be processed as there was no previously connected sink * device. */ if (dp->hpd_notification_status == NOTIFY_UNKNOWN) goto invalid_request; if (dp->hpd_notification_status == NOTIFY_DISCONNECT_IRQ_HPD) { /* * user modules already turned off. Need to explicitly * turn off DP core here. */ mdss_dp_off_hpd(dp); } else { notify = true; connect = false; } break; case NOTIFY_DISCONNECT_IRQ_HPD: if (dp->hpd_notification_status == NOTIFY_DISCONNECT) goto invalid_request; mdss_dp_send_audio_notification(dp, false); mdss_dp_send_video_notification(dp, false); if (!IS_ERR_VALUE(ret) && ret) { reinit_completion(&dp->irq_comp); ret = wait_for_completion_timeout(&dp->irq_comp, irq_comp_timeout); if (ret <= 0) { pr_warn("irq_comp timed out\n"); ret = -EINVAL; } else { ret = 0; } } notify = true; connect = false; break; default: pr_err("Invalid notification status = %d\n", status); Loading @@ -2004,7 +2098,7 @@ static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp, break; } goto end; goto notify; invalid_request: pr_err("Invalid request %s --> %s\n", Loading @@ -2013,15 +2107,34 @@ invalid_request: mdss_dp_notification_status_to_string(status)); ret = -EINVAL; end: notify: if (ret || !notify) { pr_debug("not sending notification\n"); goto end; } atomic_set(&dp->notification_pending, 1); if (connect) { mdss_dp_host_init(&dp->panel_data); ret = mdss_dp_send_video_notification(dp, true); } else { mdss_dp_send_audio_notification(dp, false); ret = mdss_dp_send_video_notification(dp, false); } if (!ret) { pr_debug("Successfully sent notification %s --> %s\n", mdss_dp_notification_status_to_string( dp->hpd_notification_status), mdss_dp_notification_status_to_string(status)); dp->hpd_notification_status = status; } else { pr_err("%s Notification failed\n", mdss_dp_notification_status_to_string(status)); atomic_set(&dp->notification_pending, 0); } end: dp->hpd_notification_status = status; mutex_unlock(&dp->pd_msg_mutex); return ret; } Loading @@ -2031,9 +2144,6 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp) int ret; u32 max_pclk_khz; if (dp->sink_info_read) return 0; pr_debug("start\n"); ret = mdss_dp_dpcd_cap_read(dp); Loading @@ -2046,8 +2156,25 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp) */ pr_err("dpcd read failed, set failsafe parameters\n"); mdss_dp_set_default_link_parameters(dp); goto read_edid; } /* * When connected to a multiport adaptor which does not have a * local EDID present, do not attempt to read the EDID. * When connected to a multiport adaptor with no downstream device * connected to it, do not attempt to read the EDID. It is possible * that the adaptor may advertise the presence of local EDID, but it * is not guaranteed to work. */ if (mdss_dp_is_ds_bridge_sink_count_zero(dp)) { if (mdss_dp_is_ds_bridge_no_local_edid(dp)) pr_debug("No local EDID present on DS branch device\n"); pr_info("no downstream devices, skip client notification\n"); goto end; } read_edid: ret = mdss_dp_edid_read(dp); if (ret) { pr_err("edid read error, setting default resolution\n"); Loading @@ -2058,14 +2185,18 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp) hdmi_edid_set_max_pclk_rate(dp->panel_data.panel_info.edid_data, min(dp->max_pclk_khz, max_pclk_khz)); if (dp->dpcd_read_required) { pr_debug("reading DPCD with updated AUX config\n"); mdss_dp_dpcd_cap_read(dp); dp->dpcd_read_required = false; } ret = hdmi_edid_parser(dp->panel_data.panel_info.edid_data); if (ret) { pr_err("edid parse failed, setting default resolution\n"); goto notify; } dp->sink_info_read = true; notify: if (ret) { /* set failsafe parameters */ Loading @@ -2092,7 +2223,6 @@ notify: end: pr_debug("end\n"); return ret; } static int mdss_dp_check_params(struct mdss_dp_drv_pdata *dp, void *arg) Loading Loading @@ -2814,8 +2944,6 @@ static void mdss_dp_mainlink_push_idle(struct mdss_panel_data *pdata) /* wait until link training is completed */ mutex_lock(&dp_drv->train_mutex); mdss_dp_aux_set_sink_power_state(dp_drv, SINK_POWER_OFF); reinit_completion(&dp_drv->idle_comp); mdss_dp_state_ctrl(&dp_drv->ctrl_io, ST_PUSH_IDLE); if (!wait_for_completion_timeout(&dp_drv->idle_comp, Loading Loading @@ -2905,28 +3033,33 @@ static int mdss_dp_event_handler(struct mdss_panel_data *pdata, switch (event) { case MDSS_EVENT_UNBLANK: mdss_dp_ack_state(dp, true); rc = mdss_dp_on(pdata); break; case MDSS_EVENT_PANEL_ON: mdss_dp_update_hdcp_info(dp); if (dp_is_hdcp_enabled(dp)) { cancel_delayed_work(&dp->hdcp_cb_work); cancel_delayed_work_sync(&dp->hdcp_cb_work); dp->hdcp_status = HDCP_STATE_AUTHENTICATING; queue_delayed_work(dp->workq, &dp->hdcp_cb_work, HZ / 2); } break; case MDSS_EVENT_POST_PANEL_ON: atomic_set(&dp->notification_pending, 0); complete_all(&dp->notification_comp); break; case MDSS_EVENT_PANEL_OFF: rc = mdss_dp_off(pdata); atomic_set(&dp->notification_pending, 0); complete_all(&dp->notification_comp); break; case MDSS_EVENT_BLANK: if (dp_is_hdcp_enabled(dp)) { dp->hdcp_status = HDCP_STATE_INACTIVE; cancel_delayed_work(&dp->hdcp_cb_work); cancel_delayed_work_sync(&dp->hdcp_cb_work); if (dp->hdcp.ops->off) dp->hdcp.ops->off(dp->hdcp.data); } Loading Loading @@ -3083,6 +3216,7 @@ static int mdss_retrieve_dp_ctrl_resources(struct platform_device *pdev, static void mdss_dp_video_ready(struct mdss_dp_drv_pdata *dp) { pr_debug("dp_video_ready\n"); mdss_dp_ack_state(dp, true); complete(&dp->video_comp); } Loading Loading @@ -3209,6 +3343,7 @@ irqreturn_t dp_isr(int irq, void *ptr) spin_lock(&dp->lock); isr1 = dp_read(base + DP_INTR_STATUS); isr2 = dp_read(base + DP_INTR_STATUS2); pr_debug("isr1=0x%08x, isr2=0x%08x\n", isr1, isr2); mask1 = isr1 & dp->mask1; Loading Loading @@ -3425,6 +3560,17 @@ static inline void mdss_dp_link_maintenance(struct mdss_dp_drv_pdata *dp, if (mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT_IRQ_HPD)) return; if (atomic_read(&dp->notification_pending)) { int ret; pr_debug("waiting for the disconnect to finish\n"); ret = wait_for_completion_timeout(&dp->notification_comp, HZ); if (ret <= 0) { pr_warn("NOTIFY_DISCONNECT_IRQ_HPD timed out\n"); return; } } mdss_dp_on_irq(dp, lt_needed); } Loading Loading @@ -3576,7 +3722,7 @@ static int mdss_dp_process_audio_pattern_request(struct mdss_dp_drv_pdata *dp) return -EINVAL; if (dp_is_hdcp_enabled(dp) && dp->hdcp.ops->off) { cancel_delayed_work(&dp->hdcp_cb_work); cancel_delayed_work_sync(&dp->hdcp_cb_work); dp->hdcp.ops->off(dp->hdcp.data); } Loading Loading @@ -3622,10 +3768,46 @@ static int mdss_dp_process_audio_pattern_request(struct mdss_dp_drv_pdata *dp) static int mdss_dp_process_downstream_port_status_change( struct mdss_dp_drv_pdata *dp) { if (!mdss_dp_is_downstream_port_status_changed(dp)) bool ds_status_changed = false; if (mdss_dp_is_downstream_port_status_changed(dp)) { pr_debug("downstream port status changed\n"); ds_status_changed = true; } /* * Ideally sink should update the downstream port status changed * whenever it updates the downstream sink count. However, it is * possible that only the sink count is updated without setting * the downstream port status changed bit. */ if (dp->sink_count.count != dp->prev_sink_count.count) { pr_debug("downstream sink count changed from %d --> %d\n", dp->prev_sink_count.count, dp->sink_count.count); ds_status_changed = true; } if (!ds_status_changed) return -EINVAL; return mdss_dp_edid_read(dp); mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT_IRQ_HPD); if (atomic_read(&dp->notification_pending)) { int ret; pr_debug("waiting for the disconnect to finish\n"); ret = wait_for_completion_timeout(&dp->notification_comp, HZ); if (ret <= 0) { pr_warn("NOTIFY_DISCONNECT_IRQ_HPD timed out\n"); return -ETIMEDOUT; } } if (mdss_dp_is_ds_bridge_sink_count_zero(dp)) { pr_debug("sink count is zero, nothing to do\n"); return 0; } return mdss_dp_process_hpd_high(dp); } static bool mdss_dp_video_pattern_test_lt_needed(struct mdss_dp_drv_pdata *dp) Loading Loading @@ -3723,19 +3905,19 @@ static int mdss_dp_process_hpd_irq_high(struct mdss_dp_drv_pdata *dp) mdss_dp_aux_parse_sink_status_field(dp); ret = mdss_dp_process_link_training_request(dp); ret = mdss_dp_process_downstream_port_status_change(dp); if (!ret) goto exit; ret = mdss_dp_process_phy_test_pattern_request(dp); ret = mdss_dp_process_link_training_request(dp); if (!ret) goto exit; ret = mdss_dp_process_link_status_update(dp); ret = mdss_dp_process_phy_test_pattern_request(dp); if (!ret) goto exit; ret = mdss_dp_process_downstream_port_status_change(dp); ret = mdss_dp_process_link_status_update(dp); if (!ret) goto exit; Loading @@ -3748,7 +3930,6 @@ static int mdss_dp_process_hpd_irq_high(struct mdss_dp_drv_pdata *dp) goto exit; pr_debug("done\n"); exit: dp->hpd_irq_on = false; return ret; Loading Loading @@ -3842,11 +4023,21 @@ static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv) if (!dp_drv->alt_mode.dp_status.hpd_high) { pr_debug("Attention: HPD low\n"); if (!dp_drv->power_on) { pr_debug("HPD already low\n"); return; } if (dp_is_hdcp_enabled(dp_drv) && dp_drv->hdcp.ops->off) { cancel_delayed_work(&dp_drv->hdcp_cb_work); cancel_delayed_work_sync(&dp_drv->hdcp_cb_work); dp_drv->hdcp.ops->off(dp_drv->hdcp.data); } /* * Reset the sink count before nofifying clients since HPD Low * indicates that the downstream device has been disconnected. */ mdss_dp_reset_sink_count(dp_drv); mdss_dp_notify_clients(dp_drv, NOTIFY_DISCONNECT); pr_debug("Attention: Notified clients\n"); Loading Loading @@ -3874,6 +4065,11 @@ static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv) pr_debug("Attention: HPD high\n"); if (dp_drv->power_on) { pr_debug("HPD high processed already\n"); return; } dp_drv->alt_mode.current_state |= DP_STATUS_DONE; if (dp_drv->alt_mode.current_state & DP_CONFIGURE_DONE) { Loading @@ -3895,6 +4091,7 @@ static void mdss_dp_handle_attention(struct mdss_dp_drv_pdata *dp) pr_debug("processing item %d in the list\n", ++i); reinit_completion(&dp->notification_comp); mutex_lock(&dp->attention_lock); node = list_first_entry(&dp->attention_head, struct mdss_dp_attention_node, list); Loading @@ -3909,6 +4106,21 @@ static void mdss_dp_handle_attention(struct mdss_dp_drv_pdata *dp) mdss_dp_usbpd_ext_dp_status(&dp->alt_mode.dp_status); mdss_dp_process_attention(dp); if (atomic_read(&dp->notification_pending)) { pr_debug("waiting for the attention event to finish\n"); /* * This wait is intentionally implemented without a * timeout since this is happens only in possible error * conditions e.g. if the display framework does not * power off/on the DisplayPort device in time. Other * events might already be queued from the sink at this * point and they cannot be processed until the power * off/on is complete otherwise we might have problems * with interleaving of these events e.g. un-clocked * register access. */ wait_for_completion(&dp->notification_comp); } pr_debug("done processing item %d in the list\n", i); }; Loading Loading @@ -4080,8 +4292,9 @@ static int mdss_dp_probe(struct platform_device *pdev) dp_drv->inited = true; dp_drv->hpd_irq_on = false; atomic_set(&dp_drv->notification_pending, 0); mdss_dp_reset_test_data(dp_drv); init_completion(&dp_drv->irq_comp); init_completion(&dp_drv->notification_comp); dp_drv->suspend_vic = HDMI_VFRMT_UNKNOWN; pr_debug("done\n"); Loading Loading
Documentation/devicetree/bindings/fb/mdss-dp.txt +50 −2 Original line number Diff line number Diff line Loading @@ -27,7 +27,46 @@ Required properties - qcom,aux-en-gpio: Specifies the aux-channel enable gpio. - qcom,aux-sel-gpio: Specifies the aux-channel select gpio. - qcom,usbplug-cc-gpio: Specifies the usbplug orientation gpio. - qcom,aux-cfg-settings: An array that specifies the DP AUX configuration settings. - qcom,aux-cfg0-settings: Specifies the DP AUX configuration 0 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg1-settings: Specifies the DP AUX configuration 1 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg2-settings: Specifies the DP AUX configuration 2 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg3-settings: Specifies the DP AUX configuration 3 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg4-settings: Specifies the DP AUX configuration 4 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg5-settings: Specifies the DP AUX configuration 5 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg6-settings: Specifies the DP AUX configuration 6 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg7-settings: Specifies the DP AUX configuration 7 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg8-settings: Specifies the DP AUX configuration 8 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. - qcom,aux-cfg9-settings: Specifies the DP AUX configuration 9 settings. The first entry in this array corresponds to the register offset within DP AUX, while the remaining entries indicate the programmable values. Optional properties: - qcom,<type>-supply-entries: A node that lists the elements of the supply used by the Loading Loading @@ -87,7 +126,16 @@ Example: "core_aux_clk", "core_cfg_ahb_clk", "ctrl_link_clk", "ctrl_link_iface_clk", "ctrl_crypto_clk", "ctrl_pixel_clk"; qcom,aux-cfg-settings = [00 13 00 10 0a 26 0a 03 8b 03]; qcom,aux-cfg0-settings = [1c 00]; qcom,aux-cfg1-settings = [20 13 23 1d]; qcom,aux-cfg2-settings = [24 00]; qcom,aux-cfg3-settings = [28 00]; qcom,aux-cfg4-settings = [2c 0a]; qcom,aux-cfg5-settings = [30 26]; qcom,aux-cfg6-settings = [34 0a]; qcom,aux-cfg7-settings = [38 03]; qcom,aux-cfg8-settings = [3c bb]; qcom,aux-cfg9-settings = [40 03]; qcom,logical2physical-lane-map = [02 03 01 00]; qcom,phy-register-offset = <0x4>; qcom,max-pclk-frequency-khz = <593470>; Loading
arch/arm/boot/dts/qcom/msm8998-mdss.dtsi +10 −1 Original line number Diff line number Diff line Loading @@ -502,7 +502,16 @@ qcom,msm_ext_disp = <&msm_ext_disp>; qcom,aux-cfg-settings = [00 13 00 10 0a 26 0a 03 8b 03]; qcom,aux-cfg0-settings = [1c 00]; qcom,aux-cfg1-settings = [20 13 23 1d]; qcom,aux-cfg2-settings = [24 00]; qcom,aux-cfg3-settings = [28 00]; qcom,aux-cfg4-settings = [2c 0a]; qcom,aux-cfg5-settings = [30 26]; qcom,aux-cfg6-settings = [34 0a]; qcom,aux-cfg7-settings = [38 03]; qcom,aux-cfg8-settings = [3c bb]; qcom,aux-cfg9-settings = [40 03]; qcom,logical2physical-lane-map = [02 03 01 00]; qcom,core-supply-entries { Loading
arch/arm/boot/dts/qcom/sdm660-mdss.dtsi +10 −1 Original line number Diff line number Diff line Loading @@ -505,7 +505,16 @@ qcom,msm_ext_disp = <&msm_ext_disp>; qcom,aux-cfg-settings = [00 13 00 00 0a 28 0a 03 b7 03]; qcom,aux-cfg0-settings = [20 00]; qcom,aux-cfg1-settings = [24 13 23 1d]; qcom,aux-cfg2-settings = [28 00]; qcom,aux-cfg3-settings = [2c 00]; qcom,aux-cfg4-settings = [30 0a]; qcom,aux-cfg5-settings = [34 28]; qcom,aux-cfg6-settings = [38 0a]; qcom,aux-cfg7-settings = [3c 03]; qcom,aux-cfg8-settings = [40 b7]; qcom,aux-cfg9-settings = [44 03]; qcom,logical2physical-lane-map = [00 01 02 03]; qcom,phy-register-offset = <0x4>; qcom,max-pclk-frequency-khz = <300000>; Loading
drivers/video/fbdev/msm/mdss_dp.c +294 −81 Original line number Diff line number Diff line Loading @@ -69,6 +69,11 @@ static int mdss_dp_process_phy_test_pattern_request( static int mdss_dp_send_audio_notification( struct mdss_dp_drv_pdata *dp, int val); static inline void mdss_dp_reset_sink_count(struct mdss_dp_drv_pdata *dp) { memset(&dp->sink_count, 0, sizeof(dp->sink_count)); } static inline void mdss_dp_reset_test_data(struct mdss_dp_drv_pdata *dp) { dp->test_data = (const struct dpcd_test_request){ 0 }; Loading Loading @@ -133,22 +138,77 @@ static int mdss_dp_is_clk_prefix(const char *clk_prefix, const char *clk_name) return !strncmp(clk_name, clk_prefix, strlen(clk_prefix)); } static int mdss_dp_parse_prop(struct platform_device *pdev, struct mdss_dp_drv_pdata *dp_drv) static void mdss_dp_reset_phy_config_indices(struct mdss_dp_drv_pdata *dp) { int len = 0, i = 0, rc = 0; int i = 0; for (i = 0; i < PHY_AUX_CFG_MAX; i++) dp->aux_cfg[i].current_index = 0; } static void mdss_dp_phy_aux_cfg_reset(struct mdss_dp_drv_pdata *dp) { int i = 0; for (i = 0; i < PHY_AUX_CFG_MAX; i++) dp->aux_cfg[i] = (const struct mdss_dp_phy_cfg){ 0 }; } static int mdss_dp_parse_aux_cfg(struct platform_device *pdev, struct mdss_dp_drv_pdata *dp) { int len = 0, i = 0, j = 0, config_count = 0; const char *data; int const minimum_config_count = 1; data = of_get_property(pdev->dev.of_node, "qcom,aux-cfg-settings", &len); if ((!data) || (len != AUX_CFG_LEN)) { pr_err("%s:%d, Unable to read DP AUX CFG settings", __func__, __LINE__); for (i = 0; i < PHY_AUX_CFG_MAX; i++) { const char *property = mdss_dp_get_phy_aux_config_property(i); data = of_get_property(pdev->dev.of_node, property, &len); if (!data) { pr_err("Unable to read %s\n", property); goto error; } config_count = len - 1; if ((config_count < minimum_config_count) || (config_count > MDSS_DP_MAX_PHY_CFG_VALUE_CNT)) { pr_err("Invalid config count (%d) configs for %s\n", config_count, property); goto error; } dp->aux_cfg[i].offset = data[0]; dp->aux_cfg[i].cfg_cnt = config_count; pr_debug("%s offset=0x%x, cfg_cnt=%d\n", property, dp->aux_cfg[i].offset, dp->aux_cfg[i].cfg_cnt); for (j = 1; j < len; j++) { dp->aux_cfg[i].lut[j - 1] = data[j]; pr_debug("%s lut[%d]=0x%x\n", property, i, dp->aux_cfg[i].lut[j - 1]); } } return 0; error: mdss_dp_phy_aux_cfg_reset(dp); return -EINVAL; } for (i = 0; i < len; i++) dp_drv->aux_cfg[i] = data[i]; static int mdss_dp_parse_prop(struct platform_device *pdev, struct mdss_dp_drv_pdata *dp_drv) { int len = 0, i = 0, rc = 0; const char *data; rc = mdss_dp_parse_aux_cfg(pdev, dp_drv); if (rc) return rc; data = of_get_property(pdev->dev.of_node, "qcom,logical2physical-lane-map", &len); Loading Loading @@ -958,6 +1018,12 @@ void mdss_dp_config_ctrl(struct mdss_dp_drv_pdata *dp) mdss_dp_configuration_ctrl(&dp->ctrl_io, data); } static inline void mdss_dp_ack_state(struct mdss_dp_drv_pdata *dp, int val) { if (dp && dp->ext_audio_data.intf_ops.notify) dp->ext_audio_data.intf_ops.notify(dp->ext_pdev, val); } static int mdss_dp_wait4video_ready(struct mdss_dp_drv_pdata *dp_drv) { int ret = 0; Loading Loading @@ -1230,12 +1296,6 @@ static int dp_init_panel_info(struct mdss_dp_drv_pdata *dp_drv, u32 vic) return 0; } /* dp_init_panel_info */ static inline void mdss_dp_ack_state(struct mdss_dp_drv_pdata *dp, int val) { if (dp && dp->ext_audio_data.intf_ops.notify) dp->ext_audio_data.intf_ops.notify(dp->ext_pdev, val); } /** * mdss_dp_get_lane_mapping() - returns lane mapping based on given orientation * @orientation: usb plug orientation Loading Loading @@ -1621,15 +1681,19 @@ int mdss_dp_on(struct mdss_panel_data *pdata) dp_drv = container_of(pdata, struct mdss_dp_drv_pdata, panel_data); if (dp_drv->power_on) { /* * Acknowledge the connection event if link training has already * been done. This will unblock the external display thread and * allow the driver to progress. For example, in the case of * video test pattern requests, to send the test response and * start transmitting the test pattern. * If the link already active, then nothing needs to be done here. * However, it is possible that the the power_on flag could be * set to true but we would still need to initialize the DP host. * An example of this use-case is when a multiport dongle is connected * and subsequently the downstream sink is disconnected. This would * only go through the IRQ HPD path where we tear down the link but * the power_on flag remains set to true. When the downstream sink * is subsequently connected again, we need to re-initialize DP * host */ mdss_dp_ack_state(dp_drv, true); if (dp_drv->power_on && (dp_drv->new_vic && (dp_drv->new_vic == dp_drv->vic))) { pr_debug("Link already setup, return\n"); return 0; } Loading @@ -1647,6 +1711,23 @@ int mdss_dp_on(struct mdss_panel_data *pdata) return mdss_dp_on_hpd(dp_drv); } static bool mdss_dp_is_ds_bridge(struct mdss_dp_drv_pdata *dp) { return dp->dpcd.downstream_port.dfp_present; } static bool mdss_dp_is_ds_bridge_sink_count_zero(struct mdss_dp_drv_pdata *dp) { return (mdss_dp_is_ds_bridge(dp) && (dp->sink_count.count == 0)); } static bool mdss_dp_is_ds_bridge_no_local_edid(struct mdss_dp_drv_pdata *dp) { return (mdss_dp_is_ds_bridge_sink_count_zero(dp) && !(dp->dpcd.flags & DPCD_PORT_0_EDID_PRESENTED)); } static int mdss_dp_off_irq(struct mdss_dp_drv_pdata *dp_drv) { if (!dp_drv->power_on) { Loading @@ -1664,10 +1745,16 @@ static int mdss_dp_off_irq(struct mdss_dp_drv_pdata *dp_drv) /* Make sure DP mainlink and audio engines are disabled */ wmb(); mdss_dp_ack_state(dp_drv, false); /* * If downstream device is a brige which no longer has any * downstream devices connected to it, then we should reset * the current panel info */ if (mdss_dp_is_ds_bridge_sink_count_zero(dp_drv)) dp_init_panel_info(dp_drv, HDMI_VFRMT_UNKNOWN); mutex_unlock(&dp_drv->train_mutex); complete_all(&dp_drv->irq_comp); pr_debug("end\n"); return 0; Loading @@ -1694,11 +1781,11 @@ static int mdss_dp_off_hpd(struct mdss_dp_drv_pdata *dp_drv) mdss_dp_host_deinit(dp_drv); dp_drv->power_on = false; dp_drv->sink_info_read = false; dp_init_panel_info(dp_drv, HDMI_VFRMT_UNKNOWN); mdss_dp_ack_state(dp_drv, false); mdss_dp_reset_test_data(dp_drv); mdss_dp_reset_sink_count(dp_drv); dp_drv->prev_sink_count = dp_drv->sink_count; mutex_unlock(&dp_drv->train_mutex); pr_debug("DP off done\n"); Loading Loading @@ -1737,8 +1824,9 @@ static int mdss_dp_send_audio_notification( if (mdss_dp_sink_audio_supp(dp) || dp->audio_test_req) { dp->audio_test_req = false; pr_debug("sending audio notification\n"); flags |= MSM_EXT_DISP_HPD_AUDIO; pr_debug("sending audio notification = %d, flags = %d\n", val, flags); if (dp->ext_audio_data.intf_ops.hpd) ret = dp->ext_audio_data.intf_ops.hpd(dp->ext_pdev, Loading @@ -1763,7 +1851,8 @@ static int mdss_dp_send_video_notification( goto end; } flags |= MSM_EXT_DISP_HPD_VIDEO; flags |= MSM_EXT_DISP_HPD_ASYNC_VIDEO; pr_debug("sending video notification = %d, flags = %d\n", val, flags); if (dp->ext_audio_data.intf_ops.hpd) ret = dp->ext_audio_data.intf_ops.hpd(dp->ext_pdev, Loading Loading @@ -1873,14 +1962,16 @@ static int mdss_dp_host_init(struct mdss_panel_data *pdata) mdss_dp_ctrl_reset(&dp_drv->ctrl_io); mdss_dp_phy_reset(&dp_drv->ctrl_io); mdss_dp_aux_reset(&dp_drv->ctrl_io); mdss_dp_aux_set_limits(&dp_drv->ctrl_io); mdss_dp_aux_ctrl(&dp_drv->ctrl_io, true); pr_debug("Ctrl_hw_rev =0x%x, phy hw_rev =0x%x\n", mdss_dp_get_ctrl_hw_version(&dp_drv->ctrl_io), mdss_dp_get_phy_hw_version(&dp_drv->phy_io)); mdss_dp_phy_aux_setup(&dp_drv->phy_io, dp_drv->aux_cfg, dp_drv->phy_reg_offset); mdss_dp_reset_phy_config_indices(dp_drv); mdss_dp_phy_aux_setup(dp_drv); mdss_dp_irq_enable(dp_drv); dp_drv->dp_initialized = true; Loading Loading @@ -1948,10 +2039,12 @@ static int mdss_dp_host_deinit(struct mdss_dp_drv_pdata *dp) static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp, enum notification_status status) { const int irq_comp_timeout = HZ * 2; int ret = 0; bool notify = false; bool connect; mutex_lock(&dp->pd_msg_mutex); pr_debug("beginning notification\n"); if (status == dp->hpd_notification_status) { pr_debug("No change in status %s --> %s\n", mdss_dp_notification_status_to_string(status), Loading @@ -1964,39 +2057,40 @@ static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp, case NOTIFY_CONNECT_IRQ_HPD: if (dp->hpd_notification_status != NOTIFY_DISCONNECT_IRQ_HPD) goto invalid_request; /* Follow the same programming as for NOTIFY_CONNECT */ mdss_dp_host_init(&dp->panel_data); mdss_dp_send_video_notification(dp, true); notify = true; connect = true; break; case NOTIFY_CONNECT: if ((dp->hpd_notification_status == NOTIFY_CONNECT_IRQ_HPD) || (dp->hpd_notification_status == NOTIFY_DISCONNECT_IRQ_HPD)) if (dp->hpd_notification_status == NOTIFY_CONNECT_IRQ_HPD) goto invalid_request; mdss_dp_host_init(&dp->panel_data); mdss_dp_send_video_notification(dp, true); notify = true; connect = true; break; case NOTIFY_DISCONNECT: mdss_dp_send_audio_notification(dp, false); mdss_dp_send_video_notification(dp, false); /* * Userspace triggers a disconnect event on boot up, this must * not be processed as there was no previously connected sink * device. */ if (dp->hpd_notification_status == NOTIFY_UNKNOWN) goto invalid_request; if (dp->hpd_notification_status == NOTIFY_DISCONNECT_IRQ_HPD) { /* * user modules already turned off. Need to explicitly * turn off DP core here. */ mdss_dp_off_hpd(dp); } else { notify = true; connect = false; } break; case NOTIFY_DISCONNECT_IRQ_HPD: if (dp->hpd_notification_status == NOTIFY_DISCONNECT) goto invalid_request; mdss_dp_send_audio_notification(dp, false); mdss_dp_send_video_notification(dp, false); if (!IS_ERR_VALUE(ret) && ret) { reinit_completion(&dp->irq_comp); ret = wait_for_completion_timeout(&dp->irq_comp, irq_comp_timeout); if (ret <= 0) { pr_warn("irq_comp timed out\n"); ret = -EINVAL; } else { ret = 0; } } notify = true; connect = false; break; default: pr_err("Invalid notification status = %d\n", status); Loading @@ -2004,7 +2098,7 @@ static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp, break; } goto end; goto notify; invalid_request: pr_err("Invalid request %s --> %s\n", Loading @@ -2013,15 +2107,34 @@ invalid_request: mdss_dp_notification_status_to_string(status)); ret = -EINVAL; end: notify: if (ret || !notify) { pr_debug("not sending notification\n"); goto end; } atomic_set(&dp->notification_pending, 1); if (connect) { mdss_dp_host_init(&dp->panel_data); ret = mdss_dp_send_video_notification(dp, true); } else { mdss_dp_send_audio_notification(dp, false); ret = mdss_dp_send_video_notification(dp, false); } if (!ret) { pr_debug("Successfully sent notification %s --> %s\n", mdss_dp_notification_status_to_string( dp->hpd_notification_status), mdss_dp_notification_status_to_string(status)); dp->hpd_notification_status = status; } else { pr_err("%s Notification failed\n", mdss_dp_notification_status_to_string(status)); atomic_set(&dp->notification_pending, 0); } end: dp->hpd_notification_status = status; mutex_unlock(&dp->pd_msg_mutex); return ret; } Loading @@ -2031,9 +2144,6 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp) int ret; u32 max_pclk_khz; if (dp->sink_info_read) return 0; pr_debug("start\n"); ret = mdss_dp_dpcd_cap_read(dp); Loading @@ -2046,8 +2156,25 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp) */ pr_err("dpcd read failed, set failsafe parameters\n"); mdss_dp_set_default_link_parameters(dp); goto read_edid; } /* * When connected to a multiport adaptor which does not have a * local EDID present, do not attempt to read the EDID. * When connected to a multiport adaptor with no downstream device * connected to it, do not attempt to read the EDID. It is possible * that the adaptor may advertise the presence of local EDID, but it * is not guaranteed to work. */ if (mdss_dp_is_ds_bridge_sink_count_zero(dp)) { if (mdss_dp_is_ds_bridge_no_local_edid(dp)) pr_debug("No local EDID present on DS branch device\n"); pr_info("no downstream devices, skip client notification\n"); goto end; } read_edid: ret = mdss_dp_edid_read(dp); if (ret) { pr_err("edid read error, setting default resolution\n"); Loading @@ -2058,14 +2185,18 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp) hdmi_edid_set_max_pclk_rate(dp->panel_data.panel_info.edid_data, min(dp->max_pclk_khz, max_pclk_khz)); if (dp->dpcd_read_required) { pr_debug("reading DPCD with updated AUX config\n"); mdss_dp_dpcd_cap_read(dp); dp->dpcd_read_required = false; } ret = hdmi_edid_parser(dp->panel_data.panel_info.edid_data); if (ret) { pr_err("edid parse failed, setting default resolution\n"); goto notify; } dp->sink_info_read = true; notify: if (ret) { /* set failsafe parameters */ Loading @@ -2092,7 +2223,6 @@ notify: end: pr_debug("end\n"); return ret; } static int mdss_dp_check_params(struct mdss_dp_drv_pdata *dp, void *arg) Loading Loading @@ -2814,8 +2944,6 @@ static void mdss_dp_mainlink_push_idle(struct mdss_panel_data *pdata) /* wait until link training is completed */ mutex_lock(&dp_drv->train_mutex); mdss_dp_aux_set_sink_power_state(dp_drv, SINK_POWER_OFF); reinit_completion(&dp_drv->idle_comp); mdss_dp_state_ctrl(&dp_drv->ctrl_io, ST_PUSH_IDLE); if (!wait_for_completion_timeout(&dp_drv->idle_comp, Loading Loading @@ -2905,28 +3033,33 @@ static int mdss_dp_event_handler(struct mdss_panel_data *pdata, switch (event) { case MDSS_EVENT_UNBLANK: mdss_dp_ack_state(dp, true); rc = mdss_dp_on(pdata); break; case MDSS_EVENT_PANEL_ON: mdss_dp_update_hdcp_info(dp); if (dp_is_hdcp_enabled(dp)) { cancel_delayed_work(&dp->hdcp_cb_work); cancel_delayed_work_sync(&dp->hdcp_cb_work); dp->hdcp_status = HDCP_STATE_AUTHENTICATING; queue_delayed_work(dp->workq, &dp->hdcp_cb_work, HZ / 2); } break; case MDSS_EVENT_POST_PANEL_ON: atomic_set(&dp->notification_pending, 0); complete_all(&dp->notification_comp); break; case MDSS_EVENT_PANEL_OFF: rc = mdss_dp_off(pdata); atomic_set(&dp->notification_pending, 0); complete_all(&dp->notification_comp); break; case MDSS_EVENT_BLANK: if (dp_is_hdcp_enabled(dp)) { dp->hdcp_status = HDCP_STATE_INACTIVE; cancel_delayed_work(&dp->hdcp_cb_work); cancel_delayed_work_sync(&dp->hdcp_cb_work); if (dp->hdcp.ops->off) dp->hdcp.ops->off(dp->hdcp.data); } Loading Loading @@ -3083,6 +3216,7 @@ static int mdss_retrieve_dp_ctrl_resources(struct platform_device *pdev, static void mdss_dp_video_ready(struct mdss_dp_drv_pdata *dp) { pr_debug("dp_video_ready\n"); mdss_dp_ack_state(dp, true); complete(&dp->video_comp); } Loading Loading @@ -3209,6 +3343,7 @@ irqreturn_t dp_isr(int irq, void *ptr) spin_lock(&dp->lock); isr1 = dp_read(base + DP_INTR_STATUS); isr2 = dp_read(base + DP_INTR_STATUS2); pr_debug("isr1=0x%08x, isr2=0x%08x\n", isr1, isr2); mask1 = isr1 & dp->mask1; Loading Loading @@ -3425,6 +3560,17 @@ static inline void mdss_dp_link_maintenance(struct mdss_dp_drv_pdata *dp, if (mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT_IRQ_HPD)) return; if (atomic_read(&dp->notification_pending)) { int ret; pr_debug("waiting for the disconnect to finish\n"); ret = wait_for_completion_timeout(&dp->notification_comp, HZ); if (ret <= 0) { pr_warn("NOTIFY_DISCONNECT_IRQ_HPD timed out\n"); return; } } mdss_dp_on_irq(dp, lt_needed); } Loading Loading @@ -3576,7 +3722,7 @@ static int mdss_dp_process_audio_pattern_request(struct mdss_dp_drv_pdata *dp) return -EINVAL; if (dp_is_hdcp_enabled(dp) && dp->hdcp.ops->off) { cancel_delayed_work(&dp->hdcp_cb_work); cancel_delayed_work_sync(&dp->hdcp_cb_work); dp->hdcp.ops->off(dp->hdcp.data); } Loading Loading @@ -3622,10 +3768,46 @@ static int mdss_dp_process_audio_pattern_request(struct mdss_dp_drv_pdata *dp) static int mdss_dp_process_downstream_port_status_change( struct mdss_dp_drv_pdata *dp) { if (!mdss_dp_is_downstream_port_status_changed(dp)) bool ds_status_changed = false; if (mdss_dp_is_downstream_port_status_changed(dp)) { pr_debug("downstream port status changed\n"); ds_status_changed = true; } /* * Ideally sink should update the downstream port status changed * whenever it updates the downstream sink count. However, it is * possible that only the sink count is updated without setting * the downstream port status changed bit. */ if (dp->sink_count.count != dp->prev_sink_count.count) { pr_debug("downstream sink count changed from %d --> %d\n", dp->prev_sink_count.count, dp->sink_count.count); ds_status_changed = true; } if (!ds_status_changed) return -EINVAL; return mdss_dp_edid_read(dp); mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT_IRQ_HPD); if (atomic_read(&dp->notification_pending)) { int ret; pr_debug("waiting for the disconnect to finish\n"); ret = wait_for_completion_timeout(&dp->notification_comp, HZ); if (ret <= 0) { pr_warn("NOTIFY_DISCONNECT_IRQ_HPD timed out\n"); return -ETIMEDOUT; } } if (mdss_dp_is_ds_bridge_sink_count_zero(dp)) { pr_debug("sink count is zero, nothing to do\n"); return 0; } return mdss_dp_process_hpd_high(dp); } static bool mdss_dp_video_pattern_test_lt_needed(struct mdss_dp_drv_pdata *dp) Loading Loading @@ -3723,19 +3905,19 @@ static int mdss_dp_process_hpd_irq_high(struct mdss_dp_drv_pdata *dp) mdss_dp_aux_parse_sink_status_field(dp); ret = mdss_dp_process_link_training_request(dp); ret = mdss_dp_process_downstream_port_status_change(dp); if (!ret) goto exit; ret = mdss_dp_process_phy_test_pattern_request(dp); ret = mdss_dp_process_link_training_request(dp); if (!ret) goto exit; ret = mdss_dp_process_link_status_update(dp); ret = mdss_dp_process_phy_test_pattern_request(dp); if (!ret) goto exit; ret = mdss_dp_process_downstream_port_status_change(dp); ret = mdss_dp_process_link_status_update(dp); if (!ret) goto exit; Loading @@ -3748,7 +3930,6 @@ static int mdss_dp_process_hpd_irq_high(struct mdss_dp_drv_pdata *dp) goto exit; pr_debug("done\n"); exit: dp->hpd_irq_on = false; return ret; Loading Loading @@ -3842,11 +4023,21 @@ static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv) if (!dp_drv->alt_mode.dp_status.hpd_high) { pr_debug("Attention: HPD low\n"); if (!dp_drv->power_on) { pr_debug("HPD already low\n"); return; } if (dp_is_hdcp_enabled(dp_drv) && dp_drv->hdcp.ops->off) { cancel_delayed_work(&dp_drv->hdcp_cb_work); cancel_delayed_work_sync(&dp_drv->hdcp_cb_work); dp_drv->hdcp.ops->off(dp_drv->hdcp.data); } /* * Reset the sink count before nofifying clients since HPD Low * indicates that the downstream device has been disconnected. */ mdss_dp_reset_sink_count(dp_drv); mdss_dp_notify_clients(dp_drv, NOTIFY_DISCONNECT); pr_debug("Attention: Notified clients\n"); Loading Loading @@ -3874,6 +4065,11 @@ static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv) pr_debug("Attention: HPD high\n"); if (dp_drv->power_on) { pr_debug("HPD high processed already\n"); return; } dp_drv->alt_mode.current_state |= DP_STATUS_DONE; if (dp_drv->alt_mode.current_state & DP_CONFIGURE_DONE) { Loading @@ -3895,6 +4091,7 @@ static void mdss_dp_handle_attention(struct mdss_dp_drv_pdata *dp) pr_debug("processing item %d in the list\n", ++i); reinit_completion(&dp->notification_comp); mutex_lock(&dp->attention_lock); node = list_first_entry(&dp->attention_head, struct mdss_dp_attention_node, list); Loading @@ -3909,6 +4106,21 @@ static void mdss_dp_handle_attention(struct mdss_dp_drv_pdata *dp) mdss_dp_usbpd_ext_dp_status(&dp->alt_mode.dp_status); mdss_dp_process_attention(dp); if (atomic_read(&dp->notification_pending)) { pr_debug("waiting for the attention event to finish\n"); /* * This wait is intentionally implemented without a * timeout since this is happens only in possible error * conditions e.g. if the display framework does not * power off/on the DisplayPort device in time. Other * events might already be queued from the sink at this * point and they cannot be processed until the power * off/on is complete otherwise we might have problems * with interleaving of these events e.g. un-clocked * register access. */ wait_for_completion(&dp->notification_comp); } pr_debug("done processing item %d in the list\n", i); }; Loading Loading @@ -4080,8 +4292,9 @@ static int mdss_dp_probe(struct platform_device *pdev) dp_drv->inited = true; dp_drv->hpd_irq_on = false; atomic_set(&dp_drv->notification_pending, 0); mdss_dp_reset_test_data(dp_drv); init_completion(&dp_drv->irq_comp); init_completion(&dp_drv->notification_comp); dp_drv->suspend_vic = HDMI_VFRMT_UNKNOWN; pr_debug("done\n"); Loading