From 19b7c1036c992e19e6bc350f6b4cb6a795c9e2e9 Mon Sep 17 00:00:00 2001 From: Ajay Singh Parmar Date: Mon, 9 Jan 2017 15:27:51 -0800 Subject: [PATCH 01/24] msm: mdss: hdcp2p2: do not process interrupts in failed state Interrupts from sinks (cp_irq) may come asynchronously at different states for HDCP authentications. In case HDCP has failed and an interrupt is received from sink, do not process it to avoid getting unnecessary failures. Change-Id: I00b0d45fdbdc3d43ac49908db66573d6d4b58283 Signed-off-by: Ajay Singh Parmar --- drivers/misc/hdcp.c | 27 ++++++++++++++--------- drivers/video/fbdev/msm/mdss_dp_hdcp2p2.c | 17 +++++++++++++- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/drivers/misc/hdcp.c b/drivers/misc/hdcp.c index efb987b4a6b6..038c7a94aca3 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 @@ -805,8 +805,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 +901,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 +949,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 +1024,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 +1051,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 +1239,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 +1621,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"); 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); From 4eaf19c57aed7e7a5bc0188cfe6a1169891bc828 Mon Sep 17 00:00:00 2001 From: Ajay Singh Parmar Date: Sat, 7 Jan 2017 18:29:04 -0800 Subject: [PATCH 02/24] msm: mdss: dp: fix link training sequence Transmit the link pattern first, update the drive settings and then update the sink about the pattern and drive settings using aux channel to avoid possible link training failures with some sinks. Change-Id: Ia8eccee99f58da94d3f1013f075c07400bde15f9 Signed-off-by: Ajay Singh Parmar --- drivers/video/fbdev/msm/mdss_dp_aux.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp_aux.c b/drivers/video/fbdev/msm/mdss_dp_aux.c index fa1af0d392e7..83ce2384b044 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 @@ -1653,10 +1653,10 @@ static int dp_start_link_train_1(struct mdss_dp_drv_pdata *ep) pr_debug("Entered++"); + 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 */ tries = 0; old_v_level = ep->v_level; @@ -1708,11 +1708,11 @@ static int dp_start_link_train_2(struct mdss_dp_drv_pdata *ep) else pattern = 0x02; + dp_host_train_set(ep, pattern); + dp_aux_set_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,11 +1723,11 @@ 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); From ed104f71b62b35ab0aa8ab086d8696e3dfdb48f8 Mon Sep 17 00:00:00 2001 From: Ajay Singh Parmar Date: Fri, 6 Jan 2017 19:22:46 -0800 Subject: [PATCH 03/24] msm: hdcp: protect encryption enable with a mutex Enable/disable encryption API can be called by multiple threads. Protect this API with a mutex to avoid any possible memory violation due to invalid calls to the API. Change-Id: I190cdf24880645ac20ec17934d76498d71b2802a Signed-off-by: Ajay Singh Parmar --- drivers/misc/hdcp.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/misc/hdcp.c b/drivers/misc/hdcp.c index 038c7a94aca3..bd21f8cca2aa 100644 --- a/drivers/misc/hdcp.c +++ b/drivers/misc/hdcp.c @@ -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) { @@ -2217,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); } } @@ -2281,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 */ @@ -2304,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) From 80612605edd74022aa335cdce5e3fc568eee8403 Mon Sep 17 00:00:00 2001 From: Ajay Singh Parmar Date: Fri, 6 Jan 2017 18:35:34 -0800 Subject: [PATCH 04/24] msm: mdss: hdcp_1x: fix hdcp off race condition During authentication, HDCP may poll hardware for status. HDCP may be turned off by other thread abruptly depending on the use-case. Do not poll hardware in case HDCP has been turned off. Also, wait for authenticating thread to finish in hdcp off thread to avoid any race condition. Change-Id: If2e4940310035f08d2077def4fa62620bef19942 Signed-off-by: Ajay Singh Parmar --- drivers/video/fbdev/msm/mdss_hdcp_1x.c | 44 ++++++++++++++++---------- 1 file changed, 28 insertions(+), 16 deletions(-) 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); From 7d6cf4f28990b59d476b2a7e889632b2d62e302f Mon Sep 17 00:00:00 2001 From: Ajay Singh Parmar Date: Fri, 6 Jan 2017 16:40:16 -0800 Subject: [PATCH 05/24] msm: mdss: dp: fix audio stream header Program the correct number of channels for header byte 3 in audio stream to avoid issues related to audio channels. Change-Id: I1de32403efc42d8fde8ac2096ae021e795707aae Signed-off-by: Ajay Singh Parmar --- drivers/video/fbdev/msm/mdss_dp.c | 4 ++-- drivers/video/fbdev/msm/mdss_dp_util.c | 11 ++++++----- drivers/video/fbdev/msm/mdss_dp_util.h | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index ccf8a0d34a6c..24deef6ba047 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 @@ -905,7 +905,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); diff --git a/drivers/video/fbdev/msm/mdss_dp_util.c b/drivers/video/fbdev/msm/mdss_dp_util.c index a7f42ba8c261..80a83716488b 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 @@ -1113,7 +1113,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; @@ -1141,7 +1142,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)); @@ -1309,7 +1310,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 +1336,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); diff --git a/drivers/video/fbdev/msm/mdss_dp_util.h b/drivers/video/fbdev/msm/mdss_dp_util.h index fcb9a77db67c..1434ea3f0163 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 @@ -312,7 +312,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, From 70a007e5b32d86aa480014748cd318f1407a4a97 Mon Sep 17 00:00:00 2001 From: Ajay Singh Parmar Date: Wed, 11 Jan 2017 22:37:10 -0800 Subject: [PATCH 06/24] msm: mdss: edid: fix extension block resolution parsing Fix the DTD (Detailed Timing Descriptor) parsing for block 1 (CEA extension block) to properly parse the resolution details. Change-Id: I5c1e448d5cdaf771c9e35479fe48067092c964e3 Signed-off-by: Ajay Singh Parmar --- drivers/video/fbdev/msm/mdss_hdmi_edid.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_hdmi_edid.c b/drivers/video/fbdev/msm/mdss_hdmi_edid.c index 3adf04214d87..67b6fed0a5dc 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 @@ -2091,7 +2091,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); From f03a0910178da306211df851876391f0b8679f5f Mon Sep 17 00:00:00 2001 From: Ajay Singh Parmar Date: Wed, 11 Jan 2017 22:50:25 -0800 Subject: [PATCH 07/24] msm: mdss: dp: fix hpd sysfs node Fix the return value for hpd sysfs write function to avoid wrong data to propagate to the caller. Change-Id: Ic2282496598ecba8511f3a0a826e152451394edb Signed-off-by: Ajay Singh Parmar --- drivers/video/fbdev/msm/mdss_dp.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index 24deef6ba047..79ee9722117f 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -1993,7 +1993,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 +2003,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; } From ff59333bdd9572a20eed86de0f568e263314ffaf Mon Sep 17 00:00:00 2001 From: Aravind Venkateswaran Date: Fri, 9 Dec 2016 16:17:02 -0800 Subject: [PATCH 08/24] msm: mdss: dp: fix switching between non-standard resolutions EDID parser assigns temporary Video Identification Codes (VIC) for all non-standard resolutions when parsing a sink's EDID. These VICs can be reused for a subsequent connection for different resolutions. In the current implementation of the Display Port (DP) driver, a resolution change is detected whenever a new VIC is used. However, when switching between two non-standard resolutions spanning connections to two different sinks, it is possible to have the same VIC assigned for the two resolutions. This can result in abnormal behavior. Fix this by resetting the video identification code and panel information to reset value every time we get an HPD for disconnect. CRs-Fixed: 1099532 Change-Id: I4bb5a6b4329f67368ebbdc7fe8d92c2ea4605082 Signed-off-by: Aravind Venkateswaran --- drivers/video/fbdev/msm/mdss_dp.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index 79ee9722117f..937c987b3066 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -993,13 +993,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; @@ -1207,6 +1211,9 @@ static int mdss_dp_on_irq(struct mdss_dp_drv_pdata *dp_drv) goto exit; } + if (dp_drv->new_vic && (dp_drv->new_vic != dp_drv->vic)) + dp_init_panel_info(dp_drv, dp_drv->new_vic); + mdss_dp_phy_share_lane_config(&dp_drv->phy_io, dp_drv->orientation, dp_drv->dpcd.max_lane_count); @@ -1398,6 +1405,7 @@ static int mdss_dp_off_irq(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); mutex_unlock(&dp_drv->train_mutex); complete_all(&dp_drv->irq_comp); pr_debug("end\n"); @@ -1445,6 +1453,7 @@ 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); mutex_unlock(&dp_drv->train_mutex); From 69475737100c34b4748a34e106d169858a0629c2 Mon Sep 17 00:00:00 2001 From: Aravind Venkateswaran Date: Thu, 5 Jan 2017 09:55:19 -0800 Subject: [PATCH 09/24] msm: mdss: dp: fix event queue management Current implementation utilizes a simple event queue to handle some of the tasks when setting up or tearing down a display port session. This is currently achieved using a workqueue and assumes that there can be at most one unhandled event outstanding. This may not be valid and can result certain events being skipped. Refactor this by handling the events in a new event thread. CRs-Fixed: 1109812 Change-Id: I2041fd2bbeadbfc7726a6c504ec3e373f34d2aea Signed-off-by: Aravind Venkateswaran --- drivers/video/fbdev/msm/mdss_dp.c | 210 ++++++++++++++++++------------ drivers/video/fbdev/msm/mdss_dp.h | 38 +++++- 2 files changed, 158 insertions(+), 90 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index 937c987b3066..15c3eb5351ee 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -2245,21 +2245,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; @@ -2370,81 +2355,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) @@ -2480,10 +2489,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 */ @@ -2506,17 +2515,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; @@ -3212,6 +3236,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..29ff8e6d62b1 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 @@ -376,6 +376,21 @@ 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_drv_pdata { /* device driver */ int (*on) (struct mdss_panel_data *pdata); @@ -484,12 +499,11 @@ struct mdss_dp_drv_pdata { bool hpd_irq_toggled; bool hpd_irq_clients_notified; - /* event */ + struct mdss_dp_event_data dp_event; + struct task_struct *ev_thread; + 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; @@ -661,6 +675,20 @@ 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"; } From ce49c9f5cdbdc3e21521f3264a2020a461276846 Mon Sep 17 00:00:00 2001 From: Aravind Venkateswaran Date: Thu, 5 Jan 2017 16:11:39 -0800 Subject: [PATCH 10/24] msm: mdss: dp: program HSYNC and VSYNC polarity Add support to program the vertical sync (VSYNC) and horizontal sync (HSYNC) polarity based on the sink's EDID. This enables transmission of these parameters as part of the Main Stream Attributes. CRs-Fixed: 1109812 Change-Id: I10a91dd778e51abc8da4d4a8280a57b927cce590 Signed-off-by: Aravind Venkateswaran --- drivers/video/fbdev/msm/mdss_dp.c | 2 ++ drivers/video/fbdev/msm/mdss_dp_util.c | 2 ++ drivers/video/fbdev/msm/mdss_panel.h | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index 15c3eb5351ee..2edd8c7dbd68 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -1014,9 +1014,11 @@ 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; diff --git a/drivers/video/fbdev/msm/mdss_dp_util.c b/drivers/video/fbdev/msm/mdss_dp_util.c index 80a83716488b..46243422e32a 100644 --- a/drivers/video/fbdev/msm/mdss_dp_util.c +++ b/drivers/video/fbdev/msm/mdss_dp_util.c @@ -735,7 +735,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); 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; From 8a4208ec19bf98286073c79803b30806726d9b59 Mon Sep 17 00:00:00 2001 From: Aravind Venkateswaran Date: Sun, 15 Jan 2017 14:53:03 -0800 Subject: [PATCH 11/24] msm: hdmi: edid: add 640x480p59.94 resolution to timing database Populate the required parameters for 640x480p59.94 resolution and add this to the resolution database. CRs-Fixed: 1109812 Change-Id: Ic600d81e81d695288abd7638112e2541df5b1197 Signed-off-by: Aravind Venkateswaran --- drivers/video/fbdev/msm/mdss_hdmi_util.c | 5 ++++- include/uapi/video/msm_hdmi_modes.h | 13 ++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) 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/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, \ From 1507849767c8c63743fb0639067add2dd3de10b8 Mon Sep 17 00:00:00 2001 From: Aravind Venkateswaran Date: Mon, 9 Jan 2017 22:38:45 -0800 Subject: [PATCH 12/24] msm: hdmi: edid: do not add VGA resolution by default Current implementation of the EDID parser always ensures that VGA resolution (640x480) is added to the list of supported modes. However, this should really be done by the client of the parser based on their policies. This will avoid usecases where the EDID parser would report the VGA resolution as supported even if it was not listed in the sink's EDID. CRs-Fixed: 1109812 Change-Id: Ic98314493029586d06a26a2c3a12b275ddaa8a8f Signed-off-by: Aravind Venkateswaran --- drivers/video/fbdev/msm/mdss_hdmi_edid.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_hdmi_edid.c b/drivers/video/fbdev/msm/mdss_hdmi_edid.c index 67b6fed0a5dc..5793186df491 100644 --- a/drivers/video/fbdev/msm/mdss_hdmi_edid.c +++ b/drivers/video/fbdev/msm/mdss_hdmi_edid.c @@ -1983,7 +1983,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 +2045,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 +2063,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 = @@ -2103,8 +2096,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 +2183,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) From bc176f3364053e465518bc4491e10864a7d9a94b Mon Sep 17 00:00:00 2001 From: Aravind Venkateswaran Date: Sun, 15 Jan 2017 15:22:46 -0800 Subject: [PATCH 13/24] msm: hdmi: edid: always add timings parsed from DTD to reserved list Resolutions listed in the Detailed Timing Descriptor (DTD) have detailed information about all the timings. Current implementation attempts to match the parsed resolution with the resolution database based on just the Video Information Code (VIC). This can potentially overlook mismatches in certain parameters such as refresh rates and can cause unintended results. Fix this by always adding the resolutions parsed from the DTDs to the reserved timing list. CRs-Fixed: 1109812 Change-Id: I8b926f08ce9b44c4a16a32fed26fedd351d77ae8 Signed-off-by: Aravind Venkateswaran --- drivers/video/fbdev/msm/mdss_hdmi_edid.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_hdmi_edid.c b/drivers/video/fbdev/msm/mdss_hdmi_edid.c index 5793186df491..160898ee2b6c 100644 --- a/drivers/video/fbdev/msm/mdss_hdmi_edid.c +++ b/drivers/video/fbdev/msm/mdss_hdmi_edid.c @@ -1607,9 +1607,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; } From 9be29e6aaa47042e2a4cc05a379c3fa9f78a8982 Mon Sep 17 00:00:00 2001 From: Aravind Venkateswaran Date: Sun, 15 Jan 2017 18:44:38 -0800 Subject: [PATCH 14/24] msm: hdmi: edid: add API to override edid configuration Add a new API to override the EDID configuration. This will allow clients to specify a particular resolution to be used for subsequent connections. CRs-Fixed: 1109812 Change-Id: Ie08ed509dd563bf265b32a2bfece86352b4e2b68 Signed-off-by: Aravind Venkateswaran --- drivers/video/fbdev/msm/mdss_hdmi_edid.c | 32 ++++++++++++++++++------ drivers/video/fbdev/msm/mdss_hdmi_edid.h | 18 ++++++++++++- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_hdmi_edid.c b/drivers/video/fbdev/msm/mdss_hdmi_edid.c index 160898ee2b6c..36209e36b324 100644 --- a/drivers/video/fbdev/msm/mdss_hdmi_edid.c +++ b/drivers/video/fbdev/msm/mdss_hdmi_edid.c @@ -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; @@ -2597,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__ */ From b8aa5377755449b5c114b8ac039295a4b1343dfa Mon Sep 17 00:00:00 2001 From: Aravind Venkateswaran Date: Thu, 22 Dec 2016 17:20:55 -0800 Subject: [PATCH 15/24] msm: mdss: dp: add support for automated video pattern tests Add support for parsing parameters in the automated CTS mode when the test type is set to TEST_VIDEO_PATTERN. The parameters parsed are the color bit depth, colorimetry and the test pattern type. Configure the source accordingly and transmit the requested video pattern. CRs-Fixed: 1109812 Change-Id: I552a50e86dc299b4cf7955992ad82dee19f35cbc Signed-off-by: Aravind Venkateswaran --- drivers/video/fbdev/msm/mdss_dp.c | 351 ++++++++++++++++++------- drivers/video/fbdev/msm/mdss_dp.h | 236 ++++++++++++++++- drivers/video/fbdev/msm/mdss_dp_aux.c | 254 +++++++++++++++++- drivers/video/fbdev/msm/mdss_dp_util.c | 21 +- drivers/video/fbdev/msm/mdss_dp_util.h | 4 +- 5 files changed, 742 insertions(+), 124 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index 2edd8c7dbd68..bae93b03a0fc 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -60,6 +60,48 @@ static inline void mdss_dp_link_retraining(struct mdss_dp_drv_pdata *dp); 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 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; +} + +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_video_pattern_requested( + struct mdss_dp_drv_pdata *dp) +{ + return (dp->test_data.test_requested == TEST_VIDEO_PATTERN); +} + +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 void mdss_dp_put_dt_clk_data(struct device *dev, struct dss_module_power *module_power) { @@ -798,9 +840,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 +867,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); @@ -983,6 +1027,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 (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; @@ -1023,7 +1116,6 @@ static int dp_init_panel_info(struct mdss_dp_drv_pdata *dp_drv, u32 vic) 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 */ @@ -1031,8 +1123,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); @@ -1157,6 +1249,9 @@ 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); } @@ -1347,44 +1442,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) { @@ -1504,11 +1569,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, @@ -1617,6 +1677,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; @@ -1643,7 +1790,7 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp) dp->sink_info_read = true; end: mdss_dp_update_cable_status(dp, true); - mdss_dp_notify_clients(dp, true); + mdss_dp_notify_clients(dp, NOTIFY_CONNECT); return ret; @@ -1885,7 +2032,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); } /** @@ -1927,7 +2074,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 { /* @@ -1942,7 +2089,7 @@ static int mdss_dp_psm_config(struct mdss_dp_drv_pdata *dp, bool enable) mdss_dp_link_retraining(dp); } else { mdss_dp_host_init(&dp->panel_data); - mdss_dp_notify_clients(dp, true); + mdss_dp_notify_clients(dp, NOTIFY_CONNECT); } } @@ -2032,7 +2179,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; @@ -2577,7 +2724,7 @@ 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); + mdss_dp_notify_clients(dp_drv, NOTIFY_DISCONNECT); } static int mdss_dp_validate_callback(u8 cmd, @@ -2633,41 +2780,6 @@ static inline void mdss_dp_send_test_response(struct mdss_dp_drv_pdata *dp) mutex_unlock(&dp->train_mutex); } -/** - * mdss_dp_hpd_irq_notify_clients() - notifies DP clients of HPD IRQ tear down - * @dp: Display Port Driver data - * - * 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 @@ -2678,7 +2790,7 @@ static int mdss_dp_hpd_irq_notify_clients(struct mdss_dp_drv_pdata *dp) */ static inline void mdss_dp_link_retraining(struct mdss_dp_drv_pdata *dp) { - if (mdss_dp_hpd_irq_notify_clients(dp)) + if (mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT_IRQ_HPD)) return; mdss_dp_on_irq(dp); @@ -2813,6 +2925,41 @@ static int mdss_dp_process_downstream_port_status_change( return mdss_dp_edid_read(dp); } +/** + * 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) +{ + if (!mdss_dp_is_video_pattern_requested(dp)) + return -EINVAL; + + 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)); + + /* Send a disconnect notification */ + mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT_IRQ_HPD); + + mdss_dp_off_irq(dp); + + /* Send a connect notification */ + mdss_dp_notify_clients(dp, NOTIFY_CONNECT_IRQ_HPD); + + mdss_dp_send_test_response(dp); + + return 0; +} + /** * mdss_dp_process_hpd_irq_high() - handle HPD IRQ transition to HIGH * @dp: Display Port Driver data @@ -2827,6 +2974,8 @@ static int mdss_dp_process_hpd_irq_high(struct mdss_dp_drv_pdata *dp) 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); @@ -2844,10 +2993,14 @@ static int mdss_dp_process_hpd_irq_high(struct mdss_dp_drv_pdata *dp) ret = mdss_dp_process_phy_test_pattern_request(dp); if (!ret) goto exit; - pr_debug("done\n"); -exit: - mdss_dp_reset_test_data(dp); + ret = mdss_dp_process_video_pattern_request(dp); + if (!ret) + goto exit; + + pr_debug("done\n"); + +exit: return ret; } @@ -2860,17 +3013,21 @@ exit: */ static int mdss_dp_process_hpd_irq_low(struct mdss_dp_drv_pdata *dp) { - if (!dp->hpd_irq_clients_notified) + if (!dp->hpd_irq_on) return -EINVAL; pr_debug("enter: HPD IRQ low\n"); - dp->hpd_irq_on = false; - dp->hpd_irq_clients_notified = false; + if (dp->hpd_notification_status == NOTIFY_CONNECT_IRQ_HPD) { + mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT); + } else { + /* Clients already notified, so shudown interface directly */ + mdss_dp_update_cable_status(dp, false); + mdss_dp_mainlink_push_idle(&dp->panel_data); + mdss_dp_off_hpd(dp); + } - mdss_dp_update_cable_status(dp, false); - mdss_dp_mainlink_push_idle(&dp->panel_data); - mdss_dp_off_hpd(dp); + dp->hpd_irq_on = false; mdss_dp_reset_test_data(dp); @@ -2977,7 +3134,7 @@ static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv) } 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"); return; } diff --git a/drivers/video/fbdev/msm/mdss_dp.h b/drivers/video/fbdev/msm/mdss_dp.h index 29ff8e6d62b1..564f490ba497 100644 --- a/drivers/video/fbdev/msm/mdss_dp.h +++ b/drivers/video/fbdev/msm/mdss_dp.h @@ -269,6 +269,21 @@ 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 response; }; @@ -493,11 +508,10 @@ 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; @@ -623,6 +637,7 @@ static inline char *mdss_dp_get_test_response(u32 test_response) enum test_type { UNKNOWN_TEST = 0, TEST_LINK_TRAINING = BIT(0), + TEST_VIDEO_PATTERN = BIT(1), PHY_TEST_PATTERN = BIT(3), TEST_EDID_READ = BIT(2), }; @@ -631,6 +646,7 @@ 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); default: return "unknown"; @@ -694,6 +710,222 @@ static inline char *mdss_dp_ev_event_to_string(int event) } } +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"; + } +} + void mdss_dp_phy_initialize(struct mdss_dp_drv_pdata *dp); void mdss_dp_dpcd_cap_read(struct mdss_dp_drv_pdata *dp); diff --git a/drivers/video/fbdev/msm/mdss_dp_aux.c b/drivers/video/fbdev/msm/mdss_dp_aux.c index 83ce2384b044..4a3e1e0b259d 100644 --- a/drivers/video/fbdev/msm/mdss_dp_aux.c +++ b/drivers/video/fbdev/msm/mdss_dp_aux.c @@ -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 { @@ -1258,6 +1256,243 @@ 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; +} + /** * dp_is_test_supported() - checks if test requested by sink is supported @@ -1268,6 +1503,7 @@ end: static bool dp_is_test_supported(u32 test_requested) { return (test_requested == TEST_LINK_TRAINING) || + (test_requested == TEST_VIDEO_PATTERN) || (test_requested == TEST_EDID_READ) || (test_requested == PHY_TEST_PATTERN); } @@ -1289,6 +1525,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 @@ -1341,12 +1578,19 @@ static void dp_sink_parse_test_request(struct mdss_dp_drv_pdata *ep) case TEST_LINK_TRAINING: ret = dp_parse_link_training_params(ep); break; + case TEST_VIDEO_PATTERN: + ret = dp_parse_video_pattern_params(ep); + break; default: pr_debug("test 0x%x not supported\n", ep->test_data.test_requested); return; } + /* 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. @@ -1833,8 +2077,6 @@ int mdss_dp_link_train(struct mdss_dp_drv_pdata *dp) 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); diff --git a/drivers/video/fbdev/msm/mdss_dp_util.c b/drivers/video/fbdev/msm/mdss_dp_util.c index 46243422e32a..26420836e530 100644 --- a/drivers/video/fbdev/msm/mdss_dp_util.c +++ b/drivers/video/fbdev/msm/mdss_dp_util.c @@ -767,28 +767,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, diff --git a/drivers/video/fbdev/msm/mdss_dp_util.h b/drivers/video/fbdev/msm/mdss_dp_util.h index 1434ea3f0163..e08a16a02174 100644 --- a/drivers/video/fbdev/msm/mdss_dp_util.h +++ b/drivers/video/fbdev/msm/mdss_dp_util.h @@ -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) @@ -285,8 +286,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); From 31975b700c56af6a6edc3f13e83e8f10bc9dacf9 Mon Sep 17 00:00:00 2001 From: Ajay Singh Parmar Date: Fri, 16 Dec 2016 12:33:53 -0800 Subject: [PATCH 16/24] msm: mdss: dp: add configuration sysfs node Add node named 'config' to let the user modules know about the supported DisplayPort configurations. On every cable connection, user modules can read this sysfs node to configure accordingly. This sysfs node can also be used to force a test pattern on the DP interface using the following syntax: echo "bpp= pattern_type=" > /sys/class/graphics//config where * bpp = [0/18/24/30] * pattern_type = [0/1/2/3] CRs-Fixed: 1109812 Change-Id: Ia2890e8c8e6214d4a1532f081d7c906484c99a03 Signed-off-by: Ajay Singh Parmar Signed-off-by: Aravind Venkateswaran --- drivers/video/fbdev/msm/mdss_dp.c | 111 +++++++++++++++++++++++++++++- drivers/video/fbdev/msm/mdss_dp.h | 1 + 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index bae93b03a0fc..3590679da102 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -1037,7 +1037,7 @@ static u32 mdss_dp_get_bpp(struct mdss_dp_drv_pdata *dp) * For test pattern, the test data has the bit depth per color * component. Otherwise, set it based on EDID. */ - if (mdss_dp_is_video_pattern_requested(dp)) + 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; @@ -2202,6 +2202,111 @@ 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 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); @@ -2209,13 +2314,15 @@ 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 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, NULL, }; diff --git a/drivers/video/fbdev/msm/mdss_dp.h b/drivers/video/fbdev/msm/mdss_dp.h index 564f490ba497..e871dbabbdb3 100644 --- a/drivers/video/fbdev/msm/mdss_dp.h +++ b/drivers/video/fbdev/msm/mdss_dp.h @@ -443,6 +443,7 @@ 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; From 262e423fdf095e832bb177a0999851f8ee552819 Mon Sep 17 00:00:00 2001 From: Aravind Venkateswaran Date: Wed, 11 Jan 2017 16:38:44 -0800 Subject: [PATCH 17/24] msm: mdss: dp: add support to read per frame CRC values Display Port (DP) controller has the ability to calculate CRC values for every frame. In addition, most DP sinks also have the capability to calculate per frame CRC values. Add support to enable/disable calculation of per-frame CRC values for both the DP controller and the DP sink. Add support to read the computed CRC values via a new sysfs node. See below for usage instructions: To enable/disable per frame CRC calculation: * echo "ctl_cr_en=[0/1]" > /sys/class/graphics//frame_crc * echo "sink_cr_en=[0/1]" > /sys/class/graphics//frame_crc To read computed CRC values: * cat /sys/class/graphics//frame_crc CRs-Fixed: 1109812 Change-Id: I41271db64463b26cbbd9baba43d098bc30da33bf Signed-off-by: Aravind Venkateswaran --- drivers/video/fbdev/msm/mdss_dp.c | 81 +++++++++++++++++ drivers/video/fbdev/msm/mdss_dp.h | 23 +++++ drivers/video/fbdev/msm/mdss_dp_aux.c | 118 +++++++++++++++++++++++++ drivers/video/fbdev/msm/mdss_dp_util.c | 39 ++++++++ drivers/video/fbdev/msm/mdss_dp_util.h | 7 ++ 5 files changed, 268 insertions(+) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index 3590679da102..57841db8bb62 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -2307,6 +2307,84 @@ static ssize_t mdss_dp_rda_config(struct device *dev, 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 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); @@ -2316,6 +2394,8 @@ 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 struct attribute *mdss_dp_fs_attrs[] = { &dev_attr_connected.attr, @@ -2323,6 +2403,7 @@ static struct attribute *mdss_dp_fs_attrs[] = { &dev_attr_hpd.attr, &dev_attr_psm.attr, &dev_attr_config.attr, + &dev_attr_frame_crc.attr, NULL, }; diff --git a/drivers/video/fbdev/msm/mdss_dp.h b/drivers/video/fbdev/msm/mdss_dp.h index e871dbabbdb3..7e1088d0e00a 100644 --- a/drivers/video/fbdev/msm/mdss_dp.h +++ b/drivers/video/fbdev/msm/mdss_dp.h @@ -406,6 +406,13 @@ struct mdss_dp_event_data { 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); @@ -446,6 +453,8 @@ struct mdss_dp_drv_pdata { 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; @@ -927,6 +936,17 @@ static inline char const *mdss_dp_notification_status_to_string( } } +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); @@ -955,5 +975,8 @@ 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); #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 4a3e1e0b259d..85820e1df97a 100644 --- a/drivers/video/fbdev/msm/mdss_dp_aux.c +++ b/drivers/video/fbdev/msm/mdss_dp_aux.c @@ -2127,6 +2127,124 @@ 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); diff --git a/drivers/video/fbdev/msm/mdss_dp_util.c b/drivers/video/fbdev/msm/mdss_dp_util.c index 26420836e530..1e7010dc47e9 100644 --- a/drivers/video/fbdev/msm/mdss_dp_util.c +++ b/drivers/video/fbdev/msm/mdss_dp_util.c @@ -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) { diff --git a/drivers/video/fbdev/msm/mdss_dp_util.h b/drivers/video/fbdev/msm/mdss_dp_util.h index e08a16a02174..cb62d145960f 100644 --- a/drivers/video/fbdev/msm/mdss_dp_util.h +++ b/drivers/video/fbdev/msm/mdss_dp_util.h @@ -74,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) @@ -136,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) @@ -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__ */ From deb557f543ec2451d72fda72b6d63dc9adb2fdd9 Mon Sep 17 00:00:00 2001 From: Aravind Venkateswaran Date: Sat, 14 Jan 2017 19:13:31 -0800 Subject: [PATCH 18/24] msm: mdss: dp: fix handling of HPD IRQ A sink uses the HPD IRQ signal to notify the source of for any link maintenance, link tests and HDCP related messages. Current implementation tears down the entire display pipeline when this signal is received. This may not be necessary as it would be needed to keep the DP interface enabled while any kind of link maintenance is performed. Fix this and ensure that when handling of the HPD IRQ is complete, re-establish the display pipeline. CRs-Fixed: 1109812 Change-Id: Id93c3b147dd206e9718f49e2a053e3ee18162130 Signed-off-by: Aravind Venkateswaran --- drivers/video/fbdev/msm/mdss_dp.c | 224 ++++++++++++++------------ drivers/video/fbdev/msm/mdss_dp.h | 2 - drivers/video/fbdev/msm/mdss_dp_aux.c | 42 ++--- 3 files changed, 143 insertions(+), 125 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index 57841db8bb62..5735f7566d53 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -56,14 +56,19 @@ 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 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) @@ -95,13 +100,6 @@ static inline bool mdss_dp_is_phy_test_pattern_requested( 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 void mdss_dp_put_dt_clk_data(struct device *dev, struct dss_module_power *module_power) { @@ -884,7 +882,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; @@ -1257,8 +1255,9 @@ static void mdss_dp_configure_source_params(struct mdss_dp_drv_pdata *dp, } /** - * 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. @@ -1266,67 +1265,86 @@ 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 (!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 (dp_drv->new_vic && (dp_drv->new_vic != dp_drv->vic)) - dp_init_panel_info(dp_drv, dp_drv->new_vic); + 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. + */ + 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) { @@ -1334,18 +1352,21 @@ 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 */ + mdss_dp_notify_clients(dp_drv, NOTIFY_CONNECT_IRQ_HPD); + return ret; } @@ -1399,8 +1420,6 @@ int mdss_dp_on_hpd(struct mdss_dp_drv_pdata *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); @@ -1417,7 +1436,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; @@ -1463,17 +1482,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; - dp_init_panel_info(dp_drv, HDMI_VFRMT_UNKNOWN); + mdss_dp_ack_state(dp_drv, false); mutex_unlock(&dp_drv->train_mutex); + complete_all(&dp_drv->irq_comp); pr_debug("end\n"); @@ -1523,6 +1538,7 @@ static int mdss_dp_off_hpd(struct mdss_dp_drv_pdata *dp_drv) 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"); @@ -1540,7 +1556,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); @@ -1789,7 +1805,6 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp) dp->sink_info_read = true; end: - mdss_dp_update_cable_status(dp, true); mdss_dp_notify_clients(dp, NOTIFY_CONNECT); return ret; @@ -2086,7 +2101,7 @@ 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, NOTIFY_CONNECT); @@ -2969,19 +2984,22 @@ static inline void mdss_dp_send_test_response(struct mdss_dp_drv_pdata *dp) } /** - * mdss_dp_link_retraining() - initiates link retraining + * mdss_dp_link_maintenance() - initiates link maintenanace * @dp: Display Port Driver data + * @lt_needed: link retraining needed * - * 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_notify_clients(dp, NOTIFY_DISCONNECT_IRQ_HPD)) return; - mdss_dp_on_irq(dp); + mdss_dp_on_irq(dp, lt_needed); } /** @@ -3006,7 +3024,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; } @@ -3037,7 +3055,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; } @@ -3081,7 +3099,7 @@ static int mdss_dp_process_phy_test_pattern_request( 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); + mdss_dp_link_maintenance(dp, true); } mdss_dp_config_ctrl(dp); @@ -3113,6 +3131,28 @@ 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->panel_data.panel_info, + dp->dpcd.max_lane_count); + 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 @@ -3125,6 +3165,11 @@ static int mdss_dp_process_downstream_port_status_change( */ 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)) return -EINVAL; @@ -3135,13 +3180,22 @@ static int mdss_dp_process_video_pattern_request(struct mdss_dp_drv_pdata *dp) mdss_dp_test_video_pattern_to_string( dp->test_data.test_video_pattern)); - /* Send a disconnect notification */ - mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT_IRQ_HPD); + 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); + } - mdss_dp_off_irq(dp); + dp_init_panel_info(dp, dp->vic); + lt_needed = ov_res | mdss_dp_video_pattern_test_lt_needed(dp); - /* Send a connect notification */ - mdss_dp_notify_clients(dp, NOTIFY_CONNECT_IRQ_HPD); + pr_debug("Link training needed: %s", lt_needed ? "yes" : "no"); + + mdss_dp_link_maintenance(dp, lt_needed); mdss_dp_send_test_response(dp); @@ -3189,38 +3243,8 @@ static int mdss_dp_process_hpd_irq_high(struct mdss_dp_drv_pdata *dp) pr_debug("done\n"); exit: - 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_on) - return -EINVAL; - - pr_debug("enter: HPD IRQ low\n"); - - if (dp->hpd_notification_status == NOTIFY_CONNECT_IRQ_HPD) { - mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT); - } else { - /* Clients already notified, so shudown interface directly */ - mdss_dp_update_cable_status(dp, false); - mdss_dp_mainlink_push_idle(&dp->panel_data); - mdss_dp_off_hpd(dp); - } - dp->hpd_irq_on = false; - - 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, @@ -3295,9 +3319,6 @@ 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"); @@ -3308,9 +3329,6 @@ static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv) 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) { @@ -3321,7 +3339,6 @@ 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, NOTIFY_DISCONNECT); pr_debug("Attention: Notified clients\n"); return; @@ -3329,8 +3346,6 @@ static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv) 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) { @@ -3444,6 +3459,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"); diff --git a/drivers/video/fbdev/msm/mdss_dp.h b/drivers/video/fbdev/msm/mdss_dp.h index 7e1088d0e00a..d5e73e264f89 100644 --- a/drivers/video/fbdev/msm/mdss_dp.h +++ b/drivers/video/fbdev/msm/mdss_dp.h @@ -491,7 +491,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; @@ -520,7 +519,6 @@ struct mdss_dp_drv_pdata { char delay_start; struct dp_statistic dp_stat; bool hpd_irq_on; - bool hpd_irq_toggled; u32 hpd_notification_status; struct mdss_dp_event_data dp_event; diff --git a/drivers/video/fbdev/msm/mdss_dp_aux.c b/drivers/video/fbdev/msm/mdss_dp_aux.c index 85820e1df97a..1386adc8cc00 100644 --- a/drivers/video/fbdev/msm/mdss_dp_aux.c +++ b/drivers/video/fbdev/msm/mdss_dp_aux.c @@ -1897,6 +1897,10 @@ 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 */ @@ -1952,6 +1956,10 @@ 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); dp_aux_set_voltage_and_pre_emphasis_lvl(ep); dp_train_pattern_set_write(ep, pattern | 0x20);/* train_2 */ @@ -2031,25 +2039,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"); @@ -2060,10 +2063,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"); @@ -2074,17 +2082,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_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; } @@ -2247,13 +2251,9 @@ int mdss_dp_aux_read_sink_frame_crc(struct mdss_dp_drv_pdata *dp) 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)); From cd78c3e01853e6ddce82186296ee1f802565692a Mon Sep 17 00:00:00 2001 From: Aravind Venkateswaran Date: Sat, 14 Jan 2017 19:30:43 -0800 Subject: [PATCH 19/24] msm: mdss: dp: cap link based on maximum rate supported by the sink Sink reports the maximum link rate that it supports as part of the DPCD information. When calculating the link clock rate, ensure that the calculated rate is always capped at the maximum rate supported by the sink. CRs-Fixed: 1109812 Change-Id: I563f3406606128b1bba705e6db33aa8ede8dbb7d Signed-off-by: Aravind Venkateswaran --- drivers/video/fbdev/msm/mdss_dp.c | 12 ++---------- drivers/video/fbdev/msm/mdss_dp.h | 2 +- drivers/video/fbdev/msm/mdss_dp_aux.c | 9 ++++++++- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index 5735f7566d53..eafe2ed93c34 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -1399,12 +1399,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; @@ -1414,8 +1409,6 @@ 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; @@ -3141,8 +3134,7 @@ static bool mdss_dp_video_pattern_test_lt_needed(struct mdss_dp_drv_pdata *dp) * 2. Lane count changes * For now, assume that lane count is not going to change */ - new_link_rate = mdss_dp_gen_link_clk(&dp->panel_data.panel_info, - dp->dpcd.max_lane_count); + 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) { diff --git a/drivers/video/fbdev/msm/mdss_dp.h b/drivers/video/fbdev/msm/mdss_dp.h index d5e73e264f89..26bf771f8ea5 100644 --- a/drivers/video/fbdev/msm/mdss_dp.h +++ b/drivers/video/fbdev/msm/mdss_dp.h @@ -960,7 +960,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); diff --git a/drivers/video/fbdev/msm/mdss_dp_aux.c b/drivers/video/fbdev/msm/mdss_dp_aux.c index 1386adc8cc00..2b2a92a0326e 100644 --- a/drivers/video/fbdev/msm/mdss_dp_aux.c +++ b/drivers/video/fbdev/msm/mdss_dp_aux.c @@ -499,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); @@ -543,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; } From bb91430ca920d79785c074327a7542d587f38063 Mon Sep 17 00:00:00 2001 From: Tatenda Chipeperekwa Date: Thu, 12 Jan 2017 18:18:28 -0800 Subject: [PATCH 20/24] msm: mdss: dp: fix electrical compliance test sequence Fix the electrical compliance test sequence by bypassing the link training whenever there is a request for a new PHY test pattern. Link training triggers the sending of training patterns which might differ from the requested PHY test pattern. Furthermore, handle the state DP transitions for power on/off since there is no userspace interaction for the electrical compliance tests. CRs-Fixed: 1108048 Change-Id: I9169b7645f7e039c8582993bf88976ff24eb6eca Signed-off-by: Tatenda Chipeperekwa --- drivers/video/fbdev/msm/mdss_dp.c | 141 +++++++++++++++++++------ drivers/video/fbdev/msm/mdss_dp.h | 9 +- drivers/video/fbdev/msm/mdss_dp_aux.c | 128 ++++++++++++++-------- drivers/video/fbdev/msm/mdss_dp_util.c | 29 ++--- 4 files changed, 208 insertions(+), 99 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index eafe2ed93c34..419510fbe999 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -62,6 +62,8 @@ 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) { @@ -1275,6 +1277,9 @@ static int mdss_dp_setup_main_link(struct mdss_dp_drv_pdata *dp, bool train) 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; @@ -1335,7 +1340,8 @@ static int mdss_dp_on_irq(struct mdss_dp_drv_pdata *dp_drv, bool lt_needed) * link clock might have been adjusted as part of the * link maintenance. */ - mdss_dp_disable_mainlink_clocks(dp_drv); + 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; @@ -1365,7 +1371,8 @@ exit_loop: pr_debug("end\n"); /* Send a connect notification */ - mdss_dp_notify_clients(dp_drv, NOTIFY_CONNECT_IRQ_HPD); + if (!mdss_dp_is_phy_test_pattern_requested(dp_drv)) + mdss_dp_notify_clients(dp_drv, NOTIFY_CONNECT_IRQ_HPD); return ret; } @@ -1780,6 +1787,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); @@ -1787,19 +1796,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: + +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; } @@ -2920,7 +2945,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, NOTIFY_DISCONNECT); + + /** + * 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, @@ -3069,38 +3105,62 @@ 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_maintenance(dp, true); + 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; } @@ -3206,6 +3266,8 @@ 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); @@ -3216,6 +3278,10 @@ 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); + if (!ret) + goto exit; + ret = mdss_dp_process_link_status_update(dp); if (!ret) goto exit; @@ -3224,10 +3290,6 @@ 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); - if (!ret) - goto exit; - ret = mdss_dp_process_video_pattern_request(dp); if (!ret) goto exit; @@ -3297,6 +3359,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); @@ -3333,6 +3396,16 @@ static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv) 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; } @@ -3351,6 +3424,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; @@ -3371,8 +3445,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) diff --git a/drivers/video/fbdev/msm/mdss_dp.h b/drivers/video/fbdev/msm/mdss_dp.h index 26bf771f8ea5..57bfdd366012 100644 --- a/drivers/video/fbdev/msm/mdss_dp.h +++ b/drivers/video/fbdev/msm/mdss_dp.h @@ -644,10 +644,10 @@ static inline char *mdss_dp_get_test_response(u32 test_response) enum test_type { UNKNOWN_TEST = 0, - TEST_LINK_TRAINING = BIT(0), - TEST_VIDEO_PATTERN = BIT(1), - 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, }; static inline char *mdss_dp_get_test_name(u32 test_requested) @@ -976,5 +976,6 @@ void mdss_dp_aux_update_voltage_and_pre_emphasis_lvl( 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 2b2a92a0326e..bacf7bc46890 100644 --- a/drivers/video/fbdev/msm/mdss_dp_aux.c +++ b/drivers/video/fbdev/msm/mdss_dp_aux.c @@ -1117,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 @@ -1232,7 +1310,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); @@ -1246,11 +1323,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", @@ -1819,7 +1891,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; @@ -1856,45 +1928,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; @@ -1911,7 +1951,7 @@ static int dp_start_link_train_1(struct mdss_dp_drv_pdata *ep) 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); + mdss_dp_aux_update_voltage_and_pre_emphasis_lvl(ep); tries = 0; old_v_level = ep->v_level; @@ -1942,7 +1982,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; @@ -1968,7 +2008,7 @@ static int dp_start_link_train_2(struct mdss_dp_drv_pdata *ep) wmb(); dp_host_train_set(ep, pattern); - dp_aux_set_voltage_and_pre_emphasis_lvl(ep); + mdss_dp_aux_update_voltage_and_pre_emphasis_lvl(ep); dp_train_pattern_set_write(ep, pattern | 0x20);/* train_2 */ do { @@ -1989,7 +2029,7 @@ static int dp_start_link_train_2(struct mdss_dp_drv_pdata *ep) 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; diff --git a/drivers/video/fbdev/msm/mdss_dp_util.c b/drivers/video/fbdev/msm/mdss_dp_util.c index 1e7010dc47e9..92182e9d61b2 100644 --- a/drivers/video/fbdev/msm/mdss_dp_util.c +++ b/drivers/video/fbdev/msm/mdss_dp_util.c @@ -1435,39 +1435,28 @@ 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); - value |= 0xFC; + value = 0xFC; writel_relaxed(value, io->base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET); writel_relaxed(0x2, io->base + DP_MAINLINK_LEVELS); 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)); @@ -1482,12 +1471,10 @@ 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; + value = 0xFC; writel_relaxed(value, io->base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET); writel_relaxed(0x2, io->base + DP_MAINLINK_LEVELS); @@ -1498,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); } From eae1dd350900c960511c33f8bc16cdc29fed6fef Mon Sep 17 00:00:00 2001 From: Ajay Singh Parmar Date: Sun, 15 Jan 2017 20:35:55 -0800 Subject: [PATCH 21/24] msm: ext_disp: allow multiple requests per client Allow each display client to issue multiple requests for audio only, video only or both to address different use cases for the display clients. CRs-Fixed: 1109812 Change-Id: I38518cebb37da0a48ffd817af9246a7c9682b494 Signed-off-by: Ajay Singh Parmar Signed-off-by: Tatenda Chipeperekwa --- drivers/video/fbdev/msm/mdss_dp.c | 8 +-- drivers/video/fbdev/msm/mdss_hdmi_tx.c | 8 ++- drivers/video/fbdev/msm/msm_ext_display.c | 87 ++++++++++++++++------- include/linux/msm_ext_display.h | 10 +-- 4 files changed, 77 insertions(+), 36 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index 419510fbe999..9553b7a5cefc 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -926,9 +926,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; } @@ -1574,8 +1572,10 @@ 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)) + 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, diff --git a/drivers/video/fbdev/msm/mdss_hdmi_tx.c b/drivers/video/fbdev/msm/mdss_hdmi_tx.c index ae6246f2ca27..e7a79ce83168 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/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 From 28ece3e2f1905b449bc6d5b8a48597af65582435 Mon Sep 17 00:00:00 2001 From: Tatenda Chipeperekwa Date: Tue, 10 Jan 2017 14:19:05 -0800 Subject: [PATCH 22/24] msm: mdss: dp: add support for automated audio tests On receiving audio test request from sink, parse audio parameters and send notifications to audio modules to start audio transmission. CRs-Fixed: 1109812 Change-Id: Id17d82c5b9e1c4bf453f1f1421d2025b32aa410a Signed-off-by: Tatenda Chipeperekwa --- drivers/video/fbdev/msm/mdss_dp.c | 67 +++++- drivers/video/fbdev/msm/mdss_dp.h | 64 ++++++ drivers/video/fbdev/msm/mdss_dp_aux.c | 274 +++++++++++++++++++++++-- drivers/video/fbdev/msm/mdss_dp_util.c | 8 +- 4 files changed, 391 insertions(+), 22 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index 9553b7a5cefc..095a9a6fb44b 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -84,6 +84,12 @@ static inline bool mdss_dp_is_downstream_port_status_changed( 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) { @@ -93,7 +99,8 @@ static inline bool mdss_dp_is_link_training_requested( static inline bool mdss_dp_is_video_pattern_requested( struct mdss_dp_drv_pdata *dp) { - return (dp->test_data.test_requested == TEST_VIDEO_PATTERN); + 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( @@ -1574,8 +1581,11 @@ static int mdss_dp_send_cable_notification( flags |= MSM_EXT_DISP_HPD_VIDEO; - if (!mdss_dp_is_dvi_mode(dp)) + 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, @@ -3164,6 +3174,48 @@ static int mdss_dp_process_phy_test_pattern_request( 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; + + 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; +} + /** * mdss_dp_process_downstream_port_status_change() - process port status changes * @dp: Display Port Driver data @@ -3223,7 +3275,7 @@ static int mdss_dp_process_video_pattern_request(struct mdss_dp_drv_pdata *dp) bool ov_res = false; if (!mdss_dp_is_video_pattern_requested(dp)) - return -EINVAL; + goto end; pr_info("%s: bit depth=%d(%d bpp) pattern=%s\n", mdss_dp_get_test_name(TEST_VIDEO_PATTERN), @@ -3249,9 +3301,14 @@ static int mdss_dp_process_video_pattern_request(struct mdss_dp_drv_pdata *dp) 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; } /** @@ -3294,6 +3351,10 @@ static int mdss_dp_process_hpd_irq_high(struct mdss_dp_drv_pdata *dp) if (!ret) goto exit; + ret = mdss_dp_process_audio_pattern_request(dp); + if (!ret) + goto exit; + pr_debug("done\n"); exit: diff --git a/drivers/video/fbdev/msm/mdss_dp.h b/drivers/video/fbdev/msm/mdss_dp.h index 57bfdd366012..bf74a8a4d7df 100644 --- a/drivers/video/fbdev/msm/mdss_dp.h +++ b/drivers/video/fbdev/msm/mdss_dp.h @@ -284,6 +284,17 @@ struct dpcd_test_request { 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; }; @@ -439,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; @@ -548,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, @@ -648,6 +709,8 @@ enum test_type { 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) @@ -657,6 +720,7 @@ static inline char *mdss_dp_get_test_name(u32 test_requested) 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"; } } diff --git a/drivers/video/fbdev/msm/mdss_dp_aux.c b/drivers/video/fbdev/msm/mdss_dp_aux.c index bacf7bc46890..ca07e80d6613 100644 --- a/drivers/video/fbdev/msm/mdss_dp_aux.c +++ b/drivers/video/fbdev/msm/mdss_dp_aux.c @@ -1295,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 @@ -1572,6 +1802,19 @@ 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 @@ -1582,9 +1825,9 @@ exit: static bool dp_is_test_supported(u32 test_requested) { return (test_requested == TEST_LINK_TRAINING) || - (test_requested == TEST_VIDEO_PATTERN) || (test_requested == TEST_EDID_READ) || - (test_requested == PHY_TEST_PATTERN); + (test_requested == PHY_TEST_PATTERN) || + mdss_dp_is_video_audio_test_requested(test_requested); } /** @@ -1646,26 +1889,27 @@ 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; - case TEST_VIDEO_PATTERN: - ret = dp_parse_video_pattern_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); diff --git a/drivers/video/fbdev/msm/mdss_dp_util.c b/drivers/video/fbdev/msm/mdss_dp_util.c index 92182e9d61b2..10812070807c 100644 --- a/drivers/video/fbdev/msm/mdss_dp_util.c +++ b/drivers/video/fbdev/msm/mdss_dp_util.c @@ -1160,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)); @@ -1208,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)); @@ -1245,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)); @@ -1395,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", From 8647848087315ac492f3d43a37c6351972f5cd97 Mon Sep 17 00:00:00 2001 From: Ajay Singh Parmar Date: Mon, 16 Jan 2017 18:08:15 -0800 Subject: [PATCH 23/24] msm: mdss: dp: add support to disable hdcp For some use cases like running compliance test, it might be necessary to disable hdcp. Add a sysfs node to allow user to take action to enable/disable hdcp for such use cases. Change-Id: I8b9ae6d6d4e750be97fece172f635847ad2e05e4 Signed-off-by: Ajay Singh Parmar --- drivers/video/fbdev/msm/mdss_dp.c | 82 +++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index 095a9a6fb44b..f3c36c5c6b5a 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -1877,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; @@ -1889,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); @@ -2428,6 +2442,50 @@ static ssize_t mdss_dp_rda_frame_crc(struct device *dev, 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); @@ -2439,6 +2497,8 @@ 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, @@ -2447,6 +2507,7 @@ static struct attribute *mdss_dp_fs_attrs[] = { &dev_attr_psm.attr, &dev_attr_config.attr, &dev_attr_frame_crc.attr, + &dev_attr_hdcp_feature.attr, NULL, }; @@ -2514,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) @@ -2543,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) { @@ -2887,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"); } @@ -3188,6 +3247,11 @@ 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), @@ -3438,7 +3502,7 @@ static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv) 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; } From 4709f01bbd7b5412d1220d00945ba1fcdbff331b Mon Sep 17 00:00:00 2001 From: Tatenda Chipeperekwa Date: Mon, 23 Jan 2017 12:22:40 -0800 Subject: [PATCH 24/24] msm: mdss: dp: fix HBR2 pattern generation Fix the HBR2 pattern generation by ensuring that the pattern selection bit is not overwritten by a subsequent register write that updates the scrambler reset count. CRs-Fixed: 1108048 Change-Id: I2d2dcc79de82756eab015a343c24411a735947c9 Signed-off-by: Tatenda Chipeperekwa --- drivers/video/fbdev/msm/mdss_dp_util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/msm/mdss_dp_util.c b/drivers/video/fbdev/msm/mdss_dp_util.c index 10812070807c..1dcf83f094c1 100644 --- a/drivers/video/fbdev/msm/mdss_dp_util.c +++ b/drivers/video/fbdev/msm/mdss_dp_util.c @@ -1449,7 +1449,7 @@ void mdss_dp_phy_send_test_pattern(struct mdss_dp_drv_pdata *dp) value &= ~(1 << 16); writel_relaxed(value, io->base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET); - value = 0xFC; + value |= 0xFC; writel_relaxed(value, io->base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET); writel_relaxed(0x2, io->base + DP_MAINLINK_LEVELS); @@ -1474,7 +1474,7 @@ void mdss_dp_phy_send_test_pattern(struct mdss_dp_drv_pdata *dp) value = BIT(16); writel_relaxed(value, io->base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET); - value = 0xFC; + value |= 0xFC; writel_relaxed(value, io->base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET); writel_relaxed(0x2, io->base + DP_MAINLINK_LEVELS);