diff --git a/drivers/misc/hdcp.c b/drivers/misc/hdcp.c index efb987b4a6b6..bd21f8cca2aa 100644 --- a/drivers/misc/hdcp.c +++ b/drivers/misc/hdcp.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -559,6 +559,7 @@ static int hdcp_lib_txmtr_init_legacy(struct hdcp_lib_handle *handle); static struct qseecom_handle *hdcp1_handle; static bool hdcp1_supported = true; static bool hdcp1_enc_enabled; +static struct mutex hdcp1_ta_cmd_lock; static const char *hdcp_lib_message_name(int msg_id) { @@ -805,8 +806,8 @@ static int hdcp_lib_get_version(struct hdcp_lib_handle *handle) goto exit; } - if (handle->hdcp_state & HDCP_STATE_APP_LOADED) { - pr_err("library already loaded\n"); + if (!(handle->hdcp_state & HDCP_STATE_APP_LOADED)) { + pr_err("library not loaded\n"); return rc; } @@ -901,8 +902,8 @@ static int hdcp_app_init_legacy(struct hdcp_lib_handle *handle) goto exit; } - if (handle->hdcp_state & HDCP_STATE_APP_LOADED) { - pr_err("library already loaded\n"); + if (!(handle->hdcp_state & HDCP_STATE_APP_LOADED)) { + pr_err("library not loaded\n"); goto exit; } @@ -949,8 +950,8 @@ static int hdcp_app_init(struct hdcp_lib_handle *handle) goto exit; } - if (handle->hdcp_state & HDCP_STATE_APP_LOADED) { - pr_err("library already loaded\n"); + if (!(handle->hdcp_state & HDCP_STATE_APP_LOADED)) { + pr_err("library not loaded\n"); goto exit; } @@ -1024,6 +1025,7 @@ static int hdcp_lib_library_load(struct hdcp_lib_handle *handle) goto exit; } + handle->hdcp_state |= HDCP_STATE_APP_LOADED; pr_debug("qseecom_start_app success\n"); rc = hdcp_lib_get_version(handle); @@ -1050,8 +1052,6 @@ static int hdcp_lib_library_load(struct hdcp_lib_handle *handle) pr_err("app init failed\n"); goto exit; } - - handle->hdcp_state |= HDCP_STATE_APP_LOADED; exit: return rc; } @@ -1240,8 +1240,8 @@ static int hdcp_lib_txmtr_init(struct hdcp_lib_handle *handle) goto exit; } - if (handle->hdcp_state & HDCP_STATE_TXMTR_INIT) { - pr_err("txmtr already initialized\n"); + if (!(handle->hdcp_state & HDCP_STATE_APP_LOADED)) { + pr_err("library not loaded\n"); goto exit; } @@ -1622,6 +1622,12 @@ static int hdcp_lib_check_valid_state(struct hdcp_lib_handle *handle) rc = -EBUSY; goto exit; } + + if (handle->hdcp_state & HDCP_STATE_APP_LOADED) { + pr_debug("library already loaded\n"); + rc = -EBUSY; + goto exit; + } } else { if (atomic_read(&handle->hdcp_off)) { pr_debug("hdcp2.2 session tearing down\n"); @@ -2212,6 +2218,8 @@ bool hdcp1_check_if_supported_load_app(void) if (rc) { pr_err("qseecom_start_app failed %d\n", rc); hdcp1_supported = false; + } else { + mutex_init(&hdcp1_ta_cmd_lock); } } @@ -2276,12 +2284,16 @@ int hdcp1_set_enc(bool enable) struct hdcp1_set_enc_req *set_enc_req; struct hdcp1_set_enc_rsp *set_enc_rsp; - if (!hdcp1_supported || !hdcp1_handle) - return -EINVAL; + mutex_lock(&hdcp1_ta_cmd_lock); + + if (!hdcp1_supported || !hdcp1_handle) { + rc = -EINVAL; + goto end; + } if (hdcp1_enc_enabled == enable) { pr_debug("already %s\n", enable ? "enabled" : "disabled"); - return rc; + goto end; } /* set keys and request aksv */ @@ -2299,18 +2311,21 @@ int hdcp1_set_enc(bool enable) if (rc < 0) { pr_err("qseecom cmd failed err=%d\n", rc); - return -EINVAL; + goto end; } rc = set_enc_rsp->ret; if (rc) { pr_err("enc cmd failed, rsp=%d\n", set_enc_rsp->ret); - return -EINVAL; + rc = -EINVAL; + goto end; } hdcp1_enc_enabled = enable; pr_debug("%s success\n", enable ? "enable" : "disable"); - return 0; +end: + mutex_unlock(&hdcp1_ta_cmd_lock); + return rc; } int hdcp_library_register(struct hdcp_register_data *data) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index ccf8a0d34a6c..f3c36c5c6b5a 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016 The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -56,9 +56,58 @@ struct mdss_dp_attention_node { static int mdss_dp_off_irq(struct mdss_dp_drv_pdata *dp_drv); static void mdss_dp_mainlink_push_idle(struct mdss_panel_data *pdata); -static inline void mdss_dp_link_retraining(struct mdss_dp_drv_pdata *dp); +static inline void mdss_dp_link_maintenance(struct mdss_dp_drv_pdata *dp, + bool lt_needed); static void mdss_dp_handle_attention(struct mdss_dp_drv_pdata *dp_drv); static void dp_send_events(struct mdss_dp_drv_pdata *dp, u32 events); +static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp, + enum notification_status status); +static int mdss_dp_process_phy_test_pattern_request( + struct mdss_dp_drv_pdata *dp); + +static inline void mdss_dp_reset_test_data(struct mdss_dp_drv_pdata *dp) +{ + dp->test_data = (const struct dpcd_test_request){ 0 }; + dp->test_data.test_bit_depth = DP_TEST_BIT_DEPTH_UNKNOWN; + hdmi_edid_config_override(dp->panel_data.panel_info.edid_data, + false, 0); +} + +static inline bool mdss_dp_is_link_status_updated(struct mdss_dp_drv_pdata *dp) +{ + return dp->link_status.link_status_updated; +} + +static inline bool mdss_dp_is_downstream_port_status_changed( + struct mdss_dp_drv_pdata *dp) +{ + return dp->link_status.downstream_port_status_changed; +} + +static inline bool mdss_dp_is_audio_pattern_requested( + struct mdss_dp_drv_pdata *dp) +{ + return (dp->test_data.test_requested & TEST_AUDIO_PATTERN); +} + +static inline bool mdss_dp_is_link_training_requested( + struct mdss_dp_drv_pdata *dp) +{ + return (dp->test_data.test_requested == TEST_LINK_TRAINING); +} + +static inline bool mdss_dp_is_video_pattern_requested( + struct mdss_dp_drv_pdata *dp) +{ + return (dp->test_data.test_requested & TEST_VIDEO_PATTERN) + && !(dp->test_data.test_requested & TEST_AUDIO_DISABLED_VIDEO); +} + +static inline bool mdss_dp_is_phy_test_pattern_requested( + struct mdss_dp_drv_pdata *dp) +{ + return (dp->test_data.test_requested == PHY_TEST_PATTERN); +} static void mdss_dp_put_dt_clk_data(struct device *dev, struct dss_module_power *module_power) @@ -798,9 +847,11 @@ void mdss_dp_config_ctrl(struct mdss_dp_drv_pdata *dp) { struct dpcd_cap *cap; struct display_timing_desc *timing; + struct mdss_panel_info *pinfo; u32 data = 0; timing = &dp->edid.timing[0]; + pinfo = &dp->panel_data.panel_info; cap = &dp->dpcd; @@ -823,8 +874,8 @@ void mdss_dp_config_ctrl(struct mdss_dp_drv_pdata *dp) if (cap->scrambler_reset) data |= (1 << 10); - if (dp->edid.color_depth != 6) - data |= 0x100; /* Default: 8 bits */ + /* Bits per components */ + data |= (mdss_dp_bpp_to_test_bit_depth(pinfo->bpp) << 8); /* Num of Lanes */ data |= ((dp->lane_cnt - 1) << 4); @@ -840,7 +891,7 @@ void mdss_dp_config_ctrl(struct mdss_dp_drv_pdata *dp) mdss_dp_configuration_ctrl(&dp->ctrl_io, data); } -int mdss_dp_wait4train(struct mdss_dp_drv_pdata *dp_drv) +static int mdss_dp_wait4video_ready(struct mdss_dp_drv_pdata *dp_drv) { int ret = 0; @@ -882,9 +933,7 @@ static int dp_get_cable_status(struct platform_device *pdev, u32 vote) return -ENODEV; } - mutex_lock(&dp_ctrl->pd_msg_mutex); hpd = dp_ctrl->cable_connected; - mutex_unlock(&dp_ctrl->pd_msg_mutex); return hpd; } @@ -905,7 +954,7 @@ static int dp_audio_info_setup(struct platform_device *pdev, return -ENODEV; } - mdss_dp_audio_setup_sdps(&dp_ctrl->ctrl_io); + mdss_dp_audio_setup_sdps(&dp_ctrl->ctrl_io, params->num_of_channels); mdss_dp_config_audio_acr_ctrl(&dp_ctrl->ctrl_io, dp_ctrl->link_rate); mdss_dp_set_safe_to_exit_level(&dp_ctrl->ctrl_io, dp_ctrl->lane_cnt); mdss_dp_audio_enable(&dp_ctrl->ctrl_io, true); @@ -983,6 +1032,55 @@ end: return ret; } +static u32 mdss_dp_get_bpp(struct mdss_dp_drv_pdata *dp) +{ + u32 bpp; + u32 bit_depth; + + /* + * Set bpp value based on whether a test video pattern is requested. + * For test pattern, the test data has the bit depth per color + * component. Otherwise, set it based on EDID. + */ + if (dp->override_config || mdss_dp_is_video_pattern_requested(dp)) + bit_depth = dp->test_data.test_bit_depth; + else + bit_depth = dp->edid.color_depth; + + if (!mdss_dp_is_test_bit_depth_valid(bit_depth)) { + pr_debug("invalid bit_depth=%d. fall back to default\n", + bit_depth); + bit_depth = DP_TEST_BIT_DEPTH_8; /* default to 24bpp */ + } + + bpp = mdss_dp_test_bit_depth_to_bpp(bit_depth); + return bpp; +} + +static u32 mdss_dp_get_colorimetry_config(struct mdss_dp_drv_pdata *dp) +{ + u32 cc; + enum dynamic_range dr; + + /* unless a video pattern CTS test is ongoing, use CEA_VESA */ + if (mdss_dp_is_video_pattern_requested(dp)) + dr = dp->test_data.test_dyn_range; + else + dr = DP_DYNAMIC_RANGE_RGB_VESA; + + /* Only RGB_VESA nd RGB_CEA supported for now */ + switch (dr) { + case DP_DYNAMIC_RANGE_RGB_CEA: + cc = BIT(3); + break; + case DP_DYNAMIC_RANGE_RGB_VESA: + default: + cc = 0; + } + + return cc; +} + static int dp_init_panel_info(struct mdss_dp_drv_pdata *dp_drv, u32 vic) { struct mdss_panel_info *pinfo; @@ -993,13 +1091,17 @@ static int dp_init_panel_info(struct mdss_dp_drv_pdata *dp_drv, u32 vic) DEV_ERR("invalid input\n"); return -EINVAL; } - - ret = hdmi_get_supported_mode(&timing, 0, vic); pinfo = &dp_drv->panel_data.panel_info; - if (ret || !timing.supported || !pinfo) { - DEV_ERR("%s: invalid timing data\n", __func__); - return -EINVAL; + if (vic != HDMI_VFRMT_UNKNOWN) { + ret = hdmi_get_supported_mode(&timing, 0, vic); + + if (ret || !timing.supported || !pinfo) { + DEV_ERR("%s: invalid timing data\n", __func__); + return -EINVAL; + } + } else { + pr_debug("reset panel info to zeroes\n"); } dp_drv->vic = vic; @@ -1010,14 +1112,15 @@ static int dp_init_panel_info(struct mdss_dp_drv_pdata *dp_drv, u32 vic) pinfo->lcdc.h_back_porch = timing.back_porch_h; pinfo->lcdc.h_front_porch = timing.front_porch_h; pinfo->lcdc.h_pulse_width = timing.pulse_width_h; + pinfo->lcdc.h_active_low = timing.active_low_h; pinfo->lcdc.v_back_porch = timing.back_porch_v; pinfo->lcdc.v_front_porch = timing.front_porch_v; pinfo->lcdc.v_pulse_width = timing.pulse_width_v; + pinfo->lcdc.v_active_low = timing.active_low_v; pinfo->type = DP_PANEL; pinfo->pdest = DISPLAY_4; pinfo->wait_cycle = 0; - pinfo->bpp = 24; pinfo->fb_num = 1; pinfo->lcdc.border_clr = 0; /* blk */ @@ -1025,8 +1128,8 @@ static int dp_init_panel_info(struct mdss_dp_drv_pdata *dp_drv, u32 vic) pinfo->lcdc.hsync_skew = 0; pinfo->is_pluggable = true; - dp_drv->bpp = pinfo->bpp; - + pinfo->bpp = mdss_dp_get_bpp(dp_drv); + pr_debug("bpp=%d\n", pinfo->bpp); pr_debug("update res. vic= %d, pclk_rate = %llu\n", dp_drv->vic, pinfo->clk_rate); @@ -1151,13 +1254,17 @@ static void mdss_dp_configure_source_params(struct mdss_dp_drv_pdata *dp, mdss_dp_fill_link_cfg(dp); mdss_dp_mainlink_ctrl(&dp->ctrl_io, true); mdss_dp_config_ctrl(dp); + mdss_dp_config_misc(dp, + mdss_dp_bpp_to_test_bit_depth(mdss_dp_get_bpp(dp)), + mdss_dp_get_colorimetry_config(dp)); mdss_dp_sw_config_msa(&dp->ctrl_io, dp->link_rate, &dp->dp_cc_io); mdss_dp_timing_cfg(&dp->ctrl_io, &dp->panel_data.panel_info); } /** - * mdss_dp_train_main_link() - initiates training of DP main link + * mdss_dp_setup_main_link() - initiates training of DP main link * @dp: Display Port Driver data + * @train: specify if link training should be done or not * * Initiates training of the DP main link and checks the state of the main * link after the training is complete. @@ -1165,64 +1272,90 @@ static void mdss_dp_configure_source_params(struct mdss_dp_drv_pdata *dp, * Return: error code. -EINVAL if any invalid data or -EAGAIN if retraining * is required. */ -static int mdss_dp_train_main_link(struct mdss_dp_drv_pdata *dp) +static int mdss_dp_setup_main_link(struct mdss_dp_drv_pdata *dp, bool train) { int ret = 0; int ready = 0; pr_debug("enter\n"); + mdss_dp_mainlink_ctrl(&dp->ctrl_io, true); + mdss_dp_aux_set_sink_power_state(dp, SINK_POWER_ON); + reinit_completion(&dp->video_comp); + + if (mdss_dp_is_phy_test_pattern_requested(dp)) + goto end; + + if (!train) + goto send_video; + + /* + * As part of previous calls, DP controller state might have + * transitioned to PUSH_IDLE. In order to start transmitting a link + * training pattern, we have to first to a DP software reset. + */ + mdss_dp_ctrl_reset(&dp->ctrl_io); ret = mdss_dp_link_train(dp); if (ret) goto end; - mdss_dp_wait4train(dp); +send_video: + /* + * Set up transfer unit values and set controller state to send + * video. + */ + mdss_dp_setup_tr_unit(&dp->ctrl_io, dp->link_rate, dp->lane_cnt, + dp->vic, &dp->panel_data.panel_info); + mdss_dp_state_ctrl(&dp->ctrl_io, ST_SEND_VIDEO); + mdss_dp_wait4video_ready(dp); ready = mdss_dp_mainlink_ready(dp, BIT(0)); - pr_debug("main link %s\n", ready ? "READY" : "NOT READY"); + end: return ret; } -static int mdss_dp_on_irq(struct mdss_dp_drv_pdata *dp_drv) +static int mdss_dp_on_irq(struct mdss_dp_drv_pdata *dp_drv, bool lt_needed) { int ret = 0; struct lane_mapping ln_map; /* wait until link training is completed */ - pr_debug("enter\n"); + pr_debug("enter, lt_needed=%s\n", lt_needed ? "true" : "false"); do { - if (ret == -EAGAIN) { - mdss_dp_mainlink_push_idle(&dp_drv->panel_data); - mdss_dp_off_irq(dp_drv); - } + if (ret == -EAGAIN) + mdss_dp_mainlink_ctrl(&dp_drv->ctrl_io, false); mutex_lock(&dp_drv->train_mutex); + dp_init_panel_info(dp_drv, dp_drv->vic); ret = mdss_dp_get_lane_mapping(dp_drv, dp_drv->orientation, &ln_map); - if (ret) { - mutex_unlock(&dp_drv->train_mutex); - goto exit; - } + if (ret) + goto exit_loop; mdss_dp_phy_share_lane_config(&dp_drv->phy_io, dp_drv->orientation, dp_drv->dpcd.max_lane_count); - ret = mdss_dp_enable_mainlink_clocks(dp_drv); - if (ret) { - mutex_unlock(&dp_drv->train_mutex); - goto exit; + if (lt_needed) { + /* + * Diasable and re-enable the mainlink clock since the + * link clock might have been adjusted as part of the + * link maintenance. + */ + if (!mdss_dp_is_phy_test_pattern_requested(dp_drv)) + mdss_dp_disable_mainlink_clocks(dp_drv); + ret = mdss_dp_enable_mainlink_clocks(dp_drv); + if (ret) + goto exit_loop; } - mdss_dp_mainlink_reset(&dp_drv->ctrl_io); + mdss_dp_configure_source_params(dp_drv, &ln_map); reinit_completion(&dp_drv->idle_comp); - mdss_dp_configure_source_params(dp_drv, &ln_map); - dp_drv->power_on = true; if (dp_drv->psm_enabled) { @@ -1230,18 +1363,22 @@ static int mdss_dp_on_irq(struct mdss_dp_drv_pdata *dp_drv) if (ret) { pr_err("Failed to exit low power mode, rc=%d\n", ret); - goto exit; + goto exit_loop; } } - ret = mdss_dp_train_main_link(dp_drv); + ret = mdss_dp_setup_main_link(dp_drv, lt_needed); +exit_loop: mutex_unlock(&dp_drv->train_mutex); } while (ret == -EAGAIN); pr_debug("end\n"); -exit: + /* Send a connect notification */ + if (!mdss_dp_is_phy_test_pattern_requested(dp_drv)) + mdss_dp_notify_clients(dp_drv, NOTIFY_CONNECT_IRQ_HPD); + return ret; } @@ -1274,12 +1411,7 @@ int mdss_dp_on_hpd(struct mdss_dp_drv_pdata *dp_drv) if (dp_drv->new_vic && (dp_drv->new_vic != dp_drv->vic)) dp_init_panel_info(dp_drv, dp_drv->new_vic); - dp_drv->link_rate = - mdss_dp_gen_link_clk(&dp_drv->panel_data.panel_info, - dp_drv->dpcd.max_lane_count); - - pr_debug("link_rate=0x%x, Max rate supported by sink=0x%x\n", - dp_drv->link_rate, dp_drv->dpcd.max_link_rate); + dp_drv->link_rate = mdss_dp_gen_link_clk(dp_drv); if (!dp_drv->link_rate) { pr_err("Unable to configure required link rate\n"); ret = -EINVAL; @@ -1289,14 +1421,10 @@ int mdss_dp_on_hpd(struct mdss_dp_drv_pdata *dp_drv) mdss_dp_phy_share_lane_config(&dp_drv->phy_io, dp_drv->orientation, dp_drv->dpcd.max_lane_count); - pr_debug("link_rate = 0x%x\n", dp_drv->link_rate); - ret = mdss_dp_enable_mainlink_clocks(dp_drv); if (ret) goto exit; - mdss_dp_mainlink_reset(&dp_drv->ctrl_io); - reinit_completion(&dp_drv->idle_comp); mdss_dp_configure_source_params(dp_drv, &ln_map); @@ -1313,7 +1441,7 @@ int mdss_dp_on_hpd(struct mdss_dp_drv_pdata *dp_drv) link_training: dp_drv->power_on = true; - while (-EAGAIN == mdss_dp_train_main_link(dp_drv)) + while (-EAGAIN == mdss_dp_setup_main_link(dp_drv, true)) pr_debug("MAIN LINK TRAINING RETRY\n"); dp_drv->cont_splash = 0; @@ -1338,44 +1466,14 @@ 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) { + pr_debug("Link already setup, return\n"); + return 0; + } + return mdss_dp_on_hpd(dp_drv); } -static inline void mdss_dp_reset_test_data(struct mdss_dp_drv_pdata *dp) -{ - dp->test_data = (const struct dpcd_test_request){ 0 }; -} - -static inline bool mdss_dp_is_link_status_updated(struct mdss_dp_drv_pdata *dp) -{ - return dp->link_status.link_status_updated; -} - -static inline bool mdss_dp_is_downstream_port_status_changed( - struct mdss_dp_drv_pdata *dp) -{ - return dp->link_status.downstream_port_status_changed; -} - -static inline bool mdss_dp_is_link_training_requested( - struct mdss_dp_drv_pdata *dp) -{ - return (dp->test_data.test_requested == TEST_LINK_TRAINING); -} - -static inline bool mdss_dp_is_phy_test_pattern_requested( - struct mdss_dp_drv_pdata *dp) -{ - return (dp->test_data.test_requested == PHY_TEST_PATTERN); -} - -static inline bool mdss_dp_soft_hpd_reset(struct mdss_dp_drv_pdata *dp) -{ - return (mdss_dp_is_link_training_requested(dp) || - mdss_dp_is_phy_test_pattern_requested(dp)) && - dp->alt_mode.dp_status.hpd_irq; -} - static int mdss_dp_off_irq(struct mdss_dp_drv_pdata *dp_drv) { if (!dp_drv->power_on) { @@ -1389,16 +1487,13 @@ static int mdss_dp_off_irq(struct mdss_dp_drv_pdata *dp_drv) pr_debug("start\n"); mdss_dp_mainlink_ctrl(&dp_drv->ctrl_io, false); - mdss_dp_audio_enable(&dp_drv->ctrl_io, false); - - /* Make sure the DP main link is disabled before clk disable */ + /* Make sure DP mainlink and audio engines are disabled */ wmb(); - mdss_dp_disable_mainlink_clocks(dp_drv); - dp_drv->power_on = false; - dp_drv->sink_info_read = false; + mdss_dp_ack_state(dp_drv, false); mutex_unlock(&dp_drv->train_mutex); + complete_all(&dp_drv->irq_comp); pr_debug("end\n"); @@ -1445,8 +1540,10 @@ static int mdss_dp_off_hpd(struct mdss_dp_drv_pdata *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); mutex_unlock(&dp_drv->train_mutex); pr_debug("DP off done\n"); @@ -1464,7 +1561,7 @@ int mdss_dp_off(struct mdss_panel_data *pdata) return -EINVAL; } - if (mdss_dp_soft_hpd_reset(dp)) + if (dp->hpd_irq_on) return mdss_dp_off_irq(dp); else return mdss_dp_off_hpd(dp); @@ -1482,8 +1579,13 @@ static int mdss_dp_send_cable_notification( goto end; } - if (mdss_dp_is_dvi_mode(dp)) - flags |= MSM_EXT_DISP_HPD_NO_AUDIO; + flags |= MSM_EXT_DISP_HPD_VIDEO; + + if (!mdss_dp_is_dvi_mode(dp) || dp->audio_test_req) { + dp->audio_test_req = false; + + flags |= MSM_EXT_DISP_HPD_AUDIO; + } if (dp->ext_audio_data.intf_ops.hpd) ret = dp->ext_audio_data.intf_ops.hpd(dp->ext_pdev, @@ -1493,11 +1595,6 @@ end: return ret; } -static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp, bool enable) -{ - return mdss_dp_send_cable_notification(dp, enable); -} - static void mdss_dp_set_default_resolution(struct mdss_dp_drv_pdata *dp) { hdmi_edid_set_video_resolution(dp->panel_data.panel_info.edid_data, @@ -1606,6 +1703,93 @@ vreg_error: return ret; } +/** + * mdss_dp_notify_clients() - notifies DP clients of cable connection + * @dp: Display Port Driver data + * @status: HPD notification status requested + * + * This function will send a notification to display/audio clients of change + * in DP connection status. + */ +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; + + mutex_lock(&dp->pd_msg_mutex); + if (status == dp->hpd_notification_status) { + pr_debug("No change in status %s --> %s\n", + mdss_dp_notification_status_to_string(status), + mdss_dp_notification_status_to_string( + dp->hpd_notification_status)); + goto end; + } + + switch (status) { + 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_cable_notification(dp, true); + break; + case NOTIFY_CONNECT: + if ((dp->hpd_notification_status == NOTIFY_CONNECT_IRQ_HPD) || + (dp->hpd_notification_status == + NOTIFY_DISCONNECT_IRQ_HPD)) + goto invalid_request; + mdss_dp_host_init(&dp->panel_data); + mdss_dp_send_cable_notification(dp, true); + break; + case NOTIFY_DISCONNECT: + mdss_dp_send_cable_notification(dp, false); + break; + case NOTIFY_DISCONNECT_IRQ_HPD: + if (dp->hpd_notification_status == NOTIFY_DISCONNECT) + goto invalid_request; + + mdss_dp_send_cable_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; + } + } + break; + default: + pr_err("Invalid notification status = %d\n", status); + ret = -EINVAL; + break; + } + + goto end; + +invalid_request: + pr_err("Invalid request %s --> %s\n", + mdss_dp_notification_status_to_string( + dp->hpd_notification_status), + mdss_dp_notification_status_to_string(status)); + ret = -EINVAL; + +end: + 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; + } + + mutex_unlock(&dp->pd_msg_mutex); + return ret; +} + static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp) { int ret; @@ -1613,6 +1797,8 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp) if (dp->sink_info_read) return 0; + pr_debug("start\n"); + mdss_dp_dpcd_cap_read(dp); ret = mdss_dp_edid_read(dp); @@ -1620,20 +1806,35 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp) pr_debug("edid read error, setting default resolution\n"); mdss_dp_set_default_resolution(dp); - goto end; + goto notify; } ret = hdmi_edid_parser(dp->panel_data.panel_info.edid_data); if (ret) { pr_err("edid parse failed\n"); - goto end; + goto notify; } dp->sink_info_read = true; -end: - mdss_dp_update_cable_status(dp, true); - mdss_dp_notify_clients(dp, true); +notify: + /* Check if there is a PHY_TEST_PATTERN request when we get HPD high. + * Update the DP driver with the test parameters including link rate, + * lane count, voltage level, and pre-emphasis level. Do not notify + * the userspace of the connection, just power on the DP controller + * and mainlink with the new settings. + */ + if (mdss_dp_is_phy_test_pattern_requested(dp)) { + pr_info("PHY_TEST_PATTERN requested by sink\n"); + mdss_dp_process_phy_test_pattern_request(dp); + pr_info("skip client notification\n"); + goto end; + } + + mdss_dp_notify_clients(dp, NOTIFY_CONNECT); + +end: + pr_debug("end\n"); return ret; } @@ -1676,6 +1877,13 @@ end: return rc; } +static inline bool dp_is_hdcp_enabled(struct mdss_dp_drv_pdata *dp_drv) +{ + return dp_drv->hdcp.feature_enabled && + (dp_drv->hdcp.hdcp1_present || dp_drv->hdcp.hdcp2_present) && + dp_drv->hdcp.ops; +} + static void mdss_dp_hdcp_cb_work(struct work_struct *work) { struct mdss_dp_drv_pdata *dp; @@ -1688,6 +1896,13 @@ static void mdss_dp_hdcp_cb_work(struct work_struct *work) dp = container_of(dw, struct mdss_dp_drv_pdata, hdcp_cb_work); base = dp->base; + + if (dp->hdcp_status == HDCP_STATE_AUTHENTICATING && + mdss_dp_is_audio_pattern_requested(dp)) { + pr_debug("no hdcp for audio tests\n"); + return; + } + hdcp_auth_state = (dp_read(base + DP_HDCP_STATUS) >> 20) & 0x3; pr_debug("hdcp auth state %d\n", hdcp_auth_state); @@ -1874,7 +2089,7 @@ static ssize_t mdss_dp_sysfs_rda_s3d_mode(struct device *dev, static bool mdss_dp_is_test_ongoing(struct mdss_dp_drv_pdata *dp) { - return dp->hpd_irq_clients_notified; + return (dp->hpd_notification_status == NOTIFY_DISCONNECT_IRQ_HPD); } /** @@ -1916,7 +2131,7 @@ static int mdss_dp_psm_config(struct mdss_dp_drv_pdata *dp, bool enable) mdss_dp_mainlink_push_idle(&dp->panel_data); mdss_dp_off_irq(dp); } else { - mdss_dp_notify_clients(dp, false); + mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT); } } else { /* @@ -1928,10 +2143,10 @@ static int mdss_dp_psm_config(struct mdss_dp_drv_pdata *dp, bool enable) * to user modules. */ if (mdss_dp_is_test_ongoing(dp)) { - mdss_dp_link_retraining(dp); + mdss_dp_link_maintenance(dp, true); } else { mdss_dp_host_init(&dp->panel_data); - mdss_dp_notify_clients(dp, true); + mdss_dp_notify_clients(dp, NOTIFY_CONNECT); } } @@ -1993,7 +2208,7 @@ static ssize_t mdss_dp_rda_psm(struct device *dev, static ssize_t mdss_dp_wta_hpd(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - int hpd; + int hpd, rc; ssize_t ret = strnlen(buf, PAGE_SIZE); struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); @@ -2003,9 +2218,10 @@ static ssize_t mdss_dp_wta_hpd(struct device *dev, goto end; } - ret = kstrtoint(buf, 10, &hpd); - if (ret) { - pr_err("kstrtoint failed. ret=%d\n", (int)ret); + rc = kstrtoint(buf, 10, &hpd); + if (rc) { + pr_err("kstrtoint failed. ret=%d\n", rc); + ret = rc; goto end; } @@ -2020,7 +2236,7 @@ static ssize_t mdss_dp_wta_hpd(struct device *dev, dp_send_events(dp, EV_USBPD_DISCOVER_MODES); } } else if (!dp->hpd && dp->power_on) { - mdss_dp_notify_clients(dp, false); + mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT); } end: return ret; @@ -2043,6 +2259,233 @@ static ssize_t mdss_dp_rda_hpd(struct device *dev, return ret; } +static int mdss_dp_parse_config_value(char const *buf, char const *name, + u32 *val) +{ + int ret = 0; + char *buf1; + char *token; + + buf1 = strnstr(buf, name, PAGE_SIZE); + if (buf1) { + buf1 = buf1 + strlen(name); + token = strsep(&buf1, " "); + ret = kstrtou32(token, 10, val); + if (ret) { + pr_err("kstrtoint failed. ret=%d\n", (int)ret); + goto end; + } + pr_debug("parsed %s(%d)\n", name, *val); + } else { + ret = -EINVAL; + } + +end: + return ret; +} + +static ssize_t mdss_dp_wta_config(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + u32 val; + u32 bit_depth; + int ret; + char const *bpp_key = "bpp="; + char const *pattern_type_key = "pattern_type="; + struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); + + if (!dp) { + pr_err("invalid data\n"); + ret = -EINVAL; + goto end; + } + + ret = mdss_dp_parse_config_value(buf, bpp_key, &val); + if (ret) { + pr_debug("%s config not found\n", bpp_key); + goto pattern_type; + } + + bit_depth = mdss_dp_bpp_to_test_bit_depth(val); + if (!mdss_dp_is_test_bit_depth_valid(bit_depth)) { + pr_err("invalid bpp = %d\n", val); + } else { + dp->test_data.test_bit_depth = bit_depth; + if (val != 0) + dp->override_config = true; + else + dp->override_config = false; + pr_debug("bpp=%d, test_bit_depth=%d\n", val, + dp->test_data.test_bit_depth); + } + +pattern_type: + ret = mdss_dp_parse_config_value(buf, pattern_type_key, &val); + if (ret) { + pr_debug("%s config not found\n", pattern_type_key); + goto end; + } + + if (!mdss_dp_is_test_video_pattern_valid(val)) { + pr_err("invalid test video pattern = %d\n", val); + } else { + dp->test_data.test_video_pattern = val; + pr_debug("test_video_pattern=%d (%s)\n", + dp->test_data.test_video_pattern, + mdss_dp_test_video_pattern_to_string( + dp->test_data.test_video_pattern)); + } + +end: + return count; +} + +static ssize_t mdss_dp_rda_config(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + u32 bpp; + struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); + + if (!dp) { + pr_err("invalid input\n"); + return -EINVAL; + } + + bpp = mdss_dp_get_bpp(dp); + ret = snprintf(buf, PAGE_SIZE, "bpp=%d\npattern_type=%d\n", + bpp, dp->test_data.test_video_pattern); + + pr_debug("bpp: %d pattern_type=%d (%s)\n", + bpp, dp->test_data.test_video_pattern, + mdss_dp_test_video_pattern_to_string( + dp->test_data.test_video_pattern)); + + return ret; +} + +static ssize_t mdss_dp_wta_frame_crc(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret; + u32 val; + struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); + char const *ctl_crc_key = "ctl_crc_en="; + char const *sink_crc_key = "sink_crc_en="; + bool ctl_crc_en, sink_crc_en; + + if (!dp) { + pr_err("invalid data\n"); + goto end; + } + + if (!dp->power_on) { + pr_err("DP controller not powered on\n"); + goto end; + } + + ret = mdss_dp_parse_config_value(buf, ctl_crc_key, &val); + if (ret) { + pr_debug("%s config not found\n", ctl_crc_key); + goto sink_crc; + } + ctl_crc_en = val ? true : false; + mdss_dp_config_ctl_frame_crc(dp, ctl_crc_en); + +sink_crc: + ret = mdss_dp_parse_config_value(buf, sink_crc_key, &val); + if (ret) { + pr_debug("%s config not found\n", sink_crc_key); + goto end; + } + sink_crc_en = val ? true : false; + mdss_dp_aux_config_sink_frame_crc(dp, sink_crc_en); + +end: + return count; +} + +static ssize_t mdss_dp_print_crc_values(struct mdss_dp_drv_pdata *dp, + char *buf, ssize_t len) +{ + char line[] = "------------------------------"; + + mdss_dp_read_ctl_frame_crc(dp); + mdss_dp_aux_read_sink_frame_crc(dp); + + return snprintf(buf, PAGE_SIZE, + "\t\t|R_Cr\t\t|G_y\t\t|B_Cb\n%s%s\nctl(%s)\t|0x%08x\t|0x%08x\t|0x%08x\nsink(%s)\t|0x%08x\t|0x%08x\t|0x%08x\n", + line, line, dp->ctl_crc.en ? "enabled" : "disabled", + dp->ctl_crc.r_cr, dp->ctl_crc.g_y, dp->ctl_crc.b_cb, + dp->sink_crc.en ? "enabled" : "disabled", + dp->sink_crc.r_cr, dp->sink_crc.g_y, dp->sink_crc.b_cb); +} + +static ssize_t mdss_dp_rda_frame_crc(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); + + if (!dp) { + pr_err("invalid input\n"); + return -EINVAL; + } + + if (!dp->power_on) { + pr_err("DP controller not powered on\n"); + return 0; + } + + ret = mdss_dp_print_crc_values(dp, buf, PAGE_SIZE); + + return ret; +} + +static ssize_t mdss_dp_wta_hdcp_feature(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + u32 hdcp; + int rc; + ssize_t ret = strnlen(buf, PAGE_SIZE); + struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); + + if (!dp) { + pr_err("invalid data\n"); + ret = -EINVAL; + goto end; + } + + rc = kstrtoint(buf, 10, &hdcp); + if (rc) { + pr_err("kstrtoint failed. ret=%d\n", rc); + ret = rc; + goto end; + } + + dp->hdcp.feature_enabled = !!hdcp; + pr_debug("hdcp=%d\n", dp->hdcp.feature_enabled); +end: + return ret; +} + +static ssize_t mdss_dp_rda_hdcp_feature(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); + + if (!dp) { + pr_err("invalid input\n"); + return -EINVAL; + } + + ret = snprintf(buf, PAGE_SIZE, "%d\n", dp->hdcp.feature_enabled); + pr_debug("hdcp: %d\n", dp->hdcp.feature_enabled); + + return ret; +} + static DEVICE_ATTR(connected, S_IRUGO, mdss_dp_rda_connected, NULL); static DEVICE_ATTR(s3d_mode, S_IRUGO | S_IWUSR, mdss_dp_sysfs_rda_s3d_mode, mdss_dp_sysfs_wta_s3d_mode); @@ -2050,13 +2493,21 @@ static DEVICE_ATTR(hpd, S_IRUGO | S_IWUSR, mdss_dp_rda_hpd, mdss_dp_wta_hpd); static DEVICE_ATTR(psm, S_IRUGO | S_IWUSR, mdss_dp_rda_psm, mdss_dp_wta_psm); - +static DEVICE_ATTR(config, S_IRUGO | S_IWUSR, mdss_dp_rda_config, + mdss_dp_wta_config); +static DEVICE_ATTR(frame_crc, S_IRUGO | S_IWUSR, mdss_dp_rda_frame_crc, + mdss_dp_wta_frame_crc); +static DEVICE_ATTR(hdcp_feature, S_IRUGO | S_IWUSR, mdss_dp_rda_hdcp_feature, + mdss_dp_wta_hdcp_feature); static struct attribute *mdss_dp_fs_attrs[] = { &dev_attr_connected.attr, &dev_attr_s3d_mode.attr, &dev_attr_hpd.attr, &dev_attr_psm.attr, + &dev_attr_config.attr, + &dev_attr_frame_crc.attr, + &dev_attr_hdcp_feature.attr, NULL, }; @@ -2124,6 +2575,11 @@ static void mdss_dp_update_hdcp_info(struct mdss_dp_drv_pdata *dp) return; } + if (!dp->hdcp.feature_enabled) { + pr_debug("feature not enabled\n"); + return; + } + /* check first if hdcp2p2 is supported */ fd = dp->hdcp.hdcp2; if (fd) @@ -2153,13 +2609,6 @@ static void mdss_dp_update_hdcp_info(struct mdss_dp_drv_pdata *dp) } } -static inline bool dp_is_hdcp_enabled(struct mdss_dp_drv_pdata *dp_drv) -{ - return dp_drv->hdcp.feature_enabled && - (dp_drv->hdcp.hdcp1_present || dp_drv->hdcp.hdcp2_present) && - dp_drv->hdcp.ops; -} - static int mdss_dp_event_handler(struct mdss_panel_data *pdata, int event, void *arg) { @@ -2235,21 +2684,6 @@ static int mdss_dp_event_handler(struct mdss_panel_data *pdata, return rc; } -static int mdss_dp_remove(struct platform_device *pdev) -{ - struct mdss_dp_drv_pdata *dp_drv = NULL; - - dp_drv = platform_get_drvdata(pdev); - dp_hdcp2p2_deinit(dp_drv->hdcp.data); - - iounmap(dp_drv->ctrl_io.base); - dp_drv->ctrl_io.base = NULL; - iounmap(dp_drv->phy_io.base); - dp_drv->phy_io.base = NULL; - - return 0; -} - static int mdss_dp_device_register(struct mdss_dp_drv_pdata *dp_drv) { int ret; @@ -2360,81 +2794,105 @@ static void mdss_dp_do_link_train(struct mdss_dp_drv_pdata *dp) mdss_dp_link_train(dp); } -static void mdss_dp_event_work(struct work_struct *work) +static int mdss_dp_event_thread(void *data) { - struct mdss_dp_drv_pdata *dp = NULL; unsigned long flag; u32 todo = 0, config; - if (!work) { - pr_err("invalid work structure\n"); - return; + struct mdss_dp_event_data *ev_data; + struct mdss_dp_event *ev; + struct mdss_dp_drv_pdata *dp = NULL; + + if (!data) + return -EINVAL; + + ev_data = (struct mdss_dp_event_data *)data; + init_waitqueue_head(&ev_data->event_q); + spin_lock_init(&ev_data->event_lock); + + while (!kthread_should_stop()) { + wait_event(ev_data->event_q, + (ev_data->pndx != ev_data->gndx) || + kthread_should_stop()); + spin_lock_irqsave(&ev_data->event_lock, flag); + ev = &(ev_data->event_list[ev_data->gndx++]); + todo = ev->id; + dp = ev->dp; + ev->id = 0; + ev_data->gndx %= MDSS_DP_EVENT_Q_MAX; + spin_unlock_irqrestore(&ev_data->event_lock, flag); + + pr_debug("todo=%s\n", mdss_dp_ev_event_to_string(todo)); + + switch (todo) { + case EV_EDID_READ: + mdss_dp_edid_read(dp); + break; + case EV_DPCD_CAP_READ: + mdss_dp_dpcd_cap_read(dp); + break; + case EV_DPCD_STATUS_READ: + mdss_dp_dpcd_status_read(dp); + break; + case EV_LINK_TRAIN: + mdss_dp_do_link_train(dp); + break; + case EV_VIDEO_READY: + mdss_dp_video_ready(dp); + break; + case EV_IDLE_PATTERNS_SENT: + mdss_dp_idle_patterns_sent(dp); + break; + case EV_USBPD_ATTENTION: + mdss_dp_handle_attention(dp); + break; + case EV_USBPD_DISCOVER_MODES: + usbpd_send_svdm(dp->pd, USB_C_DP_SID, + USBPD_SVDM_DISCOVER_MODES, + SVDM_CMD_TYPE_INITIATOR, 0x0, 0x0, 0x0); + break; + case EV_USBPD_ENTER_MODE: + usbpd_send_svdm(dp->pd, USB_C_DP_SID, + USBPD_SVDM_ENTER_MODE, + SVDM_CMD_TYPE_INITIATOR, 0x1, 0x0, 0x0); + break; + case EV_USBPD_EXIT_MODE: + usbpd_send_svdm(dp->pd, USB_C_DP_SID, + USBPD_SVDM_EXIT_MODE, + SVDM_CMD_TYPE_INITIATOR, 0x1, 0x0, 0x0); + break; + case EV_USBPD_DP_STATUS: + config = 0x1; /* DFP_D connected */ + usbpd_send_svdm(dp->pd, USB_C_DP_SID, DP_VDM_STATUS, + SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1); + break; + case EV_USBPD_DP_CONFIGURE: + config = mdss_dp_usbpd_gen_config_pkt(dp); + usbpd_send_svdm(dp->pd, USB_C_DP_SID, DP_VDM_CONFIGURE, + SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1); + break; + default: + pr_err("Unknown event:%d\n", todo); + } } - dp = container_of(work, struct mdss_dp_drv_pdata, work); - - spin_lock_irqsave(&dp->event_lock, flag); - todo = dp->current_event; - dp->current_event = 0; - spin_unlock_irqrestore(&dp->event_lock, flag); - - pr_debug("todo=%s\n", mdss_dp_ev_event_to_string(todo)); - - switch (todo) { - case EV_EDID_READ: - mdss_dp_edid_read(dp); - break; - case EV_DPCD_CAP_READ: - mdss_dp_dpcd_cap_read(dp); - break; - case EV_DPCD_STATUS_READ: - mdss_dp_dpcd_status_read(dp); - break; - case EV_LINK_TRAIN: - mdss_dp_do_link_train(dp); - break; - case EV_VIDEO_READY: - mdss_dp_video_ready(dp); - break; - case EV_IDLE_PATTERNS_SENT: - mdss_dp_idle_patterns_sent(dp); - break; - case EV_USBPD_ATTENTION: - mdss_dp_handle_attention(dp); - break; - case EV_USBPD_DISCOVER_MODES: - usbpd_send_svdm(dp->pd, USB_C_DP_SID, USBPD_SVDM_DISCOVER_MODES, - SVDM_CMD_TYPE_INITIATOR, 0x0, 0x0, 0x0); - break; - case EV_USBPD_ENTER_MODE: - usbpd_send_svdm(dp->pd, USB_C_DP_SID, USBPD_SVDM_ENTER_MODE, - SVDM_CMD_TYPE_INITIATOR, 0x1, 0x0, 0x0); - break; - case EV_USBPD_EXIT_MODE: - usbpd_send_svdm(dp->pd, USB_C_DP_SID, USBPD_SVDM_EXIT_MODE, - SVDM_CMD_TYPE_INITIATOR, 0x1, 0x0, 0x0); - break; - case EV_USBPD_DP_STATUS: - config = 0x1; /* DFP_D connected */ - usbpd_send_svdm(dp->pd, USB_C_DP_SID, DP_VDM_STATUS, - SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1); - break; - case EV_USBPD_DP_CONFIGURE: - config = mdss_dp_usbpd_gen_config_pkt(dp); - usbpd_send_svdm(dp->pd, USB_C_DP_SID, DP_VDM_CONFIGURE, - SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1); - break; - default: - pr_err("Unknown event:%d\n", todo); - } + return 0; } -static void dp_send_events(struct mdss_dp_drv_pdata *dp, u32 events) +static void dp_send_events(struct mdss_dp_drv_pdata *dp, u32 event) { - spin_lock(&dp->event_lock); - dp->current_event = events; - queue_work(dp->workq, &dp->work); - spin_unlock(&dp->event_lock); + struct mdss_dp_event *ev; + struct mdss_dp_event_data *ev_data = &dp->dp_event; + + pr_debug("event=%s\n", mdss_dp_ev_event_to_string(event)); + + spin_lock(&ev_data->event_lock); + ev = &ev_data->event_list[ev_data->pndx++]; + ev->id = event; + ev->dp = dp; + ev_data->pndx %= MDSS_DP_EVENT_Q_MAX; + wake_up(&ev_data->event_q); + spin_unlock(&ev_data->event_lock); } irqreturn_t dp_isr(int irq, void *ptr) @@ -2470,10 +2928,10 @@ irqreturn_t dp_isr(int irq, void *ptr) } if (isr2 & EDP_INTR_READY_FOR_VIDEO) - dp_send_events(dp, EV_VIDEO_READY); + mdss_dp_video_ready(dp); if (isr2 & EDP_INTR_IDLE_PATTERNs_SENT) - dp_send_events(dp, EV_IDLE_PATTERNS_SENT); + mdss_dp_idle_patterns_sent(dp); if (isr1 && dp->aux_cmd_busy) { /* clear DP_AUX_TRANS_CTRL */ @@ -2488,7 +2946,7 @@ irqreturn_t dp_isr(int irq, void *ptr) dp_aux_native_handler(dp, isr1); } - if (dp->hdcp.ops && dp->hdcp.ops->isr) { + if (dp_is_hdcp_enabled(dp) && dp->hdcp.ops->isr) { if (dp->hdcp.ops->isr(dp->hdcp.data)) pr_err("dp_hdcp_isr failed\n"); } @@ -2496,17 +2954,32 @@ irqreturn_t dp_isr(int irq, void *ptr) return IRQ_HANDLED; } +static void mdss_dp_event_cleanup(struct mdss_dp_drv_pdata *dp) +{ + destroy_workqueue(dp->workq); + + if (dp->ev_thread == current) + return; + + kthread_stop(dp->ev_thread); +} + static int mdss_dp_event_setup(struct mdss_dp_drv_pdata *dp) { - spin_lock_init(&dp->event_lock); + dp->ev_thread = kthread_run(mdss_dp_event_thread, + (void *)&dp->dp_event, "mdss_dp_event"); + if (IS_ERR(dp->ev_thread)) { + pr_err("unable to start event thread\n"); + return PTR_ERR(dp->ev_thread); + } + dp->workq = create_workqueue("mdss_dp_hpd"); if (!dp->workq) { pr_err("%s: Error creating workqueue\n", __func__); return -EPERM; } - INIT_WORK(&dp->work, mdss_dp_event_work); INIT_DELAYED_WORK(&dp->hdcp_cb_work, mdss_dp_hdcp_cb_work); INIT_LIST_HEAD(&dp->attention_head); return 0; @@ -2541,7 +3014,18 @@ static void usbpd_disconnect_callback(struct usbpd_svid_handler *hdlr) pr_debug("cable disconnected\n"); mdss_dp_update_cable_status(dp_drv, false); dp_drv->alt_mode.current_state = UNKNOWN_STATE; - mdss_dp_notify_clients(dp_drv, false); + + /** + * Manually turn off the DP controller if we are in PHY + * testing mode. + */ + if (mdss_dp_is_phy_test_pattern_requested(dp_drv)) { + pr_info("turning off DP controller for PHY testing\n"); + mdss_dp_mainlink_push_idle(&dp_drv->panel_data); + mdss_dp_off_hpd(dp_drv); + } else { + mdss_dp_notify_clients(dp_drv, NOTIFY_DISCONNECT); + } } static int mdss_dp_validate_callback(u8 cmd, @@ -2598,54 +3082,22 @@ static inline void mdss_dp_send_test_response(struct mdss_dp_drv_pdata *dp) } /** - * mdss_dp_hpd_irq_notify_clients() - notifies DP clients of HPD IRQ tear down + * mdss_dp_link_maintenance() - initiates link maintenanace * @dp: Display Port Driver data + * @lt_needed: link retraining needed * - * This function will send a notification to display/audio clients of DP tear - * down during an HPD IRQ. This happens only if HPD IRQ is toggled, - * in which case the user space proceeds with shutdown of DP driver, including - * mainlink disable, and pushing the controller into idle state. - */ -static int mdss_dp_hpd_irq_notify_clients(struct mdss_dp_drv_pdata *dp) -{ - const int irq_comp_timeout = HZ * 2; - int ret = 0; - - if (dp->hpd_irq_toggled) { - dp->hpd_irq_clients_notified = true; - - ret = mdss_dp_notify_clients(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; - } - } - } - - return 0; -} - -/** - * mdss_dp_link_retraining() - initiates link retraining - * @dp: Display Port Driver data - * - * This function will initiate link retraining by first notifying + * This function will perform link maintenance by first notifying * DP clients and triggering DP shutdown, and then enabling DP after - * notification is done successfully. + * notification is done successfully. It will perform link retraining + * if specified. */ -static inline void mdss_dp_link_retraining(struct mdss_dp_drv_pdata *dp) +static inline void mdss_dp_link_maintenance(struct mdss_dp_drv_pdata *dp, + bool lt_needed) { - if (mdss_dp_hpd_irq_notify_clients(dp)) + if (mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT_IRQ_HPD)) return; - mdss_dp_on_irq(dp); + mdss_dp_on_irq(dp, lt_needed); } /** @@ -2670,7 +3122,7 @@ static int mdss_dp_process_link_status_update(struct mdss_dp_drv_pdata *dp) mdss_dp_aux_channel_eq_done(dp), mdss_dp_aux_clock_recovery_done(dp)); - mdss_dp_link_retraining(dp); + mdss_dp_link_maintenance(dp, true); return 0; } @@ -2701,7 +3153,7 @@ static int mdss_dp_process_link_training_request(struct mdss_dp_drv_pdata *dp) dp->test_data.test_lane_count; dp->link_rate = dp->test_data.test_link_rate; - mdss_dp_link_retraining(dp); + mdss_dp_link_maintenance(dp, true); return 0; } @@ -2722,38 +3174,109 @@ static int mdss_dp_process_phy_test_pattern_request( if (!mdss_dp_is_phy_test_pattern_requested(dp)) return -EINVAL; - mdss_dp_send_test_response(dp); - test_link_rate = dp->test_data.test_link_rate; test_lane_count = dp->test_data.test_lane_count; - pr_info("%s link rate = 0x%x, lane count = 0x%x\n", - mdss_dp_get_test_name(TEST_LINK_TRAINING), - test_link_rate, test_lane_count); - - /** - * Retrain the mainlink if there is a change in link rate or lane - * count. - */ - if (mdss_dp_aux_is_link_rate_valid(test_link_rate) && - mdss_dp_aux_is_lane_count_valid(test_lane_count) && - ((dp->dpcd.max_lane_count != test_lane_count) || - (dp->link_rate != test_link_rate))) { - - pr_info("updated link rate or lane count, retraining.\n"); - - dp->dpcd.max_lane_count = dp->test_data.test_lane_count; - dp->link_rate = dp->test_data.test_link_rate; - - mdss_dp_link_retraining(dp); + if (!mdss_dp_aux_is_link_rate_valid(test_link_rate) || + !mdss_dp_aux_is_lane_count_valid(test_lane_count)) { + pr_info("Invalid params: link rate = 0x%x, lane count = 0x%x\n", + test_link_rate, test_lane_count); + return -EINVAL; } - mdss_dp_config_ctrl(dp); + pr_debug("start\n"); + if (dp->power_on) { + pr_info("turning off DP controller for PHY testing\n"); + mdss_dp_mainlink_push_idle(&dp->panel_data); + /* + * The global reset will need DP link ralated clocks to be + * running. Add the global reset just before disabling the + * link clocks and core clocks. + */ + mdss_dp_ctrl_reset(&dp->ctrl_io); + mdss_dp_off_irq(dp); + } + + /** + * Set the timing information to 1920x1080p60. This resolution will be + * used when enabling the pixel clock. + */ + dp_init_panel_info(dp, HDMI_VFRMT_1920x1080p60_16_9); + + pr_info("Current: link rate = 0x%x, lane count = 0x%x\n", + dp->dpcd.max_lane_count, + dp->link_rate); + + pr_info("Requested: link rate = 0x%x, lane count = 0x%x\n", + dp->test_data.test_link_rate, + dp->test_data.test_lane_count); + + dp->dpcd.max_lane_count = dp->test_data.test_lane_count; + dp->link_rate = dp->test_data.test_link_rate; + + mdss_dp_on_irq(dp, true); + + /** + * Read the updated values for voltage and pre-emphasis levels and + * then program the DP controller PHY accordingly. + */ + mdss_dp_aux_parse_vx_px(dp); mdss_dp_aux_update_voltage_and_pre_emphasis_lvl(dp); mdss_dp_phy_send_test_pattern(dp); + mdss_dp_send_test_response(dp); + + pr_debug("end\n"); + + return 0; +} + +/** + * mdss_dp_process_audio_pattern_request() - process new audio pattern request + * @dp: Display Port Driver data + * + * This function will handle a new audio pattern request that is initiated by + * the sink. This is acheieved by sending the necessary secondary data packets + * to the sink. It is expected that any simulatenous requests for video + * patterns will be handled before the audio pattern is sent to the sink. + */ +static int mdss_dp_process_audio_pattern_request(struct mdss_dp_drv_pdata *dp) +{ + if (!mdss_dp_is_audio_pattern_requested(dp)) + return -EINVAL; + + if (dp_is_hdcp_enabled(dp) && dp->hdcp.ops->off) { + cancel_delayed_work(&dp->hdcp_cb_work); + dp->hdcp.ops->off(dp->hdcp.data); + } + + pr_debug("sampling_rate=%s, channel_count=%d, pattern_type=%s\n", + mdss_dp_get_audio_sample_rate( + dp->test_data.test_audio_sampling_rate), + dp->test_data.test_audio_channel_count, + mdss_dp_get_audio_test_pattern( + dp->test_data.test_audio_pattern_type)); + + pr_debug("audio_period: ch1=0x%x, ch2=0x%x, ch3=0x%x, ch4=0x%x\n", + dp->test_data.test_audio_period_ch_1, + dp->test_data.test_audio_period_ch_2, + dp->test_data.test_audio_period_ch_3, + dp->test_data.test_audio_period_ch_4); + + pr_debug("audio_period: ch5=0x%x, ch6=0x%x, ch7=0x%x, ch8=0x%x\n", + dp->test_data.test_audio_period_ch_5, + dp->test_data.test_audio_period_ch_6, + dp->test_data.test_audio_period_ch_7, + dp->test_data.test_audio_period_ch_8); + + if (dp->ext_audio_data.intf_ops.hpd) + dp->ext_audio_data.intf_ops.hpd(dp->ext_pdev, + dp->ext_audio_data.type, 1, MSM_EXT_DISP_HPD_AUDIO); + + dp->audio_test_req = true; + return 0; } @@ -2777,6 +3300,81 @@ static int mdss_dp_process_downstream_port_status_change( return mdss_dp_edid_read(dp); } +static bool mdss_dp_video_pattern_test_lt_needed(struct mdss_dp_drv_pdata *dp) +{ + char new_link_rate; + + /* + * Link re-training for video format change is only needed if: + * 1. Link rate changes + * 2. Lane count changes + * For now, assume that lane count is not going to change + */ + new_link_rate = mdss_dp_gen_link_clk(dp); + pr_debug("new link rate = 0x%x, current link rate = 0x%x\n", + new_link_rate, dp->link_rate); + if (new_link_rate != dp->link_rate) { + dp->link_rate = new_link_rate; + return true; + } + + return false; +} + +/** + * mdss_dp_process_video_pattern_request() - process new video pattern request + * @dp: Display Port Driver data + * + * This function will handle a new video pattern request that are initiated by + * the sink. This is acheieved by first sending a disconnect notification to + * the sink followed by a subsequent connect notification to the user modules, + * where it is expected that the user modules would draw the required test + * pattern. + */ +static int mdss_dp_process_video_pattern_request(struct mdss_dp_drv_pdata *dp) +{ + bool lt_needed; + struct hdmi_edid_override_data ov_data = {0, 0, 1, + HDMI_VFRMT_640x480p59_4_3}; + bool ov_res = false; + + if (!mdss_dp_is_video_pattern_requested(dp)) + goto end; + + pr_info("%s: bit depth=%d(%d bpp) pattern=%s\n", + mdss_dp_get_test_name(TEST_VIDEO_PATTERN), + dp->test_data.test_bit_depth, + mdss_dp_test_bit_depth_to_bpp(dp->test_data.test_bit_depth), + mdss_dp_test_video_pattern_to_string( + dp->test_data.test_video_pattern)); + + if (dp->test_data.test_h_width == 640) { + pr_debug("Set resolution to 640x480p59"); + if (dp->vic != HDMI_VFRMT_640x480p59_4_3) { + ov_res = true; + dp->vic = HDMI_VFRMT_640x480p59_4_3; + } + hdmi_edid_config_override(dp->panel_data.panel_info.edid_data, + true, &ov_data); + } + + dp_init_panel_info(dp, dp->vic); + lt_needed = ov_res | mdss_dp_video_pattern_test_lt_needed(dp); + + pr_debug("Link training needed: %s", lt_needed ? "yes" : "no"); + + mdss_dp_link_maintenance(dp, lt_needed); + + if (mdss_dp_is_audio_pattern_requested(dp)) + goto end; + + mdss_dp_send_test_response(dp); + + return 0; +end: + return -EINVAL; +} + /** * mdss_dp_process_hpd_irq_high() - handle HPD IRQ transition to HIGH * @dp: Display Port Driver data @@ -2789,14 +3387,22 @@ static int mdss_dp_process_hpd_irq_high(struct mdss_dp_drv_pdata *dp) { int ret = 0; + pr_debug("start\n"); + dp->hpd_irq_on = true; + mdss_dp_reset_test_data(dp); + mdss_dp_aux_parse_sink_status_field(dp); ret = mdss_dp_process_link_training_request(dp); if (!ret) goto exit; + ret = mdss_dp_process_phy_test_pattern_request(dp); + if (!ret) + goto exit; + ret = mdss_dp_process_link_status_update(dp); if (!ret) goto exit; @@ -2805,41 +3411,19 @@ static int mdss_dp_process_hpd_irq_high(struct mdss_dp_drv_pdata *dp) if (!ret) goto exit; - ret = mdss_dp_process_phy_test_pattern_request(dp); + ret = mdss_dp_process_video_pattern_request(dp); if (!ret) goto exit; + + ret = mdss_dp_process_audio_pattern_request(dp); + if (!ret) + goto exit; + pr_debug("done\n"); + exit: - mdss_dp_reset_test_data(dp); - - return ret; -} - -/** - * mdss_dp_process_hpd_irq_low() - handle HPD IRQ transition to LOW - * @dp: Display Port Driver data - * - * This function will handle the HPD IRQ state transitions from HIGH to LOW, - * indicating the end of a test request. - */ -static int mdss_dp_process_hpd_irq_low(struct mdss_dp_drv_pdata *dp) -{ - if (!dp->hpd_irq_clients_notified) - return -EINVAL; - - pr_debug("enter: HPD IRQ low\n"); - dp->hpd_irq_on = false; - dp->hpd_irq_clients_notified = false; - - mdss_dp_update_cable_status(dp, false); - mdss_dp_mainlink_push_idle(&dp->panel_data); - mdss_dp_off_hpd(dp); - - mdss_dp_reset_test_data(dp); - - pr_debug("done\n"); - return 0; + return ret; } static void usbpd_response_callback(struct usbpd_svid_handler *hdlr, u8 cmd, @@ -2900,6 +3484,7 @@ static void usbpd_response_callback(struct usbpd_svid_handler *hdlr, u8 cmd, case DP_VDM_CONFIGURE: dp_drv->alt_mode.current_state |= DP_CONFIGURE_DONE; pr_debug("Configure: config USBPD to DP done\n"); + mdss_dp_usbpd_ext_dp_status(&dp_drv->alt_mode.dp_status); mdss_dp_host_init(&dp_drv->panel_data); @@ -2914,22 +3499,16 @@ static void usbpd_response_callback(struct usbpd_svid_handler *hdlr, u8 cmd, static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv) { - dp_drv->hpd_irq_toggled = dp_drv->hpd_irq_on != - dp_drv->alt_mode.dp_status.hpd_irq; - if (dp_drv->alt_mode.dp_status.hpd_irq) { pr_debug("Attention: hpd_irq high\n"); - if (dp_drv->hdcp.ops && dp_drv->hdcp.ops->cp_irq) { + if (dp_is_hdcp_enabled(dp_drv) && dp_drv->hdcp.ops->cp_irq) { if (!dp_drv->hdcp.ops->cp_irq(dp_drv->hdcp.data)) return; } if (!mdss_dp_process_hpd_irq_high(dp_drv)) return; - } else if (dp_drv->hpd_irq_toggled) { - if (!mdss_dp_process_hpd_irq_low(dp_drv)) - return; } if (!dp_drv->alt_mode.dp_status.hpd_high) { @@ -2940,16 +3519,23 @@ static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv) dp_drv->hdcp.ops->off(dp_drv->hdcp.data); } - mdss_dp_update_cable_status(dp_drv, false); - mdss_dp_notify_clients(dp_drv, false); + mdss_dp_notify_clients(dp_drv, NOTIFY_DISCONNECT); pr_debug("Attention: Notified clients\n"); + + /** + * Manually turn off the DP controller if we are in PHY + * testing mode. + */ + if (mdss_dp_is_phy_test_pattern_requested(dp_drv)) { + pr_info("turning off DP controller for PHY testing\n"); + mdss_dp_mainlink_push_idle(&dp_drv->panel_data); + mdss_dp_off_hpd(dp_drv); + } return; } pr_debug("Attention: HPD high\n"); - mdss_dp_update_cable_status(dp_drv, true); - dp_drv->alt_mode.current_state |= DP_STATUS_DONE; if (dp_drv->alt_mode.current_state & DP_CONFIGURE_DONE) { @@ -2963,6 +3549,7 @@ static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv) static void mdss_dp_handle_attention(struct mdss_dp_drv_pdata *dp) { int i = 0; + pr_debug("start\n"); while (!list_empty_careful(&dp->attention_head)) { struct mdss_dp_attention_node *node; @@ -2983,8 +3570,11 @@ static void mdss_dp_handle_attention(struct mdss_dp_drv_pdata *dp) dp->alt_mode.dp_status.response = vdo; mdss_dp_usbpd_ext_dp_status(&dp->alt_mode.dp_status); mdss_dp_process_attention(dp); + + pr_debug("done processing item %d in the list\n", i); }; + pr_debug("exit\n"); } static int mdss_dp_usbpd_setup(struct mdss_dp_drv_pdata *dp_drv) @@ -3063,6 +3653,11 @@ static int mdss_dp_probe(struct platform_device *pdev) mutex_init(&dp_drv->attention_lock); mutex_init(&dp_drv->hdcp_mutex); spin_lock_init(&dp_drv->lock); + mutex_init(&dp_drv->aux_mutex); + mutex_init(&dp_drv->train_mutex); + init_completion(&dp_drv->aux_comp); + init_completion(&dp_drv->idle_comp); + init_completion(&dp_drv->video_comp); if (mdss_dp_usbpd_setup(dp_drv)) { pr_err("Error usbpd setup!\n"); @@ -3202,6 +3797,22 @@ static inline bool dp_is_stream_shareable(struct mdss_dp_drv_pdata *dp_drv) return ret; } +static int mdss_dp_remove(struct platform_device *pdev) +{ + struct mdss_dp_drv_pdata *dp_drv = NULL; + + dp_drv = platform_get_drvdata(pdev); + dp_hdcp2p2_deinit(dp_drv->hdcp.data); + + mdss_dp_event_cleanup(dp_drv); + iounmap(dp_drv->ctrl_io.base); + dp_drv->ctrl_io.base = NULL; + iounmap(dp_drv->phy_io.base); + dp_drv->phy_io.base = NULL; + + return 0; +} + static const struct of_device_id msm_mdss_dp_dt_match[] = { {.compatible = "qcom,mdss-dp"}, {} diff --git a/drivers/video/fbdev/msm/mdss_dp.h b/drivers/video/fbdev/msm/mdss_dp.h index b7a8583e5864..bf74a8a4d7df 100644 --- a/drivers/video/fbdev/msm/mdss_dp.h +++ b/drivers/video/fbdev/msm/mdss_dp.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2014, 2016 The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2014, 2016-2017 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -269,6 +269,32 @@ struct dpcd_test_request { u32 test_link_rate; u32 test_lane_count; u32 phy_test_pattern_sel; + u32 test_video_pattern; + u32 test_bit_depth; + u32 test_dyn_range; + u32 test_h_total; + u32 test_v_total; + u32 test_h_start; + u32 test_v_start; + u32 test_hsync_pol; + u32 test_hsync_width; + u32 test_vsync_pol; + u32 test_vsync_width; + u32 test_h_width; + u32 test_v_height; + u32 test_rr_d; + u32 test_rr_n; + u32 test_audio_sampling_rate; + u32 test_audio_channel_count; + u32 test_audio_pattern_type; + u32 test_audio_period_ch_1; + u32 test_audio_period_ch_2; + u32 test_audio_period_ch_3; + u32 test_audio_period_ch_4; + u32 test_audio_period_ch_5; + u32 test_audio_period_ch_6; + u32 test_audio_period_ch_7; + u32 test_audio_period_ch_8; u32 response; }; @@ -376,6 +402,28 @@ struct dp_hdcp { bool feature_enabled; }; +struct mdss_dp_event { + struct mdss_dp_drv_pdata *dp; + u32 id; +}; + +#define MDSS_DP_EVENT_Q_MAX 4 + +struct mdss_dp_event_data { + wait_queue_head_t event_q; + u32 pndx; + u32 gndx; + struct mdss_dp_event event_list[MDSS_DP_EVENT_Q_MAX]; + spinlock_t event_lock; +}; + +struct mdss_dp_crc_data { + bool en; + u32 r_cr; + u32 g_y; + u32 b_cb; +}; + struct mdss_dp_drv_pdata { /* device driver */ int (*on) (struct mdss_panel_data *pdata); @@ -402,6 +450,7 @@ struct mdss_dp_drv_pdata { bool sink_info_read; bool hpd; bool psm_enabled; + bool audio_test_req; /* dp specific */ unsigned char *base; @@ -413,8 +462,11 @@ struct mdss_dp_drv_pdata { struct dss_io_data hdcp_io; int base_size; unsigned char *mmss_cc_base; + bool override_config; u32 mask1; u32 mask2; + struct mdss_dp_crc_data ctl_crc; + struct mdss_dp_crc_data sink_crc; struct mdss_panel_data panel_data; struct mdss_util_intf *mdss_util; @@ -451,7 +503,6 @@ struct mdss_dp_drv_pdata { /* aux */ struct completion aux_comp; - struct completion train_comp; struct completion idle_comp; struct completion video_comp; struct completion irq_comp; @@ -478,18 +529,15 @@ struct mdss_dp_drv_pdata { char tu_desired; char valid_boundary; char delay_start; - u32 bpp; struct dp_statistic dp_stat; bool hpd_irq_on; - bool hpd_irq_toggled; - bool hpd_irq_clients_notified; + u32 hpd_notification_status; + + struct mdss_dp_event_data dp_event; + struct task_struct *ev_thread; - /* event */ struct workqueue_struct *workq; - struct work_struct work; struct delayed_work hdcp_cb_work; - u32 current_event; - spinlock_t event_lock; spinlock_t lock; struct switch_dev sdev; struct kobject *kobj; @@ -512,6 +560,55 @@ enum dp_lane_count { DP_LANE_COUNT_4 = 4, }; +enum audio_pattern_type { + AUDIO_TEST_PATTERN_OPERATOR_DEFINED = 0x00, + AUDIO_TEST_PATTERN_SAWTOOTH = 0x01, +}; + +static inline char *mdss_dp_get_audio_test_pattern(u32 pattern) +{ + switch (pattern) { + case AUDIO_TEST_PATTERN_OPERATOR_DEFINED: + return DP_ENUM_STR(AUDIO_TEST_PATTERN_OPERATOR_DEFINED); + case AUDIO_TEST_PATTERN_SAWTOOTH: + return DP_ENUM_STR(AUDIO_TEST_PATTERN_SAWTOOTH); + default: + return "unknown"; + } +} + +enum audio_sample_rate { + AUDIO_SAMPLE_RATE_32_KHZ = 0x00, + AUDIO_SAMPLE_RATE_44_1_KHZ = 0x01, + AUDIO_SAMPLE_RATE_48_KHZ = 0x02, + AUDIO_SAMPLE_RATE_88_2_KHZ = 0x03, + AUDIO_SAMPLE_RATE_96_KHZ = 0x04, + AUDIO_SAMPLE_RATE_176_4_KHZ = 0x05, + AUDIO_SAMPLE_RATE_192_KHZ = 0x06, +}; + +static inline char *mdss_dp_get_audio_sample_rate(u32 rate) +{ + switch (rate) { + case AUDIO_SAMPLE_RATE_32_KHZ: + return DP_ENUM_STR(AUDIO_SAMPLE_RATE_32_KHZ); + case AUDIO_SAMPLE_RATE_44_1_KHZ: + return DP_ENUM_STR(AUDIO_SAMPLE_RATE_44_1_KHZ); + case AUDIO_SAMPLE_RATE_48_KHZ: + return DP_ENUM_STR(AUDIO_SAMPLE_RATE_48_KHZ); + case AUDIO_SAMPLE_RATE_88_2_KHZ: + return DP_ENUM_STR(AUDIO_SAMPLE_RATE_88_2_KHZ); + case AUDIO_SAMPLE_RATE_96_KHZ: + return DP_ENUM_STR(AUDIO_SAMPLE_RATE_96_KHZ); + case AUDIO_SAMPLE_RATE_176_4_KHZ: + return DP_ENUM_STR(AUDIO_SAMPLE_RATE_176_4_KHZ); + case AUDIO_SAMPLE_RATE_192_KHZ: + return DP_ENUM_STR(AUDIO_SAMPLE_RATE_192_KHZ); + default: + return "unknown"; + } +} + enum phy_test_pattern { PHY_TEST_PATTERN_NONE, PHY_TEST_PATTERN_D10_2_NO_SCRAMBLING, @@ -608,17 +705,22 @@ static inline char *mdss_dp_get_test_response(u32 test_response) enum test_type { UNKNOWN_TEST = 0, - TEST_LINK_TRAINING = BIT(0), - PHY_TEST_PATTERN = BIT(3), - TEST_EDID_READ = BIT(2), + TEST_LINK_TRAINING = 0x1, + TEST_VIDEO_PATTERN = 0x2, + PHY_TEST_PATTERN = 0x8, + TEST_EDID_READ = 0x4, + TEST_AUDIO_PATTERN = 32, + TEST_AUDIO_DISABLED_VIDEO = 64, }; static inline char *mdss_dp_get_test_name(u32 test_requested) { switch (test_requested) { case TEST_LINK_TRAINING: return DP_ENUM_STR(TEST_LINK_TRAINING); + case TEST_VIDEO_PATTERN: return DP_ENUM_STR(TEST_VIDEO_PATTERN); case PHY_TEST_PATTERN: return DP_ENUM_STR(PHY_TEST_PATTERN); case TEST_EDID_READ: return DP_ENUM_STR(TEST_EDID_READ); + case TEST_AUDIO_PATTERN: return DP_ENUM_STR(TEST_AUDIO_PATTERN); default: return "unknown"; } } @@ -661,11 +763,252 @@ static inline char *mdss_dp_ev_event_to_string(int event) return DP_ENUM_STR(EV_IDLE_PATTERNS_SENT); case EV_VIDEO_READY: return DP_ENUM_STR(EV_VIDEO_READY); + case EV_USBPD_DISCOVER_MODES: + return DP_ENUM_STR(EV_USBPD_DISCOVER_MODES); + case EV_USBPD_ENTER_MODE: + return DP_ENUM_STR(EV_USBPD_ENTER_MODE); + case EV_USBPD_DP_STATUS: + return DP_ENUM_STR(EV_USBPD_DP_STATUS); + case EV_USBPD_DP_CONFIGURE: + return DP_ENUM_STR(EV_USBPD_DP_CONFIGURE); + case EV_USBPD_CC_PIN_POLARITY: + return DP_ENUM_STR(EV_USBPD_CC_PIN_POLARITY); + case EV_USBPD_EXIT_MODE: + return DP_ENUM_STR(EV_USBPD_EXIT_MODE); + case EV_USBPD_ATTENTION: + return DP_ENUM_STR(EV_USBPD_ATTENTION); default: return "unknown"; } } +enum dynamic_range { + DP_DYNAMIC_RANGE_RGB_VESA = 0x00, + DP_DYNAMIC_RANGE_RGB_CEA = 0x01, + DP_DYNAMIC_RANGE_UNKNOWN = 0xFFFFFFFF, +}; + +static inline char *mdss_dp_dynamic_range_to_string(u32 dr) +{ + switch (dr) { + case DP_DYNAMIC_RANGE_RGB_VESA: + return DP_ENUM_STR(DP_DYNAMIC_RANGE_RGB_VESA); + case DP_DYNAMIC_RANGE_RGB_CEA: + return DP_ENUM_STR(DP_DYNAMIC_RANGE_RGB_CEA); + case DP_DYNAMIC_RANGE_UNKNOWN: + default: + return "unknown"; + } +} + +/** + * mdss_dp_is_dynamic_range_valid() - validates the dynamic range + * @bit_depth: the dynamic range value to be checked + * + * Returns true if the dynamic range value is supported. + */ +static inline bool mdss_dp_is_dynamic_range_valid(u32 dr) +{ + switch (dr) { + case DP_DYNAMIC_RANGE_RGB_VESA: + case DP_DYNAMIC_RANGE_RGB_CEA: + return true; + default: + return false; + } +} + +enum test_bit_depth { + DP_TEST_BIT_DEPTH_6 = 0x00, + DP_TEST_BIT_DEPTH_8 = 0x01, + DP_TEST_BIT_DEPTH_10 = 0x02, + DP_TEST_BIT_DEPTH_UNKNOWN = 0xFFFFFFFF, +}; + +static inline char *mdss_dp_test_bit_depth_to_string(u32 tbd) +{ + switch (tbd) { + case DP_TEST_BIT_DEPTH_6: + return DP_ENUM_STR(DP_TEST_BIT_DEPTH_6); + case DP_TEST_BIT_DEPTH_8: + return DP_ENUM_STR(DP_TEST_BIT_DEPTH_8); + case DP_TEST_BIT_DEPTH_10: + return DP_ENUM_STR(DP_TEST_BIT_DEPTH_10); + case DP_TEST_BIT_DEPTH_UNKNOWN: + default: + return "unknown"; + } +} + +/** + * mdss_dp_is_test_bit_depth_valid() - validates the bit depth requested + * @bit_depth: bit depth requested by the sink + * + * Returns true if the requested bit depth is supported. + */ +static inline bool mdss_dp_is_test_bit_depth_valid(u32 tbd) +{ + /* DP_TEST_VIDEO_PATTERN_NONE is treated as invalid */ + switch (tbd) { + case DP_TEST_BIT_DEPTH_6: + case DP_TEST_BIT_DEPTH_8: + case DP_TEST_BIT_DEPTH_10: + return true; + default: + return false; + } +} + +/** + * mdss_dp_test_bit_depth_to_bpp() - convert test bit depth to bpp + * @tbd: test bit depth + * + * Returns the bits per pixel (bpp) to be used corresponding to the + * git bit depth value. This function assumes that bit depth has + * already been validated. + */ +static inline u32 mdss_dp_test_bit_depth_to_bpp(enum test_bit_depth tbd) +{ + u32 bpp; + + /* + * Few simplistic rules and assumptions made here: + * 1. Bit depth is per color component + * 2. If bit depth is unknown return 0 + * 3. Assume 3 color components + */ + switch (tbd) { + case DP_TEST_BIT_DEPTH_6: + bpp = 18; + break; + case DP_TEST_BIT_DEPTH_8: + bpp = 24; + break; + case DP_TEST_BIT_DEPTH_10: + bpp = 30; + break; + case DP_TEST_BIT_DEPTH_UNKNOWN: + default: + bpp = 0; + } + + return bpp; +} + +/** + * mdss_dp_bpp_to_test_bit_depth() - convert bpp to test bit depth + * &bpp: the bpp to be converted + * + * Return the bit depth per color component to used with the video + * test pattern data based on the bits per pixel value. + */ +static inline u32 mdss_dp_bpp_to_test_bit_depth(u32 bpp) +{ + enum test_bit_depth tbd; + + /* + * Few simplistic rules and assumptions made here: + * 1. Test bit depth is bit depth per color component + * 2. Assume 3 color components + */ + switch (bpp) { + case 18: + tbd = DP_TEST_BIT_DEPTH_6; + break; + case 24: + tbd = DP_TEST_BIT_DEPTH_8; + break; + case 30: + tbd = DP_TEST_BIT_DEPTH_10; + break; + default: + tbd = DP_TEST_BIT_DEPTH_UNKNOWN; + break; + } + + return tbd; +} + +enum test_video_pattern { + DP_TEST_VIDEO_PATTERN_NONE = 0x00, + DP_TEST_VIDEO_PATTERN_COLOR_RAMPS = 0x01, + DP_TEST_VIDEO_PATTERN_BW_VERT_LINES = 0x02, + DP_TEST_VIDEO_PATTERN_COLOR_SQUARE = 0x03, +}; + +static inline char *mdss_dp_test_video_pattern_to_string(u32 test_video_pattern) +{ + switch (test_video_pattern) { + case DP_TEST_VIDEO_PATTERN_NONE: + return DP_ENUM_STR(DP_TEST_VIDEO_PATTERN_NONE); + case DP_TEST_VIDEO_PATTERN_COLOR_RAMPS: + return DP_ENUM_STR(DP_TEST_VIDEO_PATTERN_COLOR_RAMPS); + case DP_TEST_VIDEO_PATTERN_BW_VERT_LINES: + return DP_ENUM_STR(DP_TEST_VIDEO_PATTERN_BW_VERT_LINES); + case DP_TEST_VIDEO_PATTERN_COLOR_SQUARE: + return DP_ENUM_STR(DP_TEST_VIDEO_PATTERN_COLOR_SQUARE); + default: + return "unknown"; + } +} + +/** + * mdss_dp_is_test_video_pattern_valid() - validates the video pattern + * @pattern: video pattern requested by the sink + * + * Returns true if the requested video pattern is supported. + */ +static inline bool mdss_dp_is_test_video_pattern_valid(u32 pattern) +{ + switch (pattern) { + case DP_TEST_VIDEO_PATTERN_NONE: + case DP_TEST_VIDEO_PATTERN_COLOR_RAMPS: + case DP_TEST_VIDEO_PATTERN_BW_VERT_LINES: + case DP_TEST_VIDEO_PATTERN_COLOR_SQUARE: + return true; + default: + return false; + } +} + +enum notification_status { + NOTIFY_UNKNOWN, + NOTIFY_CONNECT, + NOTIFY_DISCONNECT, + NOTIFY_CONNECT_IRQ_HPD, + NOTIFY_DISCONNECT_IRQ_HPD, +}; + +static inline char const *mdss_dp_notification_status_to_string( + enum notification_status status) +{ + switch (status) { + case NOTIFY_UNKNOWN: + return DP_ENUM_STR(NOTIFY_UNKNOWN); + case NOTIFY_CONNECT: + return DP_ENUM_STR(NOTIFY_CONNECT); + case NOTIFY_DISCONNECT: + return DP_ENUM_STR(NOTIFY_DISCONNECT); + case NOTIFY_CONNECT_IRQ_HPD: + return DP_ENUM_STR(NOTIFY_CONNECT_IRQ_HPD); + case NOTIFY_DISCONNECT_IRQ_HPD: + return DP_ENUM_STR(NOTIFY_DISCONNECT_IRQ_HPD); + default: + return "unknown"; + } +} + +static inline void mdss_dp_reset_frame_crc_data(struct mdss_dp_crc_data *crc) +{ + if (!crc) + return; + + crc->r_cr = 0; + crc->g_y = 0; + crc->b_cb = 0; + crc->en = false; +} + void mdss_dp_phy_initialize(struct mdss_dp_drv_pdata *dp); void mdss_dp_dpcd_cap_read(struct mdss_dp_drv_pdata *dp); @@ -681,7 +1024,7 @@ void mdss_dp_fill_link_cfg(struct mdss_dp_drv_pdata *ep); void mdss_dp_sink_power_down(struct mdss_dp_drv_pdata *ep); void mdss_dp_lane_power_ctrl(struct mdss_dp_drv_pdata *ep, int up); void mdss_dp_config_ctrl(struct mdss_dp_drv_pdata *ep); -char mdss_dp_gen_link_clk(struct mdss_panel_info *pinfo, char lane_cnt); +char mdss_dp_gen_link_clk(struct mdss_dp_drv_pdata *dp); int mdss_dp_aux_set_sink_power_state(struct mdss_dp_drv_pdata *ep, char state); int mdss_dp_aux_send_psm_request(struct mdss_dp_drv_pdata *dp, bool enable); void mdss_dp_aux_send_test_response(struct mdss_dp_drv_pdata *ep); @@ -694,5 +1037,9 @@ bool mdss_dp_aux_is_lane_count_valid(u32 lane_count); int mdss_dp_aux_link_status_read(struct mdss_dp_drv_pdata *ep, int len); void mdss_dp_aux_update_voltage_and_pre_emphasis_lvl( struct mdss_dp_drv_pdata *dp); +int mdss_dp_aux_read_sink_frame_crc(struct mdss_dp_drv_pdata *dp); +int mdss_dp_aux_config_sink_frame_crc(struct mdss_dp_drv_pdata *dp, + bool enable); +int mdss_dp_aux_parse_vx_px(struct mdss_dp_drv_pdata *ep); #endif /* MDSS_DP_H */ diff --git a/drivers/video/fbdev/msm/mdss_dp_aux.c b/drivers/video/fbdev/msm/mdss_dp_aux.c index fa1af0d392e7..ca07e80d6613 100644 --- a/drivers/video/fbdev/msm/mdss_dp_aux.c +++ b/drivers/video/fbdev/msm/mdss_dp_aux.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013, 2014, 2016 The Linux Foundation. All rights reserved. +/* Copyright (c) 2013-2014, 2016-2017 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -466,10 +466,8 @@ void dp_extract_edid_video_support(struct edp_edid *edid, char *buf) edid->video_intf = *bp & 0x0f; /* 6, 8, 10, 12, 14 and 16 bit per component */ edid->color_depth = ((*bp & 0x70) >> 4); /* color bit depth */ - if (edid->color_depth) { - edid->color_depth *= 2; - edid->color_depth += 4; - } + /* decrement to match with the test_bit_depth enum definition */ + edid->color_depth--; pr_debug("Digital Video intf=%d color_depth=%d\n", edid->video_intf, edid->color_depth); } else { @@ -501,12 +499,14 @@ void dp_extract_edid_feature(struct edp_edid *edid, char *buf) edid->dpm, edid->color_format); }; -char mdss_dp_gen_link_clk(struct mdss_panel_info *pinfo, char lane_cnt) +char mdss_dp_gen_link_clk(struct mdss_dp_drv_pdata *dp) { const u32 encoding_factx10 = 8; const u32 ln_to_link_ratio = 10; u32 min_link_rate, reminder = 0; char calc_link_rate = 0; + struct mdss_panel_info *pinfo = &dp->panel_data.panel_info; + char lane_cnt = dp->dpcd.max_lane_count; pr_debug("clk_rate=%llu, bpp= %d, lane_cnt=%d\n", pinfo->clk_rate, pinfo->bpp, lane_cnt); @@ -545,6 +545,11 @@ char mdss_dp_gen_link_clk(struct mdss_panel_info *pinfo, char lane_cnt) calc_link_rate = DP_LINK_RATE_540; } + pr_debug("calc_link_rate=0x%x, Max rate supported by sink=0x%x\n", + dp->link_rate, dp->dpcd.max_link_rate); + if (calc_link_rate > dp->dpcd.max_link_rate) + calc_link_rate = dp->dpcd.max_link_rate; + pr_debug("calc_link_rate = 0x%x\n", calc_link_rate); return calc_link_rate; } @@ -1112,6 +1117,84 @@ bool mdss_dp_aux_is_lane_count_valid(u32 lane_count) (lane_count == DP_LANE_COUNT_4); } +int mdss_dp_aux_parse_vx_px(struct mdss_dp_drv_pdata *ep) +{ + char *bp; + char data; + struct edp_buf *rp; + int rlen; + int const param_len = 0x1; + int const addr1 = 0x206; + int const addr2 = 0x207; + int ret = 0; + u32 v0, p0, v1, p1, v2, p2, v3, p3; + + pr_info("Parsing DPCP for updated voltage and pre-emphasis levels\n"); + + rlen = dp_aux_read_buf(ep, addr1, param_len, 0); + if (rlen < param_len) { + pr_err("failed reading lanes 0/1\n"); + ret = -EINVAL; + goto end; + } + + rp = &ep->rxp; + bp = rp->data; + data = *bp++; + + pr_info("lanes 0/1 (Byte 0x206): 0x%x\n", data); + + v0 = data & 0x3; + data = data >> 2; + p0 = data & 0x3; + data = data >> 2; + + v1 = data & 0x3; + data = data >> 2; + p1 = data & 0x3; + data = data >> 2; + + rlen = dp_aux_read_buf(ep, addr2, param_len, 0); + if (rlen < param_len) { + pr_err("failed reading lanes 2/3\n"); + ret = -EINVAL; + goto end; + } + + rp = &ep->rxp; + bp = rp->data; + data = *bp++; + + pr_info("lanes 2/3 (Byte 0x207): 0x%x\n", data); + + v2 = data & 0x3; + data = data >> 2; + p2 = data & 0x3; + data = data >> 2; + + v3 = data & 0x3; + data = data >> 2; + p3 = data & 0x3; + data = data >> 2; + + pr_info("vx: 0=%d, 1=%d, 2=%d, 3=%d\n", v0, v1, v2, v3); + pr_info("px: 0=%d, 1=%d, 2=%d, 3=%d\n", p0, p1, p2, p3); + + /** + * Update the voltage and pre-emphasis levels as per DPCD request + * vector. + */ + pr_info("Current: v_level = 0x%x, p_level = 0x%x\n", + ep->v_level, ep->p_level); + pr_info("Requested: v_level = 0x%x, p_level = 0x%x\n", v0, p0); + ep->v_level = v0; + ep->p_level = p0; + + pr_info("Success\n"); +end: + return ret; +} + /** * dp_parse_link_training_params() - parses link training parameters from DPCD * @ep: Display Port Driver data @@ -1212,6 +1295,236 @@ static void dp_sink_parse_sink_count(struct mdss_dp_drv_pdata *ep) ep->sink_count.count, ep->sink_count.cp_ready); } +static int dp_get_test_period(struct mdss_dp_drv_pdata *ep, int const addr) +{ + int ret = 0; + char *bp; + char data; + struct edp_buf *rp; + int rlen; + int const test_parameter_len = 0x1; + int const max_audio_period = 0xA; + + /* TEST_AUDIO_PERIOD_CH_XX */ + rlen = dp_aux_read_buf(ep, addr, test_parameter_len, 0); + if (rlen < test_parameter_len) { + pr_err("failed to read test_audio_period (0x%x)\n", addr); + ret = -EINVAL; + goto exit; + } + rp = &ep->rxp; + bp = rp->data; + data = *bp++; + + /* Period - Bits 3:0 */ + data = data & 0xF; + if ((int)data > max_audio_period) { + pr_err("invalid test_audio_period_ch_1 = 0x%x\n", data); + ret = -EINVAL; + goto exit; + } + + ret = data; + +exit: + return ret; +} + +static int dp_parse_audio_channel_test_period(struct mdss_dp_drv_pdata *ep) +{ + int ret = 0; + int const test_audio_period_ch_1_addr = 0x273; + int const test_audio_period_ch_2_addr = 0x274; + int const test_audio_period_ch_3_addr = 0x275; + int const test_audio_period_ch_4_addr = 0x276; + int const test_audio_period_ch_5_addr = 0x277; + int const test_audio_period_ch_6_addr = 0x278; + int const test_audio_period_ch_7_addr = 0x279; + int const test_audio_period_ch_8_addr = 0x27A; + + /* TEST_AUDIO_PERIOD_CH_1 (Byte 0x273) */ + ret = dp_get_test_period(ep, test_audio_period_ch_1_addr); + if (ret == -EINVAL) + goto exit; + + ep->test_data.test_audio_period_ch_1 = ret; + pr_debug("test_audio_period_ch_1 = 0x%x\n", ret); + + /* TEST_AUDIO_PERIOD_CH_2 (Byte 0x274) */ + ret = dp_get_test_period(ep, test_audio_period_ch_2_addr); + if (ret == -EINVAL) + goto exit; + + ep->test_data.test_audio_period_ch_2 = ret; + pr_debug("test_audio_period_ch_2 = 0x%x\n", ret); + + /* TEST_AUDIO_PERIOD_CH_3 (Byte 0x275) */ + ret = dp_get_test_period(ep, test_audio_period_ch_3_addr); + if (ret == -EINVAL) + goto exit; + + ep->test_data.test_audio_period_ch_3 = ret; + pr_debug("test_audio_period_ch_3 = 0x%x\n", ret); + + /* TEST_AUDIO_PERIOD_CH_4 (Byte 0x276) */ + ret = dp_get_test_period(ep, test_audio_period_ch_4_addr); + if (ret == -EINVAL) + goto exit; + + ep->test_data.test_audio_period_ch_4 = ret; + pr_debug("test_audio_period_ch_4 = 0x%x\n", ret); + + /* TEST_AUDIO_PERIOD_CH_5 (Byte 0x277) */ + ret = dp_get_test_period(ep, test_audio_period_ch_5_addr); + if (ret == -EINVAL) + goto exit; + + ep->test_data.test_audio_period_ch_5 = ret; + pr_debug("test_audio_period_ch_5 = 0x%x\n", ret); + + /* TEST_AUDIO_PERIOD_CH_6 (Byte 0x278) */ + ret = dp_get_test_period(ep, test_audio_period_ch_6_addr); + if (ret == -EINVAL) + goto exit; + + ep->test_data.test_audio_period_ch_6 = ret; + pr_debug("test_audio_period_ch_6 = 0x%x\n", ret); + + /* TEST_AUDIO_PERIOD_CH_7 (Byte 0x279) */ + ret = dp_get_test_period(ep, test_audio_period_ch_7_addr); + if (ret == -EINVAL) + goto exit; + + ep->test_data.test_audio_period_ch_7 = ret; + pr_debug("test_audio_period_ch_7 = 0x%x\n", ret); + + /* TEST_AUDIO_PERIOD_CH_8 (Byte 0x27A) */ + ret = dp_get_test_period(ep, test_audio_period_ch_8_addr); + if (ret == -EINVAL) + goto exit; + + ep->test_data.test_audio_period_ch_8 = ret; + pr_debug("test_audio_period_ch_8 = 0x%x\n", ret); + + +exit: + return ret; +} + +static int dp_parse_audio_pattern_type(struct mdss_dp_drv_pdata *ep) +{ + int ret = 0; + char *bp; + char data; + struct edp_buf *rp; + int rlen; + int const test_parameter_len = 0x1; + int const test_audio_pattern_type_addr = 0x272; + int const max_audio_pattern_type = 0x1; + + /* Read the requested audio pattern type (Byte 0x272). */ + rlen = dp_aux_read_buf(ep, test_audio_pattern_type_addr, + test_parameter_len, 0); + if (rlen < test_parameter_len) { + pr_err("failed to read test audio mode data\n"); + ret = -EINVAL; + goto exit; + } + rp = &ep->rxp; + bp = rp->data; + data = *bp++; + + /* Audio Pattern Type - Bits 7:0 */ + if ((int)data > max_audio_pattern_type) { + pr_err("invalid audio pattern type = 0x%x\n", data); + ret = -EINVAL; + goto exit; + } + + ep->test_data.test_audio_pattern_type = data; + pr_debug("audio pattern type = %s\n", + mdss_dp_get_audio_test_pattern(data)); + +exit: + return ret; +} + +static int dp_parse_audio_mode(struct mdss_dp_drv_pdata *ep) +{ + int ret = 0; + char *bp; + char data; + struct edp_buf *rp; + int rlen; + int const test_parameter_len = 0x1; + int const test_audio_mode_addr = 0x271; + int const max_audio_sampling_rate = 0x6; + int const max_audio_channel_count = 0x8; + int sampling_rate = 0x0; + int channel_count = 0x0; + + /* Read the requested audio mode (Byte 0x271). */ + rlen = dp_aux_read_buf(ep, test_audio_mode_addr, + test_parameter_len, 0); + if (rlen < test_parameter_len) { + pr_err("failed to read test audio mode data\n"); + ret = -EINVAL; + goto exit; + } + rp = &ep->rxp; + bp = rp->data; + data = *bp++; + + /* Sampling Rate - Bits 3:0 */ + sampling_rate = data & 0xF; + if (sampling_rate > max_audio_sampling_rate) { + pr_err("sampling rate (0x%x) greater than max (0x%x)\n", + sampling_rate, max_audio_sampling_rate); + ret = -EINVAL; + goto exit; + } + + /* Channel Count - Bits 7:4 */ + channel_count = ((data & 0xF0) >> 4) + 1; + if (channel_count > max_audio_channel_count) { + pr_err("channel_count (0x%x) greater than max (0x%x)\n", + channel_count, max_audio_channel_count); + ret = -EINVAL; + goto exit; + } + + ep->test_data.test_audio_sampling_rate = sampling_rate; + ep->test_data.test_audio_channel_count = channel_count; + pr_debug("sampling_rate = %s, channel_count = 0x%x\n", + mdss_dp_get_audio_sample_rate(sampling_rate), channel_count); + +exit: + return ret; +} + +/** + * dp_parse_audio_pattern_params() - parses audio pattern parameters from DPCD + * @ep: Display Port Driver data + * + * Returns 0 if it successfully parses the audio test pattern parameters. + */ +static int dp_parse_audio_pattern_params(struct mdss_dp_drv_pdata *ep) +{ + int ret = 0; + + ret = dp_parse_audio_mode(ep); + if (ret) + goto exit; + + ret = dp_parse_audio_pattern_type(ep); + if (ret) + goto exit; + + ret = dp_parse_audio_channel_test_period(ep); + +exit: + return ret; +} /** * dp_parse_phy_test_params() - parses the phy test parameters * @ep: Display Port Driver data @@ -1227,7 +1540,6 @@ static int dp_parse_phy_test_params(struct mdss_dp_drv_pdata *ep) int rlen; int const param_len = 0x1; int const phy_test_pattern_addr = 0x248; - int const dpcd_version_1_2 = 0x12; int ret = 0; rlen = dp_aux_read_buf(ep, phy_test_pattern_addr, param_len, 0); @@ -1241,11 +1553,6 @@ static int dp_parse_phy_test_params(struct mdss_dp_drv_pdata *ep) bp = rp->data; data = *bp++; - if (ep->dpcd.major == dpcd_version_1_2) - data = data & 0x7; - else - data = data & 0x3; - ep->test_data.phy_test_pattern_sel = data; pr_debug("phy_test_pattern_sel = %s\n", @@ -1258,6 +1565,256 @@ end: return ret; } +static int dp_parse_test_timing_params1(struct mdss_dp_drv_pdata *ep, + int const addr, int const len, u32 *val) +{ + char *bp; + struct edp_buf *rp; + int rlen; + + if (len < 2) + return -EINVAL; + + /* Read the requested video test pattern (Byte 0x221). */ + rlen = dp_aux_read_buf(ep, addr, len, 0); + if (rlen < len) { + pr_err("failed to read 0x%x\n", addr); + return -EINVAL; + } + rp = &ep->rxp; + bp = rp->data; + + *val = bp[1] | (bp[0] << 8); + + return 0; +} + +static int dp_parse_test_timing_params2(struct mdss_dp_drv_pdata *ep, + int const addr, int const len, u32 *val1, u32 *val2) +{ + char *bp; + struct edp_buf *rp; + int rlen; + + if (len < 2) + return -EINVAL; + + /* Read the requested video test pattern (Byte 0x221). */ + rlen = dp_aux_read_buf(ep, addr, len, 0); + if (rlen < len) { + pr_err("failed to read 0x%x\n", addr); + return -EINVAL; + } + rp = &ep->rxp; + bp = rp->data; + + *val1 = (bp[0] & BIT(7)) >> 7; + *val2 = bp[1] | ((bp[0] & 0x7F) << 8); + + return 0; +} + +static int dp_parse_test_timing_params3(struct mdss_dp_drv_pdata *ep, + int const addr, u32 *val) +{ + char *bp; + struct edp_buf *rp; + int rlen; + + /* Read the requested video test pattern (Byte 0x221). */ + rlen = dp_aux_read_buf(ep, addr, 1, 0); + if (rlen < 1) { + pr_err("failed to read 0x%x\n", addr); + return -EINVAL; + } + rp = &ep->rxp; + bp = rp->data; + *val = bp[0]; + + return 0; +} + +/** + * dp_parse_video_pattern_params() - parses video pattern parameters from DPCD + * @ep: Display Port Driver data + * + * Returns 0 if it successfully parses the video test pattern and the test + * bit depth requested by the sink and, and if the values parsed are valid. + */ +static int dp_parse_video_pattern_params(struct mdss_dp_drv_pdata *ep) +{ + int ret = 0; + char *bp; + char data; + struct edp_buf *rp; + int rlen; + u32 dyn_range; + int const test_parameter_len = 0x1; + int const test_video_pattern_addr = 0x221; + int const test_misc_addr = 0x232; + + /* Read the requested video test pattern (Byte 0x221). */ + rlen = dp_aux_read_buf(ep, test_video_pattern_addr, + test_parameter_len, 0); + if (rlen < test_parameter_len) { + pr_err("failed to read test video pattern\n"); + ret = -EINVAL; + goto exit; + } + rp = &ep->rxp; + bp = rp->data; + data = *bp++; + + if (!mdss_dp_is_test_video_pattern_valid(data)) { + pr_err("invalid test video pattern = 0x%x\n", data); + ret = -EINVAL; + goto exit; + } + + ep->test_data.test_video_pattern = data; + pr_debug("test video pattern = 0x%x (%s)\n", + ep->test_data.test_video_pattern, + mdss_dp_test_video_pattern_to_string( + ep->test_data.test_video_pattern)); + + /* Read the requested color bit depth and dynamic range (Byte 0x232) */ + rlen = dp_aux_read_buf(ep, test_misc_addr, test_parameter_len, 0); + if (rlen < test_parameter_len) { + pr_err("failed to read test bit depth\n"); + ret = -EINVAL; + goto exit; + } + rp = &ep->rxp; + bp = rp->data; + data = *bp++; + + /* Dynamic Range */ + dyn_range = (data & BIT(3)) >> 3; + if (!mdss_dp_is_dynamic_range_valid(dyn_range)) { + pr_err("invalid test dynamic range = 0x%x", dyn_range); + ret = -EINVAL; + goto exit; + } + ep->test_data.test_dyn_range = dyn_range; + pr_debug("test dynamic range = 0x%x (%s)\n", + ep->test_data.test_dyn_range, + mdss_dp_dynamic_range_to_string(ep->test_data.test_dyn_range)); + + /* Color bit depth */ + data &= (BIT(5) | BIT(6) | BIT(7)); + data >>= 5; + if (!mdss_dp_is_test_bit_depth_valid(data)) { + pr_err("invalid test bit depth = 0x%x\n", data); + ret = -EINVAL; + goto exit; + } + + ep->test_data.test_bit_depth = data; + pr_debug("test bit depth = 0x%x (%s)\n", + ep->test_data.test_bit_depth, + mdss_dp_test_bit_depth_to_string(ep->test_data.test_bit_depth)); + + /* resolution timing params */ + ret = dp_parse_test_timing_params1(ep, 0x222, 2, + &ep->test_data.test_h_total); + if (ret) { + pr_err("failed to parse test_h_total (0x222)\n"); + goto exit; + } + pr_debug("TEST_H_TOTAL = %d\n", ep->test_data.test_h_total); + + ret = dp_parse_test_timing_params1(ep, 0x224, 2, + &ep->test_data.test_v_total); + if (ret) { + pr_err("failed to parse test_v_total (0x224)\n"); + goto exit; + } + pr_debug("TEST_V_TOTAL = %d\n", ep->test_data.test_v_total); + + ret = dp_parse_test_timing_params1(ep, 0x226, 2, + &ep->test_data.test_h_start); + if (ret) { + pr_err("failed to parse test_h_start (0x226)\n"); + goto exit; + } + pr_debug("TEST_H_START = %d\n", ep->test_data.test_h_start); + + ret = dp_parse_test_timing_params1(ep, 0x228, 2, + &ep->test_data.test_v_start); + if (ret) { + pr_err("failed to parse test_v_start (0x228)\n"); + goto exit; + } + pr_debug("TEST_V_START = %d\n", ep->test_data.test_v_start); + + ret = dp_parse_test_timing_params2(ep, 0x22A, 2, + &ep->test_data.test_hsync_pol, + &ep->test_data.test_hsync_width); + if (ret) { + pr_err("failed to parse (0x22A)\n"); + goto exit; + } + pr_debug("TEST_HSYNC_POL = %d\n", ep->test_data.test_hsync_pol); + pr_debug("TEST_HSYNC_WIDTH = %d\n", ep->test_data.test_hsync_width); + + ret = dp_parse_test_timing_params2(ep, 0x22C, 2, + &ep->test_data.test_vsync_pol, + &ep->test_data.test_vsync_width); + if (ret) { + pr_err("failed to parse (0x22C)\n"); + goto exit; + } + pr_debug("TEST_VSYNC_POL = %d\n", ep->test_data.test_vsync_pol); + pr_debug("TEST_VSYNC_WIDTH = %d\n", ep->test_data.test_vsync_width); + + ret = dp_parse_test_timing_params1(ep, 0x22E, 2, + &ep->test_data.test_h_width); + if (ret) { + pr_err("failed to parse test_h_width (0x22E)\n"); + goto exit; + } + pr_debug("TEST_H_WIDTH = %d\n", ep->test_data.test_h_width); + + ret = dp_parse_test_timing_params1(ep, 0x230, 2, + &ep->test_data.test_v_height); + if (ret) { + pr_err("failed to parse test_v_height (0x230)\n"); + goto exit; + } + pr_debug("TEST_V_HEIGHT = %d\n", ep->test_data.test_v_height); + + ret = dp_parse_test_timing_params3(ep, 0x233, &ep->test_data.test_rr_d); + ep->test_data.test_rr_d &= BIT(0); + if (ret) { + pr_err("failed to parse test_rr_d (0x233)\n"); + goto exit; + } + pr_debug("TEST_REFRESH_DENOMINATOR = %d\n", ep->test_data.test_rr_d); + + ret = dp_parse_test_timing_params3(ep, 0x234, &ep->test_data.test_rr_n); + if (ret) { + pr_err("failed to parse test_rr_n (0x234)\n"); + goto exit; + } + pr_debug("TEST_REFRESH_NUMERATOR = %d\n", ep->test_data.test_rr_n); + +exit: + return ret; +} + +/** + * mdss_dp_is_video_audio_test_requested() - checks for audio/video test request + * @test: test requested by the sink + * + * Returns true if the requested test is a permitted audio/video test. + */ +static bool mdss_dp_is_video_audio_test_requested(u32 test) +{ + return (test == TEST_VIDEO_PATTERN) || + (test == (TEST_AUDIO_PATTERN | TEST_VIDEO_PATTERN)) || + (test == TEST_AUDIO_PATTERN) || + (test == (TEST_AUDIO_PATTERN | TEST_AUDIO_DISABLED_VIDEO)); +} /** * dp_is_test_supported() - checks if test requested by sink is supported @@ -1269,7 +1826,8 @@ static bool dp_is_test_supported(u32 test_requested) { return (test_requested == TEST_LINK_TRAINING) || (test_requested == TEST_EDID_READ) || - (test_requested == PHY_TEST_PATTERN); + (test_requested == PHY_TEST_PATTERN) || + mdss_dp_is_video_audio_test_requested(test_requested); } /** @@ -1289,6 +1847,7 @@ static void dp_sink_parse_test_request(struct mdss_dp_drv_pdata *ep) int const test_parameter_len = 0x1; int const device_service_irq_addr = 0x201; int const test_request_addr = 0x218; + char buf[4]; /** * Read the device service IRQ vector (Byte 0x201) to determine @@ -1330,23 +1889,31 @@ static void dp_sink_parse_test_request(struct mdss_dp_drv_pdata *ep) return; } - pr_debug("%s requested\n", mdss_dp_get_test_name(data)); + pr_debug("%s (0x%x) requested\n", mdss_dp_get_test_name(data), data); ep->test_data.test_requested = data; - switch (ep->test_data.test_requested) { - case PHY_TEST_PATTERN: + if (ep->test_data.test_requested == PHY_TEST_PATTERN) { ret = dp_parse_phy_test_params(ep); if (ret) - break; - case TEST_LINK_TRAINING: + goto end; ret = dp_parse_link_training_params(ep); - break; - default: - pr_debug("test 0x%x not supported\n", - ep->test_data.test_requested); - return; } + if (ep->test_data.test_requested == TEST_LINK_TRAINING) + ret = dp_parse_link_training_params(ep); + + if (mdss_dp_is_video_audio_test_requested( + ep->test_data.test_requested)) { + ret = dp_parse_video_pattern_params(ep); + if (ret) + goto end; + ret = dp_parse_audio_pattern_params(ep); + } +end: + /* clear the test request IRQ */ + buf[0] = 1; + dp_aux_write_buf(ep, test_request_addr, buf, 1, 0); + /** * Send a TEST_ACK if all test parameters are valid, otherwise send * a TEST_NACK. @@ -1568,7 +2135,7 @@ char vm_voltage_swing[4][4] = { {0xFF, 0xFF, 0xFF, 0xFF} /* sw1, 1.2 v, optional */ }; -static void dp_aux_set_voltage_and_pre_emphasis_lvl( +void mdss_dp_aux_update_voltage_and_pre_emphasis_lvl( struct mdss_dp_drv_pdata *dp) { u32 value0 = 0; @@ -1605,45 +2172,13 @@ static void dp_aux_set_voltage_and_pre_emphasis_lvl( QSERDES_TX1_OFFSET + TXn_TX_EMP_POST1_LVL, value1); - pr_debug("value0=0x%x value1=0x%x", + pr_debug("host PHY settings: value0=0x%x value1=0x%x", value0, value1); dp_lane_set_write(dp, dp->v_level, dp->p_level); } } -/** - * mdss_dp_aux_update_voltage_and_pre_emphasis_lvl() - updates DP PHY settings - * @ep: Display Port Driver data - * - * Updates the DP PHY with the requested voltage swing and pre-emphasis - * levels if they are different from the current settings. - */ -void mdss_dp_aux_update_voltage_and_pre_emphasis_lvl( - struct mdss_dp_drv_pdata *dp) -{ - int const num_bytes = 6; - struct dpcd_link_status *status = &dp->link_status; - - /* Read link status for updated voltage and pre-emphasis levels. */ - mdss_dp_aux_link_status_read(dp, num_bytes); - - pr_info("Current: v_level = %d, p_level = %d\n", - dp->v_level, dp->p_level); - pr_info("Requested: v_level = %d, p_level = %d\n", - status->req_voltage_swing[0], - status->req_pre_emphasis[0]); - - if ((status->req_voltage_swing[0] != dp->v_level) || - (status->req_pre_emphasis[0] != dp->p_level)) { - dp->v_level = status->req_voltage_swing[0]; - dp->p_level = status->req_pre_emphasis[0]; - - dp_aux_set_voltage_and_pre_emphasis_lvl(dp); - } - - pr_debug("end\n"); -} static int dp_start_link_train_1(struct mdss_dp_drv_pdata *ep) { int tries, old_v_level; @@ -1653,10 +2188,14 @@ static int dp_start_link_train_1(struct mdss_dp_drv_pdata *ep) pr_debug("Entered++"); + dp_write(ep->base + DP_STATE_CTRL, 0x0); + /* Make sure to clear the current pattern before starting a new one */ + wmb(); + + dp_host_train_set(ep, 0x01); /* train_1 */ dp_cap_lane_rate_set(ep); dp_train_pattern_set_write(ep, 0x21); /* train_1 */ - dp_aux_set_voltage_and_pre_emphasis_lvl(ep); - dp_host_train_set(ep, 0x01); /* train_1 */ + mdss_dp_aux_update_voltage_and_pre_emphasis_lvl(ep); tries = 0; old_v_level = ep->v_level; @@ -1687,7 +2226,7 @@ static int dp_start_link_train_1(struct mdss_dp_drv_pdata *ep) } dp_sink_train_set_adjust(ep); - dp_aux_set_voltage_and_pre_emphasis_lvl(ep); + mdss_dp_aux_update_voltage_and_pre_emphasis_lvl(ep); } return ret; @@ -1708,11 +2247,15 @@ static int dp_start_link_train_2(struct mdss_dp_drv_pdata *ep) else pattern = 0x02; + dp_write(ep->base + DP_STATE_CTRL, 0x0); + /* Make sure to clear the current pattern before starting a new one */ + wmb(); + + dp_host_train_set(ep, pattern); + mdss_dp_aux_update_voltage_and_pre_emphasis_lvl(ep); dp_train_pattern_set_write(ep, pattern | 0x20);/* train_2 */ do { - dp_host_train_set(ep, pattern); - usleep_time = ep->dpcd.training_read_interval; usleep_range(usleep_time, usleep_time); @@ -1723,14 +2266,14 @@ static int dp_start_link_train_2(struct mdss_dp_drv_pdata *ep) break; } - tries++; if (tries > maximum_retries) { ret = -1; break; } + tries++; dp_sink_train_set_adjust(ep); - dp_aux_set_voltage_and_pre_emphasis_lvl(ep); + mdss_dp_aux_update_voltage_and_pre_emphasis_lvl(ep); } while (1); return ret; @@ -1787,25 +2330,20 @@ int mdss_dp_link_train(struct mdss_dp_drv_pdata *dp) ret = dp_aux_chan_ready(dp); if (ret) { pr_err("LINK Train failed: aux chan NOT ready\n"); - complete(&dp->train_comp); return ret; } - dp_write(dp->base + DP_MAINLINK_CTRL, 0x1); - - mdss_dp_aux_set_sink_power_state(dp, SINK_POWER_ON); - dp->v_level = 0; /* start from default level */ dp->p_level = 0; mdss_dp_config_ctrl(dp); mdss_dp_state_ctrl(&dp->ctrl_io, 0); - dp_clear_training_pattern(dp); ret = dp_start_link_train_1(dp); if (ret < 0) { if (!dp_link_rate_down_shift(dp)) { pr_debug("retry with lower rate\n"); + dp_clear_training_pattern(dp); return -EAGAIN; } else { pr_err("Training 1 failed\n"); @@ -1816,10 +2354,15 @@ int mdss_dp_link_train(struct mdss_dp_drv_pdata *dp) pr_debug("Training 1 completed successfully\n"); + dp_write(dp->base + DP_STATE_CTRL, 0x0); + /* Make sure to clear the current pattern before starting a new one */ + wmb(); + ret = dp_start_link_train_2(dp); if (ret < 0) { if (!dp_link_rate_down_shift(dp)) { pr_debug("retry with lower rate\n"); + dp_clear_training_pattern(dp); return -EAGAIN; } else { pr_err("Training 2 failed\n"); @@ -1830,19 +2373,13 @@ int mdss_dp_link_train(struct mdss_dp_drv_pdata *dp) pr_debug("Training 2 completed successfully\n"); + dp_write(dp->base + DP_STATE_CTRL, 0x0); + /* Make sure to clear the current pattern before starting a new one */ + wmb(); + clear: dp_clear_training_pattern(dp); - if (ret != -EINVAL) { - mdss_dp_config_misc_settings(&dp->ctrl_io, - &dp->panel_data.panel_info); - mdss_dp_setup_tr_unit(&dp->ctrl_io, dp->link_rate, - dp->lane_cnt, dp->vic, - &dp->panel_data.panel_info); - mdss_dp_state_ctrl(&dp->ctrl_io, ST_SEND_VIDEO); - pr_debug("State_ctrl set to SEND_VIDEO\n"); - } - complete(&dp->train_comp); return ret; } @@ -1885,15 +2422,129 @@ void mdss_dp_fill_link_cfg(struct mdss_dp_drv_pdata *ep) } +/** + * mdss_dp_aux_config_sink_frame_crc() - enable/disable per frame CRC calc + * @dp: Display Port Driver data + * @enable: true - start CRC calculation, false - stop CRC calculation + * + * Program the sink DPCD register 0x270 to start/stop CRC calculation. + * This would take effect with the next frame. + */ +int mdss_dp_aux_config_sink_frame_crc(struct mdss_dp_drv_pdata *dp, + bool enable) +{ + int rlen; + struct edp_buf *rp; + u8 *bp; + u8 buf[4]; + u8 crc_supported; + u32 const test_sink_addr = 0x270; + u32 const test_sink_misc_addr = 0x246; + + if (dp->sink_crc.en == enable) { + pr_debug("sink crc already %s\n", + enable ? "enabled" : "disabled"); + return 0; + } + + rlen = dp_aux_read_buf(dp, test_sink_misc_addr, 1, 0); + if (rlen < 1) { + pr_err("failed to TEST_SINK_ADDR\n"); + return -EPERM; + } + rp = &dp->rxp; + bp = rp->data; + crc_supported = bp[0] & BIT(5); + pr_debug("crc supported=%s\n", crc_supported ? "true" : "false"); + + if (!crc_supported) { + pr_err("sink does not support CRC generation\n"); + return -EINVAL; + } + + buf[0] = enable ? 1 : 0; + dp_aux_write_buf(dp, test_sink_addr, buf, BIT(0), 0); + + if (!enable) + mdss_dp_reset_frame_crc_data(&dp->sink_crc); + dp->sink_crc.en = enable; + pr_debug("TEST_SINK_START (CRC calculation) %s\n", + enable ? "enabled" : "disabled"); + + return 0; +} + +/** + * mdss_dp_aux_read_sink_frame_crc() - read frame CRC values from the sink + * @dp: Display Port Driver data + */ +int mdss_dp_aux_read_sink_frame_crc(struct mdss_dp_drv_pdata *dp) +{ + int rlen; + struct edp_buf *rp; + u8 *bp; + u32 addr, len; + struct mdss_dp_crc_data *crc = &dp->sink_crc; + + addr = 0x270; /* TEST_SINK */ + len = 1; /* one byte */ + rlen = dp_aux_read_buf(dp, addr, len, 0); + if (rlen < len) { + pr_err("failed to read TEST SINK\n"); + return -EPERM; + } + rp = &dp->rxp; + bp = rp->data; + if (!(bp[0] & BIT(0))) { + pr_err("Sink side CRC calculation not enabled, TEST_SINK=0x%08x\n", + (u32)bp[0]); + return -EINVAL; + } + + addr = 0x240; /* TEST_CRC_R_Cr */ + len = 2; /* 2 bytes */ + rlen = dp_aux_read_buf(dp, addr, len, 0); + if (rlen < len) { + pr_err("failed to read TEST_CRC_R_Cr\n"); + return -EPERM; + } + rp = &dp->rxp; + bp = rp->data; + crc->r_cr = bp[0] | (bp[1] << 8); + + addr = 0x242; /* TEST_CRC_G_Y */ + len = 2; /* 2 bytes */ + rlen = dp_aux_read_buf(dp, addr, len, 0); + if (rlen < len) { + pr_err("failed to read TEST_CRC_G_Y\n"); + return -EPERM; + } + rp = &dp->rxp; + bp = rp->data; + crc->g_y = bp[0] | (bp[1] << 8); + + addr = 0x244; /* TEST_CRC_B_Cb */ + len = 2; /* 2 bytes */ + rlen = dp_aux_read_buf(dp, addr, len, 0); + if (rlen < len) { + pr_err("failed to read TEST_CRC_B_Cb\n"); + return -EPERM; + } + rp = &dp->rxp; + bp = rp->data; + crc->b_cb = bp[0] | (bp[1] << 8); + + pr_debug("r_cr=0x%08x\t g_y=0x%08x\t b_cb=0x%08x\n", + crc->r_cr, crc->g_y, crc->b_cb); + + return 0; +} + void mdss_dp_aux_init(struct mdss_dp_drv_pdata *ep) { - mutex_init(&ep->aux_mutex); - mutex_init(&ep->train_mutex); - init_completion(&ep->aux_comp); - init_completion(&ep->train_comp); - init_completion(&ep->idle_comp); - init_completion(&ep->video_comp); - complete(&ep->train_comp); /* make non block at first time */ + reinit_completion(&ep->aux_comp); + reinit_completion(&ep->idle_comp); + reinit_completion(&ep->video_comp); complete(&ep->video_comp); /* make non block at first time */ dp_buf_init(&ep->txp, ep->txbuf, sizeof(ep->txbuf)); diff --git a/drivers/video/fbdev/msm/mdss_dp_hdcp2p2.c b/drivers/video/fbdev/msm/mdss_dp_hdcp2p2.c index 73b9ad65482f..3bcacf945761 100644 --- a/drivers/video/fbdev/msm/mdss_dp_hdcp2p2.c +++ b/drivers/video/fbdev/msm/mdss_dp_hdcp2p2.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -370,6 +370,8 @@ static void dp_hdcp2p2_auth_failed(struct dp_hdcp2p2_ctrl *ctrl) dp_hdcp2p2_set_interrupts(ctrl, false); + atomic_set(&ctrl->auth_state, HDCP_STATE_AUTH_FAIL); + /* notify DP about HDCP failure */ ctrl->init_data.notify_status(ctrl->init_data.cb_data, HDCP_STATE_AUTH_FAIL); @@ -625,6 +627,12 @@ static void dp_hdcp2p2_link_work(struct kthread_work *work) return; } + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_AUTH_FAIL || + atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("invalid hdcp state\n"); + return; + } + cdata.context = ctrl->lib_ctx; if (ctrl->sink_rx_status & ctrl->abort_mask) { @@ -685,6 +693,13 @@ static int dp_hdcp2p2_cp_irq(void *input) return -EINVAL; } + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_AUTH_FAIL || + atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("invalid hdcp state\n"); + rc = -EINVAL; + goto error; + } + ctrl->sink_rx_status = 0; rc = mdss_dp_aux_read_rx_status(ctrl->init_data.cb_data, &ctrl->sink_rx_status); diff --git a/drivers/video/fbdev/msm/mdss_dp_util.c b/drivers/video/fbdev/msm/mdss_dp_util.c index a7f42ba8c261..1dcf83f094c1 100644 --- a/drivers/video/fbdev/msm/mdss_dp_util.c +++ b/drivers/video/fbdev/msm/mdss_dp_util.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -202,6 +202,45 @@ void mdss_dp_configuration_ctrl(struct dss_io_data *ctrl_io, u32 data) writel_relaxed(data, ctrl_io->base + DP_CONFIGURATION_CTRL); } +void mdss_dp_config_ctl_frame_crc(struct mdss_dp_drv_pdata *dp, bool enable) +{ + if (dp->ctl_crc.en == enable) { + pr_debug("CTL crc already %s\n", + enable ? "enabled" : "disabled"); + return; + } + + writel_relaxed(BIT(8), dp->ctrl_io.base + MMSS_DP_TIMING_ENGINE_EN); + if (!enable) + mdss_dp_reset_frame_crc_data(&dp->ctl_crc); + dp->ctl_crc.en = enable; + + pr_debug("CTL crc %s\n", enable ? "enabled" : "disabled"); +} + +int mdss_dp_read_ctl_frame_crc(struct mdss_dp_drv_pdata *dp) +{ + u32 data; + u32 crc_rg = 0; + struct mdss_dp_crc_data *crc = &dp->ctl_crc; + + data = readl_relaxed(dp->ctrl_io.base + MMSS_DP_TIMING_ENGINE_EN); + if (!(data & BIT(8))) { + pr_debug("frame CRC calculation not enabled\n"); + return -EPERM; + } + + crc_rg = readl_relaxed(dp->ctrl_io.base + MMSS_DP_PSR_CRC_RG); + crc->r_cr = crc_rg & 0xFFFF; + crc->g_y = crc_rg >> 16; + crc->b_cb = readl_relaxed(dp->ctrl_io.base + MMSS_DP_PSR_CRC_B); + + pr_debug("r_cr=0x%08x\t g_y=0x%08x\t b_cb=0x%08x\n", + crc->r_cr, crc->g_y, crc->b_cb); + + return 0; +} + /* DP state controller*/ void mdss_dp_state_ctrl(struct dss_io_data *ctrl_io, u32 data) { @@ -735,7 +774,9 @@ void mdss_dp_timing_cfg(struct dss_io_data *ctrl_io, data = pinfo->lcdc.v_pulse_width; data <<= 16; + data |= (pinfo->lcdc.v_active_low << 31); data |= pinfo->lcdc.h_pulse_width; + data |= (pinfo->lcdc.h_active_low << 15); /* DP_HSYNC_VSYNC_WIDTH_POLARITY */ writel_relaxed(data, ctrl_io->base + DP_HSYNC_VSYNC_WIDTH_POLARITY); @@ -765,28 +806,15 @@ void mdss_dp_sw_config_msa(struct dss_io_data *ctrl_io, writel_relaxed(nvid, ctrl_io->base + DP_SOFTWARE_NVID); } -void mdss_dp_config_misc_settings(struct dss_io_data *ctrl_io, - struct mdss_panel_info *pinfo) +void mdss_dp_config_misc(struct mdss_dp_drv_pdata *dp, u32 bd, u32 cc) { - u32 bpp = pinfo->bpp; - u32 misc_val = 0x0; - - switch (bpp) { - case 18: - misc_val |= (0x0 << 5); - break; - case 30: - misc_val |= (0x2 << 5); - break; - case 24: - default: - misc_val |= (0x1 << 5); - } + u32 misc_val = cc; + misc_val |= (bd << 5); misc_val |= BIT(0); /* Configure clock to synchronous mode */ pr_debug("Misc settings = 0x%x\n", misc_val); - writel_relaxed(misc_val, ctrl_io->base + DP_MISC1_MISC0); + writel_relaxed(misc_val, dp->ctrl_io.base + DP_MISC1_MISC0); } void mdss_dp_setup_tr_unit(struct dss_io_data *ctrl_io, u8 link_rate, @@ -1113,7 +1141,8 @@ static u8 mdss_dp_calculate_parity_byte(u32 data) return parityByte; } -static void mdss_dp_audio_setup_audio_stream_sdp(struct dss_io_data *ctrl_io) +static void mdss_dp_audio_setup_audio_stream_sdp(struct dss_io_data *ctrl_io, + u32 num_of_channels) { u32 value = 0; u32 new_value = 0; @@ -1131,7 +1160,7 @@ static void mdss_dp_audio_setup_audio_stream_sdp(struct dss_io_data *ctrl_io) /* Config header and parity byte 2 */ value = readl_relaxed(ctrl_io->base + MMSS_DP_AUDIO_STREAM_1); - new_value = 0x0; + new_value = value; parity_byte = mdss_dp_calculate_parity_byte(new_value); value |= ((new_value << HEADER_BYTE_2_BIT) | (parity_byte << PARITY_BYTE_2_BIT)); @@ -1141,7 +1170,7 @@ static void mdss_dp_audio_setup_audio_stream_sdp(struct dss_io_data *ctrl_io) /* Config header and parity byte 3 */ value = readl_relaxed(ctrl_io->base + MMSS_DP_AUDIO_STREAM_1); - new_value = 0x01; + new_value = num_of_channels - 1; parity_byte = mdss_dp_calculate_parity_byte(new_value); value |= ((new_value << HEADER_BYTE_3_BIT) | (parity_byte << PARITY_BYTE_3_BIT)); @@ -1179,7 +1208,7 @@ static void mdss_dp_audio_setup_audio_timestamp_sdp(struct dss_io_data *ctrl_io) /* Config header and parity byte 3 */ value = readl_relaxed(ctrl_io->base + MMSS_DP_AUDIO_TIMESTAMP_1); - new_value = (0x0 | (0x12 << 2)); + new_value = (0x0 | (0x11 << 2)); parity_byte = mdss_dp_calculate_parity_byte(new_value); value |= ((new_value << HEADER_BYTE_3_BIT) | (parity_byte << PARITY_BYTE_3_BIT)); @@ -1216,7 +1245,7 @@ static void mdss_dp_audio_setup_audio_infoframe_sdp(struct dss_io_data *ctrl_io) /* Config header and parity byte 3 */ value = readl_relaxed(ctrl_io->base + MMSS_DP_AUDIO_INFOFRAME_1); - new_value = (0x0 | (0x12 << 2)); + new_value = (0x0 | (0x11 << 2)); parity_byte = mdss_dp_calculate_parity_byte(new_value); value |= ((new_value << HEADER_BYTE_3_BIT) | (parity_byte << PARITY_BYTE_3_BIT)); @@ -1309,7 +1338,7 @@ static void mdss_dp_audio_setup_isrc_sdp(struct dss_io_data *ctrl_io) writel_relaxed(0x0, ctrl_io->base + MMSS_DP_AUDIO_ISRC_4); } -void mdss_dp_audio_setup_sdps(struct dss_io_data *ctrl_io) +void mdss_dp_audio_setup_sdps(struct dss_io_data *ctrl_io, u32 num_of_channels) { u32 sdp_cfg = 0; u32 sdp_cfg2 = 0; @@ -1335,7 +1364,7 @@ void mdss_dp_audio_setup_sdps(struct dss_io_data *ctrl_io) writel_relaxed(sdp_cfg2, ctrl_io->base + MMSS_DP_SDP_CFG2); - mdss_dp_audio_setup_audio_stream_sdp(ctrl_io); + mdss_dp_audio_setup_audio_stream_sdp(ctrl_io, num_of_channels); mdss_dp_audio_setup_audio_timestamp_sdp(ctrl_io); mdss_dp_audio_setup_audio_infoframe_sdp(ctrl_io); mdss_dp_audio_setup_copy_management_sdp(ctrl_io); @@ -1366,7 +1395,7 @@ void mdss_dp_set_safe_to_exit_level(struct dss_io_data *ctrl_io, } mainlink_levels = readl_relaxed(ctrl_io->base + DP_MAINLINK_LEVELS); - mainlink_levels &= 0xFF0; + mainlink_levels &= 0xFE0; mainlink_levels |= safe_to_exit_level; pr_debug("mainlink_level = 0x%x, safe_to_exit_level = 0x%x\n", @@ -1406,28 +1435,17 @@ void mdss_dp_phy_send_test_pattern(struct mdss_dp_drv_pdata *dp) return; } - /* Disable mainlink */ - writel_relaxed(0x0, io->base + DP_MAINLINK_CTRL); - - /* Reset mainlink */ - mdss_dp_mainlink_reset(io); - - /* Enable mainlink */ - writel_relaxed(0x0, io->base + DP_MAINLINK_CTRL); - /* Initialize DP state control */ - mdss_dp_state_ctrl(io, 0x00); + writel_relaxed(0x0, io->base + DP_STATE_CTRL); pr_debug("phy_test_pattern_sel = %s\n", mdss_dp_get_phy_test_pattern(phy_test_pattern_sel)); switch (phy_test_pattern_sel) { case PHY_TEST_PATTERN_D10_2_NO_SCRAMBLING: - mdss_dp_state_ctrl(io, BIT(0)); + writel_relaxed(0x1, io->base + DP_STATE_CTRL); break; case PHY_TEST_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT: - value = readl_relaxed(io->base + - DP_HBR2_COMPLIANCE_SCRAMBLER_RESET); value &= ~(1 << 16); writel_relaxed(value, io->base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET); @@ -1438,7 +1456,7 @@ void mdss_dp_phy_send_test_pattern(struct mdss_dp_drv_pdata *dp) mdss_dp_state_ctrl(io, BIT(4)); break; case PHY_TEST_PATTERN_PRBS7: - mdss_dp_state_ctrl(io, BIT(5)); + writel_relaxed(0x20, io->base + DP_STATE_CTRL); break; case PHY_TEST_PATTERN_80_BIT_CUSTOM_PATTERN: mdss_dp_state_ctrl(io, BIT(6)); @@ -1453,9 +1471,7 @@ void mdss_dp_phy_send_test_pattern(struct mdss_dp_drv_pdata *dp) DP_TEST_80BIT_CUSTOM_PATTERN_REG2); break; case PHY_TEST_PATTERN_HBR2_CTS_EYE_PATTERN: - value = readl_relaxed(io->base + - DP_HBR2_COMPLIANCE_SCRAMBLER_RESET); - value |= BIT(16); + value = BIT(16); writel_relaxed(value, io->base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET); value |= 0xFC; @@ -1469,4 +1485,8 @@ void mdss_dp_phy_send_test_pattern(struct mdss_dp_drv_pdata *dp) phy_test_pattern_sel); return; } + + value = 0x0; + value = readl_relaxed(io->base + DP_MAINLINK_READY); + pr_info("DP_MAINLINK_READY = 0x%x\n", value); } diff --git a/drivers/video/fbdev/msm/mdss_dp_util.h b/drivers/video/fbdev/msm/mdss_dp_util.h index fcb9a77db67c..cb62d145960f 100644 --- a/drivers/video/fbdev/msm/mdss_dp_util.h +++ b/drivers/video/fbdev/msm/mdss_dp_util.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -65,6 +65,7 @@ #define DP_TEST_80BIT_CUSTOM_PATTERN_REG1 (0x000004C4) #define DP_TEST_80BIT_CUSTOM_PATTERN_REG2 (0x000004C8) +#define MMSS_DP_MISC1_MISC0 (0x0000042C) #define MMSS_DP_AUDIO_TIMING_GEN (0x00000480) #define MMSS_DP_AUDIO_TIMING_RBR_32 (0x00000484) #define MMSS_DP_AUDIO_TIMING_HBR_32 (0x00000488) @@ -73,6 +74,9 @@ #define MMSS_DP_AUDIO_TIMING_RBR_48 (0x00000494) #define MMSS_DP_AUDIO_TIMING_HBR_48 (0x00000498) +#define MMSS_DP_PSR_CRC_RG (0x00000554) +#define MMSS_DP_PSR_CRC_B (0x00000558) + #define MMSS_DP_AUDIO_CFG (0x00000600) #define MMSS_DP_AUDIO_STATUS (0x00000604) #define MMSS_DP_AUDIO_PKT_CTRL (0x00000608) @@ -135,6 +139,8 @@ #define MMSS_DP_GENERIC1_8 (0x00000748) #define MMSS_DP_GENERIC1_9 (0x0000074C) +#define MMSS_DP_TIMING_ENGINE_EN (0x00000A10) + /*DP PHY Register offsets */ #define DP_PHY_REVISION_ID0 (0x00000000) #define DP_PHY_REVISION_ID1 (0x00000004) @@ -285,8 +291,7 @@ void mdss_dp_switch_usb3_phy_to_dp_mode(struct dss_io_data *tcsr_reg_io); void mdss_dp_assert_phy_reset(struct dss_io_data *ctrl_io, bool assert); void mdss_dp_setup_tr_unit(struct dss_io_data *ctrl_io, u8 link_rate, u8 ln_cnt, u32 res, struct mdss_panel_info *pinfo); -void mdss_dp_config_misc_settings(struct dss_io_data *ctrl_io, - struct mdss_panel_info *pinfo); +void mdss_dp_config_misc(struct mdss_dp_drv_pdata *dp, u32 bd, u32 cc); void mdss_dp_phy_aux_setup(struct dss_io_data *phy_io); void mdss_dp_hpd_configure(struct dss_io_data *ctrl_io, bool enable); void mdss_dp_aux_ctrl(struct dss_io_data *ctrl_io, bool enable); @@ -312,7 +317,7 @@ void mdss_dp_phy_share_lane_config(struct dss_io_data *phy_io, u8 orientation, u8 ln_cnt); void mdss_dp_config_audio_acr_ctrl(struct dss_io_data *ctrl_io, char link_rate); -void mdss_dp_audio_setup_sdps(struct dss_io_data *ctrl_io); +void mdss_dp_audio_setup_sdps(struct dss_io_data *ctrl_io, u32 num_of_channels); void mdss_dp_audio_enable(struct dss_io_data *ctrl_io, bool enable); void mdss_dp_audio_select_core(struct dss_io_data *ctrl_io); void mdss_dp_audio_set_sample_rate(struct dss_io_data *ctrl_io, @@ -321,5 +326,7 @@ void mdss_dp_set_safe_to_exit_level(struct dss_io_data *ctrl_io, uint32_t lane_cnt); int mdss_dp_aux_read_rx_status(struct mdss_dp_drv_pdata *dp, u8 *rx_status); void mdss_dp_phy_send_test_pattern(struct mdss_dp_drv_pdata *dp); +void mdss_dp_config_ctl_frame_crc(struct mdss_dp_drv_pdata *dp, bool enable); +int mdss_dp_read_ctl_frame_crc(struct mdss_dp_drv_pdata *dp); #endif /* __DP_UTIL_H__ */ diff --git a/drivers/video/fbdev/msm/mdss_hdcp_1x.c b/drivers/video/fbdev/msm/mdss_hdcp_1x.c index 08307fe8eb16..834726e84bda 100644 --- a/drivers/video/fbdev/msm/mdss_hdcp_1x.c +++ b/drivers/video/fbdev/msm/mdss_hdcp_1x.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016 The Linux Foundation. All rights reserved. +/* Copyright (c) 2010-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -643,7 +643,8 @@ static int hdcp_1x_wait_for_hw_ready(struct hdcp_1x *hdcp) /* Wait for HDCP keys to be checked and validated */ rc = readl_poll_timeout(io->base + reg_set->status, link0_status, ((link0_status >> reg_set->keys_offset) & 0x7) - == HDCP_KEYS_STATE_VALID, + == HDCP_KEYS_STATE_VALID || + !hdcp_1x_state(HDCP_STATE_AUTHENTICATING), HDCP_POLL_SLEEP_US, HDCP_POLL_TIMEOUT_US); if (IS_ERR_VALUE(rc)) { pr_err("key not ready\n"); @@ -658,7 +659,8 @@ static int hdcp_1x_wait_for_hw_ready(struct hdcp_1x *hdcp) /* Wait for An0 and An1 bit to be ready */ rc = readl_poll_timeout(io->base + reg_set->status, link0_status, - (link0_status & (BIT(8) | BIT(9))), + (link0_status & (BIT(8) | BIT(9))) || + !hdcp_1x_state(HDCP_STATE_AUTHENTICATING), HDCP_POLL_SLEEP_US, HDCP_POLL_TIMEOUT_US); if (IS_ERR_VALUE(rc)) { pr_err("An not ready\n"); @@ -668,6 +670,9 @@ static int hdcp_1x_wait_for_hw_ready(struct hdcp_1x *hdcp) /* As per hardware recommendations, wait before reading An */ msleep(20); error: + if (!hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) + rc = -EINVAL; + return rc; } @@ -820,7 +825,8 @@ static int hdcp_1x_verify_r0(struct hdcp_1x *hdcp) /* Wait for HDCP R0 computation to be completed */ rc = readl_poll_timeout(io->base + reg_set->status, link0_status, - link0_status & BIT(reg_set->r0_offset), + (link0_status & BIT(reg_set->r0_offset)) || + !hdcp_1x_state(HDCP_STATE_AUTHENTICATING), HDCP_POLL_SLEEP_US, HDCP_POLL_TIMEOUT_US); if (IS_ERR_VALUE(rc)) { pr_err("R0 not ready\n"); @@ -862,10 +868,14 @@ static int hdcp_1x_verify_r0(struct hdcp_1x *hdcp) DSS_REG_W(io, reg_set->data2_0, (((u32)buf[1]) << 8) | buf[0]); rc = readl_poll_timeout(io->base + reg_set->status, - link0_status, link0_status & BIT(12), + link0_status, (link0_status & BIT(12)) || + !hdcp_1x_state(HDCP_STATE_AUTHENTICATING), r0_read_delay_us, r0_read_timeout_us); } while (rc && --r0_retry); error: + if (!hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) + rc = -EINVAL; + return rc; } @@ -1092,9 +1102,9 @@ static int hdcp_1x_write_ksv_fifo(struct hdcp_1x *hdcp) */ if (i && !((i + 1) % 64)) { rc = readl_poll_timeout(io->base + reg_set->sha_status, - sha_status, sha_status & BIT(0), - HDCP_POLL_SLEEP_US, - HDCP_POLL_TIMEOUT_US); + sha_status, (sha_status & BIT(0)) || + !hdcp_1x_state(HDCP_STATE_AUTHENTICATING), + HDCP_POLL_SLEEP_US, HDCP_POLL_TIMEOUT_US); if (IS_ERR_VALUE(rc)) { pr_err("block not done\n"); goto error; @@ -1108,7 +1118,8 @@ static int hdcp_1x_write_ksv_fifo(struct hdcp_1x *hdcp) /* Now wait for HDCP_SHA_COMP_DONE */ rc = readl_poll_timeout(io->base + reg_set->sha_status, sha_status, - sha_status & BIT(4), + (sha_status & BIT(4)) || + !hdcp_1x_state(HDCP_STATE_AUTHENTICATING), HDCP_POLL_SLEEP_US, HDCP_POLL_TIMEOUT_US); if (IS_ERR_VALUE(rc)) { pr_err("V computation not done\n"); @@ -1117,13 +1128,17 @@ static int hdcp_1x_write_ksv_fifo(struct hdcp_1x *hdcp) /* Wait for V_MATCHES */ rc = readl_poll_timeout(io->base + reg_set->status, status, - status & BIT(reg_set->v_offset), + (status & BIT(reg_set->v_offset)) || + !hdcp_1x_state(HDCP_STATE_AUTHENTICATING), HDCP_POLL_SLEEP_US, HDCP_POLL_TIMEOUT_US); if (IS_ERR_VALUE(rc)) { pr_err("V mismatch\n"); rc = -EINVAL; } error: + if (!hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) + rc = -EINVAL; + return rc; } @@ -1273,8 +1288,6 @@ static void hdcp_1x_update_auth_status(struct hdcp_1x *hdcp) if (hdcp_1x_state(HDCP_STATE_AUTHENTICATED)) { hdcp_1x_cache_topology(hdcp); hdcp_1x_notify_topology(hdcp); - } else { - hdcp1_set_enc(false); } if (hdcp->init_data.notify_status && @@ -1459,9 +1472,6 @@ void hdcp_1x_off(void *input) return; } - if (hdcp_1x_state(HDCP_STATE_AUTHENTICATED)) - hdcp1_set_enc(false); - /* * Disable HDCP interrupts. * Also, need to set the state to inactive here so that any ongoing @@ -1482,11 +1492,13 @@ void hdcp_1x_off(void *input) * No more reauthentiaction attempts will be scheduled since we * set the currect state to inactive. */ - rc = cancel_delayed_work(&hdcp->hdcp_auth_work); + rc = cancel_delayed_work_sync(&hdcp->hdcp_auth_work); if (rc) pr_debug("%s: Deleted hdcp auth work\n", HDCP_STATE_NAME); + hdcp1_set_enc(false); + reg = DSS_REG_R(io, reg_set->reset); DSS_REG_W(io, reg_set->reset, reg | reg_set->reset_bit); diff --git a/drivers/video/fbdev/msm/mdss_hdmi_edid.c b/drivers/video/fbdev/msm/mdss_hdmi_edid.c index 3adf04214d87..36209e36b324 100644 --- a/drivers/video/fbdev/msm/mdss_hdmi_edid.c +++ b/drivers/video/fbdev/msm/mdss_hdmi_edid.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2010-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -122,13 +122,6 @@ struct hdmi_edid_sink_caps { bool ind_view_support; }; -struct hdmi_edid_override_data { - int scramble; - int sink_mode; - int format; - int vic; -}; - struct hdmi_edid_ctrl { u8 pt_scan_info; u8 it_scan_info; @@ -1607,9 +1600,14 @@ static void hdmi_edid_detail_desc(struct hdmi_edid_ctrl *edid_ctrl, (timing.refresh_rate % 100) / 10, timing.refresh_rate % 10); - rc = hdmi_get_video_id_code(&timing, NULL); - if (rc < 0) - rc = hdmi_set_resv_timing_info(&timing); + /* + * Always add resolutions parsed from DTD in the reserved + * timing info. This can avoid matching resolutions that have + * a non-integral fps denominators with corresponding + * resolutions that have an integral fps denominator. + * For example - 640x480p@59.94Hz --> 640x480p@60Hz + */ + rc = hdmi_set_resv_timing_info(&timing); } else { rc = -EINVAL; } @@ -1983,7 +1981,6 @@ static void hdmi_edid_get_display_mode(struct hdmi_edid_ctrl *edid_ctrl) { u8 i = 0, offset = 0, std_blk = 0; u32 video_format = HDMI_VFRMT_640x480p60_4_3; - u32 has480p = false; u8 len = 0; u8 num_of_cea_blocks; u8 *data_buf; @@ -2046,9 +2043,6 @@ static void hdmi_edid_get_display_mode(struct hdmi_edid_ctrl *edid_ctrl) video_format == HDMI_VFRMT_2880x576p50_16_9 || video_format == HDMI_VFRMT_1920x1250i50_16_9) has50hz_mode = true; - - if (video_format == HDMI_VFRMT_640x480p60_4_3) - has480p = true; } } @@ -2067,9 +2061,6 @@ static void hdmi_edid_get_display_mode(struct hdmi_edid_ctrl *edid_ctrl) hdmi_edid_add_sink_video_format(edid_ctrl, video_format); - if (video_format == HDMI_VFRMT_640x480p60_4_3) - has480p = true; - /* Make a note of the preferred video format */ if (i == 0) sink_data->preferred_video_format = @@ -2091,7 +2082,7 @@ static void hdmi_edid_get_display_mode(struct hdmi_edid_ctrl *edid_ctrl) desc_offset = edid_blk1[0x02]; if (desc_offset < (EDID_BLOCK_SIZE - EDID_DTD_LEN)) { i = 0; - while (!edid_blk1[desc_offset]) { + while ((i < 4) && edid_blk1[desc_offset]) { hdmi_edid_detail_desc(edid_ctrl, edid_blk1+desc_offset, &video_format); @@ -2103,8 +2094,6 @@ static void hdmi_edid_get_display_mode(struct hdmi_edid_ctrl *edid_ctrl) hdmi_edid_add_sink_video_format(edid_ctrl, video_format); - if (video_format == HDMI_VFRMT_640x480p60_4_3) - has480p = true; /* Make a note of the preferred video format */ if (i == 0) { @@ -2192,15 +2181,6 @@ static void hdmi_edid_get_display_mode(struct hdmi_edid_ctrl *edid_ctrl) if (!rc) pr_debug("%s: 3D formats in VSD\n", __func__); } - - /* - * Need to add default 640 by 480 timings, in case not described - * in the EDID structure. - * All DTV sink devices should support this mode - */ - if (!has480p) - hdmi_edid_add_sink_video_format(edid_ctrl, - HDMI_VFRMT_640x480p60_4_3); } /* hdmi_edid_get_display_mode */ u32 hdmi_edid_get_raw_data(void *input, u8 *buf, u32 size) @@ -2610,6 +2590,31 @@ void hdmi_edid_set_video_resolution(void *input, u32 resolution, bool reset) } } /* hdmi_edid_set_video_resolution */ +void hdmi_edid_config_override(void *input, bool enable, + struct hdmi_edid_override_data *data) +{ + struct hdmi_edid_ctrl *edid_ctrl = (struct hdmi_edid_ctrl *)input; + struct hdmi_edid_override_data *ov_data = &edid_ctrl->override_data; + + if ((!edid_ctrl) || (enable && !data)) { + DEV_ERR("%s: invalid input\n", __func__); + return; + } + + edid_ctrl->edid_override = enable; + pr_debug("EDID override %s\n", enable ? "enabled" : "disabled"); + + if (enable) { + ov_data->scramble = data->scramble; + ov_data->sink_mode = data->sink_mode; + ov_data->format = data->format; + ov_data->vic = data->vic; + pr_debug("%s: Override data: scramble=%d sink_mode=%d format=%d vic=%d\n", + __func__, ov_data->scramble, ov_data->sink_mode, + ov_data->format, ov_data->vic); + } +} + void hdmi_edid_deinit(void *input) { struct hdmi_edid_ctrl *edid_ctrl = (struct hdmi_edid_ctrl *)input; diff --git a/drivers/video/fbdev/msm/mdss_hdmi_edid.h b/drivers/video/fbdev/msm/mdss_hdmi_edid.h index 5ee77fcf2066..16efb6ee4014 100644 --- a/drivers/video/fbdev/msm/mdss_hdmi_edid.h +++ b/drivers/video/fbdev/msm/mdss_hdmi_edid.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2010-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -44,6 +44,20 @@ struct hdmi_edid_hdr_data { u32 min_luminance; }; +/* + * struct hdmi_override_data - Resolution Override Data + * @scramble - scrambler enable + * @sink_mode - 0 for DVI and 1 for HDMI + * @format - pixel format (refer to msm_hdmi_modes.h) + * @vic - resolution code + */ +struct hdmi_edid_override_data { + int scramble; + int sink_mode; + int format; + int vic; +}; + int hdmi_edid_parser(void *edid_ctrl); u32 hdmi_edid_get_raw_data(void *edid_ctrl, u8 *buf, u32 size); u8 hdmi_edid_get_sink_scaninfo(void *edid_ctrl, u32 resolution); @@ -63,5 +77,7 @@ u8 hdmi_edid_get_deep_color(void *edid_ctrl); u32 hdmi_edid_get_max_pclk(void *edid_ctrl); void hdmi_edid_get_hdr_data(void *edid_ctrl, struct hdmi_edid_hdr_data **hdr_data); +void hdmi_edid_config_override(void *input, bool enable, + struct hdmi_edid_override_data *data); #endif /* __HDMI_EDID_H__ */ diff --git a/drivers/video/fbdev/msm/mdss_hdmi_tx.c b/drivers/video/fbdev/msm/mdss_hdmi_tx.c index 67813ca9cd37..d573bd1eb0ba 100644 --- a/drivers/video/fbdev/msm/mdss_hdmi_tx.c +++ b/drivers/video/fbdev/msm/mdss_hdmi_tx.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2010-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -421,8 +421,10 @@ static inline void hdmi_tx_send_cable_notification( if (hdmi_ctrl && hdmi_ctrl->ext_audio_data.intf_ops.hpd) { u32 flags = 0; - if (hdmi_tx_is_dvi_mode(hdmi_ctrl)) - flags |= MSM_EXT_DISP_HPD_NO_AUDIO; + flags |= MSM_EXT_DISP_HPD_VIDEO; + + if (!hdmi_tx_is_dvi_mode(hdmi_ctrl)) + flags |= MSM_EXT_DISP_HPD_AUDIO; hdmi_ctrl->ext_audio_data.intf_ops.hpd(hdmi_ctrl->ext_pdev, hdmi_ctrl->ext_audio_data.type, val, flags); diff --git a/drivers/video/fbdev/msm/mdss_hdmi_util.c b/drivers/video/fbdev/msm/mdss_hdmi_util.c index 89890bcf68df..102c2f994646 100644 --- a/drivers/video/fbdev/msm/mdss_hdmi_util.c +++ b/drivers/video/fbdev/msm/mdss_hdmi_util.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2010-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -555,6 +555,9 @@ int msm_hdmi_get_timing_info( case HDMI_VFRMT_3840x2160p60_64_27: MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_3840x2160p60_64_27); break; + case HDMI_VFRMT_640x480p59_4_3: + MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_640x480p59_4_3); + break; default: ret = hdmi_get_resv_timing_info(mode, id); } diff --git a/drivers/video/fbdev/msm/mdss_panel.h b/drivers/video/fbdev/msm/mdss_panel.h index 4698d441f365..6eb03dd8aa9a 100644 --- a/drivers/video/fbdev/msm/mdss_panel.h +++ b/drivers/video/fbdev/msm/mdss_panel.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2008-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2008-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -372,9 +372,11 @@ struct lcd_panel_info { u32 h_back_porch; u32 h_front_porch; u32 h_pulse_width; + u32 h_active_low; u32 v_back_porch; u32 v_front_porch; u32 v_pulse_width; + u32 v_active_low; u32 border_clr; u32 underflow_clr; u32 hsync_skew; diff --git a/drivers/video/fbdev/msm/msm_ext_display.c b/drivers/video/fbdev/msm/msm_ext_display.c index 4fccf1178dac..30f8ca0487a7 100644 --- a/drivers/video/fbdev/msm/msm_ext_display.c +++ b/drivers/video/fbdev/msm/msm_ext_display.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -42,6 +42,7 @@ struct msm_ext_disp { struct list_head display_list; struct mutex lock; struct completion hpd_comp; + u32 flags; }; static int msm_ext_disp_get_intf_data(struct msm_ext_disp *ext_disp, @@ -365,7 +366,7 @@ static int msm_ext_disp_process_display(struct msm_ext_disp *ext_disp, { int ret = 0; - if (flags & MSM_EXT_DISP_HPD_NO_VIDEO) { + if (!(flags & MSM_EXT_DISP_HPD_VIDEO)) { pr_debug("skipping video setup for display (%s)\n", msm_ext_disp_name(type)); goto end; @@ -398,7 +399,7 @@ static int msm_ext_disp_process_audio(struct msm_ext_disp *ext_disp, { int ret = 0; - if (flags & MSM_EXT_DISP_HPD_NO_AUDIO) { + if (!(flags & MSM_EXT_DISP_HPD_AUDIO)) { pr_debug("skipping audio setup for display (%s)\n", msm_ext_disp_name(type)); goto end; @@ -425,6 +426,47 @@ end: return ret; } +static bool msm_ext_disp_validate_connect(struct msm_ext_disp *ext_disp, + enum msm_ext_disp_type type, u32 flags) +{ + /* allow new connections */ + if (ext_disp->current_disp == EXT_DISPLAY_TYPE_MAX) + goto end; + + /* if already connected, block a new connection */ + if (ext_disp->current_disp != type) + return false; + + /* if same display connected, block same connection type */ + if (ext_disp->flags & flags) + return false; + +end: + ext_disp->flags |= flags; + ext_disp->current_disp = type; + return true; +} + +static bool msm_ext_disp_validate_disconnect(struct msm_ext_disp *ext_disp, + enum msm_ext_disp_type type, u32 flags) +{ + /* check if nothing connected */ + if (ext_disp->current_disp == EXT_DISPLAY_TYPE_MAX) + return false; + + /* check if a different display's request */ + if (ext_disp->current_disp != type) + return false; + + /* allow only an already connected type */ + if (ext_disp->flags & flags) { + ext_disp->flags &= ~flags; + return true; + } + + return false; +} + static int msm_ext_disp_hpd(struct platform_device *pdev, enum msm_ext_disp_type type, enum msm_ext_disp_cable_state state, @@ -446,8 +488,8 @@ static int msm_ext_disp_hpd(struct platform_device *pdev, mutex_lock(&ext_disp->lock); - pr_debug("HPD for display (%s), NEW STATE = %d\n", - msm_ext_disp_name(type), state); + pr_debug("HPD for display (%s), NEW STATE = %d, flags = %d\n", + msm_ext_disp_name(type), state, flags); if (state < EXT_DISPLAY_CABLE_DISCONNECT || state >= EXT_DISPLAY_CABLE_STATE_MAX) { @@ -456,24 +498,13 @@ static int msm_ext_disp_hpd(struct platform_device *pdev, goto end; } - if ((state == EXT_DISPLAY_CABLE_CONNECT) && - (ext_disp->current_disp != EXT_DISPLAY_TYPE_MAX)) { - pr_err("Display interface (%s) already connected\n", - msm_ext_disp_name(ext_disp->current_disp)); - ret = -EINVAL; - goto end; - } - - if ((state == EXT_DISPLAY_CABLE_DISCONNECT) && - (ext_disp->current_disp != type)) { - pr_err("Display interface (%s) is not connected\n", - msm_ext_disp_name(type)); - ret = -EINVAL; - goto end; - } - if (state == EXT_DISPLAY_CABLE_CONNECT) { - ext_disp->current_disp = type; + if (!msm_ext_disp_validate_connect(ext_disp, type, flags)) { + pr_err("Display interface (%s) already connected\n", + msm_ext_disp_name(ext_disp->current_disp)); + ret = -EINVAL; + goto end; + } ret = msm_ext_disp_process_display(ext_disp, type, state, flags); @@ -490,11 +521,19 @@ static int msm_ext_disp_hpd(struct platform_device *pdev, if (ret) goto end; } else { + if (!msm_ext_disp_validate_disconnect(ext_disp, type, flags)) { + pr_err("Display interface (%s) not connected\n", + msm_ext_disp_name(type)); + ret = -EINVAL; + goto end; + } + msm_ext_disp_process_audio(ext_disp, type, state, flags); msm_ext_disp_update_audio_ops(ext_disp, type, state, flags); msm_ext_disp_process_display(ext_disp, type, state, flags); - ext_disp->current_disp = EXT_DISPLAY_TYPE_MAX; + if (!ext_disp->flags) + ext_disp->current_disp = EXT_DISPLAY_TYPE_MAX; } pr_debug("Hpd (%d) for display (%s)\n", state, @@ -653,7 +692,7 @@ static int msm_ext_disp_update_audio_ops(struct msm_ext_disp *ext_disp, int ret = 0; struct msm_ext_disp_audio_codec_ops *ops = ext_disp->ops; - if (flags & MSM_EXT_DISP_HPD_NO_AUDIO) { + if (!(flags & MSM_EXT_DISP_HPD_AUDIO)) { pr_debug("skipping audio ops setup for display (%s)\n", msm_ext_disp_name(type)); goto end; diff --git a/include/linux/msm_ext_display.h b/include/linux/msm_ext_display.h index b3a7e4ad722a..44a04b5c2fcd 100644 --- a/include/linux/msm_ext_display.h +++ b/include/linux/msm_ext_display.h @@ -1,6 +1,6 @@ /* include/linux/msm_ext_display.h * - * Copyright (c) 2014-2016 The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -24,11 +24,11 @@ /** * Flags to be used with the HPD operation of the external display * interface: - * MSM_EXT_DISP_HPD_NO_AUDIO: audio will not be routed to external display - * MSM_EXT_DISP_HPD_NO_VIDEO: video will not be routed to external display + * MSM_EXT_DISP_HPD_AUDIO: audio will be routed to external display + * MSM_EXT_DISP_HPD_VIDEO: video will be routed to external display */ -#define MSM_EXT_DISP_HPD_NO_AUDIO BIT(0) -#define MSM_EXT_DISP_HPD_NO_VIDEO BIT(1) +#define MSM_EXT_DISP_HPD_AUDIO BIT(0) +#define MSM_EXT_DISP_HPD_VIDEO BIT(1) /** * struct ext_disp_cable_notify - cable notify handler structure diff --git a/include/uapi/video/msm_hdmi_modes.h b/include/uapi/video/msm_hdmi_modes.h index 2200485daa6c..43ca6bab4c62 100644 --- a/include/uapi/video/msm_hdmi_modes.h +++ b/include/uapi/video/msm_hdmi_modes.h @@ -234,7 +234,11 @@ struct msm_hdmi_mode_timing_info { #define HDMI_VFRMT_1920x1200p60_16_10 ETIII_OFF(8) #define ETIII_VFRMT_END HDMI_VFRMT_1920x1200p60_16_10 -#define RESERVE_OFF(x) (ETIII_VFRMT_END + x) +#define MISC_VFRMT_OFF(x) (ETIII_VFRMT_END + x) +#define HDMI_VFRMT_640x480p59_4_3 MISC_VFRMT_OFF(1) +#define MISC_VFRMT_END HDMI_VFRMT_640x480p59_4_3 + +#define RESERVE_OFF(x) (MISC_VFRMT_END + x) #define HDMI_VFRMT_RESERVE1 RESERVE_OFF(1) #define HDMI_VFRMT_RESERVE2 RESERVE_OFF(2) @@ -425,6 +429,11 @@ struct msm_hdmi_mode_timing_info { {HDMI_VFRMT_3840x2160p60_64_27, 3840, 176, 88, 296, false, \ 2160, 8, 10, 72, false, 594000, 60000, false, true, \ HDMI_RES_AR_64_27, 0} +#define HDMI_VFRMT_640x480p59_4_3_TIMING \ + {HDMI_VFRMT_640x480p59_4_3, 640, 16, 96, 48, true, \ + 480, 10, 2, 33, true, 25170, 59928, false, true, \ + HDMI_RES_AR_4_3, 1} + #define MSM_HDMI_MODES_SET_TIMING(LUT, MODE) do { \ struct msm_hdmi_mode_timing_info mode = MODE##_TIMING; \ @@ -508,6 +517,8 @@ do { \ HDMI_VFRMT_3840x2160p50_64_27); \ MSM_HDMI_MODES_SET_TIMING(__lut, \ HDMI_VFRMT_3840x2160p60_64_27); \ + MSM_HDMI_MODES_SET_TIMING(__lut, \ + HDMI_VFRMT_640x480p59_4_3); \ } \ if (__type & MSM_HDMI_MODES_XTND) { \ MSM_HDMI_MODES_SET_TIMING(__lut, \