diff --git a/msm/dp/dp_audio.c b/msm/dp/dp_audio.c index 67f3741f9ddf..8284207408ba 100644 --- a/msm/dp/dp_audio.c +++ b/msm/dp/dp_audio.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. */ #include @@ -766,6 +766,12 @@ static int dp_audio_off(struct dp_audio *dp_audio) } audio = container_of(dp_audio, struct dp_audio_private, dp_audio); + + if (!atomic_read(&audio->session_on)) { + DP_DEBUG("audio already off\n"); + return rc; + } + ext = &audio->ext_audio_data; work_pending = cancel_delayed_work_sync(&audio->notify_delayed_work); diff --git a/msm/dp/dp_aux.c b/msm/dp/dp_aux.c index 907ccdb0b087..7d73abe4f47f 100644 --- a/msm/dp/dp_aux.c +++ b/msm/dp/dp_aux.c @@ -310,7 +310,7 @@ static void dp_aux_reconfig(struct dp_aux *dp_aux) aux->catalog->reset(aux->catalog); } -static void dp_aux_abort_transaction(struct dp_aux *dp_aux) +static void dp_aux_abort_transaction(struct dp_aux *dp_aux, bool abort) { struct dp_aux_private *aux; @@ -321,7 +321,7 @@ static void dp_aux_abort_transaction(struct dp_aux *dp_aux) aux = container_of(dp_aux, struct dp_aux_private, dp_aux); - atomic_set(&aux->aborted, 1); + atomic_set(&aux->aborted, abort); } static void dp_aux_update_offset_and_segment(struct dp_aux_private *aux, diff --git a/msm/dp/dp_aux.h b/msm/dp/dp_aux.h index e7339fc35bc2..850941b1e53d 100644 --- a/msm/dp/dp_aux.h +++ b/msm/dp/dp_aux.h @@ -50,7 +50,7 @@ struct dp_aux { void (*init)(struct dp_aux *aux, struct dp_aux_cfg *aux_cfg); void (*deinit)(struct dp_aux *aux); void (*reconfig)(struct dp_aux *aux); - void (*abort)(struct dp_aux *aux); + void (*abort)(struct dp_aux *aux, bool abort); void (*dpcd_updated)(struct dp_aux *aux); void (*set_sim_mode)(struct dp_aux *aux, bool en, u8 *edid, u8 *dpcd); int (*aux_switch)(struct dp_aux *aux, bool enable, int orientation); diff --git a/msm/dp/dp_catalog_v420.c b/msm/dp/dp_catalog_v420.c index 10c6801e0934..cc391d172c24 100644 --- a/msm/dp/dp_catalog_v420.c +++ b/msm/dp/dp_catalog_v420.c @@ -247,7 +247,10 @@ static void dp_catalog_ctrl_update_vx_px_v420(struct dp_catalog_ctrl *ctrl, io_data = catalog->io->dp_ahb; version = dp_read(DP_HW_VERSION); - if (version == 0x10020003) { + /* + * For DP controller versions 1.2.3 and 1.2.4 + */ + if ((version == 0x10020003) || (version == 0x10020004)) { if (high) { value0 = dp_swing_hbr2_hbr3[v_level][p_level]; value1 = dp_pre_emp_hbr2_hbr3[v_level][p_level]; diff --git a/msm/dp/dp_ctrl.c b/msm/dp/dp_ctrl.c index be90c50c0e52..1b81192c0278 100644 --- a/msm/dp/dp_ctrl.c +++ b/msm/dp/dp_ctrl.c @@ -102,7 +102,7 @@ static void dp_ctrl_video_ready(struct dp_ctrl_private *ctrl) complete(&ctrl->video_comp); } -static void dp_ctrl_abort(struct dp_ctrl *dp_ctrl) +static void dp_ctrl_abort(struct dp_ctrl *dp_ctrl, bool abort) { struct dp_ctrl_private *ctrl; @@ -113,7 +113,7 @@ static void dp_ctrl_abort(struct dp_ctrl *dp_ctrl) ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); - atomic_set(&ctrl->aborted, 1); + atomic_set(&ctrl->aborted, abort); } static void dp_ctrl_state_ctrl(struct dp_ctrl_private *ctrl, u32 state) diff --git a/msm/dp/dp_ctrl.h b/msm/dp/dp_ctrl.h index ac6af632a342..50b3d48ab9c6 100644 --- a/msm/dp/dp_ctrl.h +++ b/msm/dp/dp_ctrl.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */ #ifndef _DP_CTRL_H_ @@ -19,7 +19,7 @@ struct dp_ctrl { int (*on)(struct dp_ctrl *dp_ctrl, bool mst_mode, bool fec_en, bool dsc_en, bool shallow); void (*off)(struct dp_ctrl *dp_ctrl); - void (*abort)(struct dp_ctrl *dp_ctrl); + void (*abort)(struct dp_ctrl *dp_ctrl, bool abort); void (*isr)(struct dp_ctrl *dp_ctrl); bool (*handle_sink_request)(struct dp_ctrl *dp_ctrl); void (*process_phy_test_request)(struct dp_ctrl *dp_ctrl); diff --git a/msm/dp/dp_debug.c b/msm/dp/dp_debug.c index 3ddb4d9f6989..54cdb3c008dc 100644 --- a/msm/dp/dp_debug.c +++ b/msm/dp/dp_debug.c @@ -1596,8 +1596,8 @@ static void dp_debug_set_sim_mode(struct dp_debug_private *debug, bool sim) debug->hpd->simulate_connect(debug->hpd, false); debug->hotplug = false; } - debug->aux->abort(debug->aux); - debug->ctrl->abort(debug->ctrl); + debug->aux->abort(debug->aux, true); + debug->ctrl->abort(debug->ctrl, true); debug->aux->set_sim_mode(debug->aux, false, NULL, NULL); debug->dp_debug.sim_mode = false; diff --git a/msm/dp/dp_debug.h b/msm/dp/dp_debug.h index c5d09699a0df..65874f2132bd 100644 --- a/msm/dp/dp_debug.h +++ b/msm/dp/dp_debug.h @@ -13,13 +13,33 @@ #include "dp_display.h" #include "dp_pll.h" -#define DP_WARN(fmt, ...) DRM_WARN("[msm-dp-warn] "fmt, ##__VA_ARGS__) -#define DP_ERR(fmt, ...) DRM_DEV_ERROR(NULL, "[msm-dp-error]" fmt, \ - ##__VA_ARGS__) -#define DP_INFO(fmt, ...) DRM_DEV_INFO(NULL, "[msm-dp-info] "fmt, \ - ##__VA_ARGS__) -#define DP_DEBUG(fmt, ...) DRM_DEV_DEBUG_DP(NULL, "[msm-dp-debug] "fmt, \ - ##__VA_ARGS__) +#define DP_DEBUG(fmt, ...) \ + do { \ + if (unlikely(drm_debug & DRM_UT_KMS)) \ + DRM_DEBUG("[msm-dp-debug][%-4d]"fmt, current->pid, \ + ##__VA_ARGS__); \ + else \ + pr_debug("[drm:%s][msm-dp-debug][%-4d]"fmt, __func__,\ + current->pid, ##__VA_ARGS__); \ + } while (0) + +#define DP_INFO(fmt, ...) \ + do { \ + if (unlikely(drm_debug & DRM_UT_KMS)) \ + DRM_INFO("[msm-dp-info][%-4d]"fmt, current->pid, \ + ##__VA_ARGS__); \ + else \ + pr_info("[drm:%s][msm-dp-info][%-4d]"fmt, __func__, \ + current->pid, ##__VA_ARGS__); \ + } while (0) + +#define DP_WARN(fmt, ...) \ + pr_warn("[drm:%s][msm-dp-warn][%-4d]"fmt, __func__, \ + current->pid, ##__VA_ARGS__) + +#define DP_ERR(fmt, ...) \ + pr_err("[drm:%s][msm-dp-err][%-4d]"fmt, __func__, \ + current->pid, ##__VA_ARGS__) /** * struct dp_debug diff --git a/msm/dp/dp_display.c b/msm/dp/dp_display.c index 92334bde6a8e..63eb244fb0ea 100644 --- a/msm/dp/dp_display.c +++ b/msm/dp/dp_display.c @@ -28,16 +28,27 @@ #include "sde_hdcp.h" #include "dp_debug.h" #include "dp_pll.h" +#include "sde_dbg.h" #define DP_MST_DEBUG(fmt, ...) DP_DEBUG(fmt, ##__VA_ARGS__) #define dp_display_state_show(x) { \ DP_ERR("%s: state (0x%x): %s\n", x, dp->state, \ - dp_display_state_name(dp->state)); } + dp_display_state_name(dp->state)); \ + SDE_EVT32_EXTERNAL(dp->state); } + +#define dp_display_state_log(x) { \ + DP_DEBUG("%s: state (0x%x): %s\n", x, dp->state, \ + dp_display_state_name(dp->state)); \ + SDE_EVT32_EXTERNAL(dp->state); } #define dp_display_state_is(x) (dp->state & (x)) -#define dp_display_state_add(x) (dp->state |= (x)) -#define dp_display_state_remove(x) (dp->state &= ~(x)) +#define dp_display_state_add(x) { \ + (dp->state |= (x)); \ + dp_display_state_log("add "#x); } +#define dp_display_state_remove(x) { \ + (dp->state &= ~(x)); \ + dp_display_state_log("remove "#x); } enum dp_display_states { DP_STATE_DISCONNECTED = 0, @@ -51,6 +62,7 @@ enum dp_display_states { DP_STATE_SUSPENDED = BIT(7), DP_STATE_ABORTED = BIT(8), DP_STATE_HDCP_ABORTED = BIT(9), + DP_STATE_SRC_PWRDN = BIT(10), }; static char *dp_display_state_name(enum dp_display_states state) @@ -100,6 +112,10 @@ static char *dp_display_state_name(enum dp_display_states state) len += scnprintf(buf + len, sizeof(buf) - len, "|%s|", "HDCP_ABORTED"); + if (state & DP_STATE_SRC_PWRDN) + len += scnprintf(buf + len, sizeof(buf) - len, "|%s|", + "SRC_PWRDN"); + if (!strlen(buf)) return "DISCONNECTED"; @@ -235,6 +251,30 @@ static bool dp_display_is_ready(struct dp_display_private *dp) dp->hpd->alt_mode_cfg_done; } +static void dp_audio_enable(struct dp_display_private *dp, bool enable) +{ + struct dp_panel *dp_panel; + int idx; + + for (idx = DP_STREAM_0; idx < DP_STREAM_MAX; idx++) { + if (!dp->active_panels[idx]) + continue; + dp_panel = dp->active_panels[idx]; + + if (dp_panel->audio_supported) { + if (enable) { + dp_panel->audio->bw_code = + dp->link->link_params.bw_code; + dp_panel->audio->lane_count = + dp->link->link_params.lane_count; + dp_panel->audio->on(dp_panel->audio); + } else { + dp_panel->audio->off(dp_panel->audio); + } + } + } +} + static void dp_display_update_hdcp_status(struct dp_display_private *dp, bool reset) { @@ -362,35 +402,19 @@ static void dp_display_hdcp_deregister_stream(struct dp_display_private *dp, } } -static void dp_display_hdcp_cb_work(struct work_struct *work) +static void dp_display_hdcp_process_delayed_off(struct dp_display_private *dp) { - struct dp_display_private *dp; - struct delayed_work *dw = to_delayed_work(work); - struct sde_hdcp_ops *ops; - struct dp_link_hdcp_status *status; - void *data; - int rc = 0; - u32 hdcp_auth_state; - u8 sink_status = 0; - - dp = container_of(dw, struct dp_display_private, hdcp_cb_work); - - if (!dp_display_state_is(DP_STATE_ENABLED | DP_STATE_CONNECTED) || - dp_display_state_is(DP_STATE_ABORTED | DP_STATE_HDCP_ABORTED)) - return; - - if (dp_display_state_is(DP_STATE_SUSPENDED)) { - DP_DEBUG("System suspending. Delay HDCP operations\n"); - queue_delayed_work(dp->wq, &dp->hdcp_cb_work, HZ); - return; - } - if (dp->hdcp_delayed_off) { if (dp->hdcp.ops && dp->hdcp.ops->off) dp->hdcp.ops->off(dp->hdcp.data); dp_display_update_hdcp_status(dp, true); dp->hdcp_delayed_off = false; } +} + +static int dp_display_hdcp_process_sink_sync(struct dp_display_private *dp) +{ + u8 sink_status = 0; if (dp->debug->hdcp_wait_sink_sync) { drm_dp_dpcd_readb(dp->aux->drm_aux, DP_SINK_STATUS, @@ -400,42 +424,59 @@ static void dp_display_hdcp_cb_work(struct work_struct *work) if (sink_status < 1) { DP_DEBUG("Sink not synchronized. Queuing again then exiting\n"); queue_delayed_work(dp->wq, &dp->hdcp_cb_work, HZ); - return; + return -EAGAIN; } } - status = &dp->link->hdcp_status; + return 0; +} - if (status->hdcp_state == HDCP_STATE_INACTIVE) { - dp_display_check_source_hdcp_caps(dp); - dp_display_update_hdcp_info(dp); +static int dp_display_hdcp_start(struct dp_display_private *dp) +{ + if (dp->link->hdcp_status.hdcp_state != HDCP_STATE_INACTIVE) + return -EINVAL; - if (dp_display_is_hdcp_enabled(dp)) { - if (dp->hdcp.ops && dp->hdcp.ops->on && - dp->hdcp.ops->on(dp->hdcp.data)) { - dp_display_update_hdcp_status(dp, true); - return; - } - } else { + dp_display_check_source_hdcp_caps(dp); + dp_display_update_hdcp_info(dp); + + if (dp_display_is_hdcp_enabled(dp)) { + if (dp->hdcp.ops && dp->hdcp.ops->on && + dp->hdcp.ops->on(dp->hdcp.data)) { dp_display_update_hdcp_status(dp, true); - return; + return 0; } + } else { + dp_display_update_hdcp_status(dp, true); + return 0; } + return -EINVAL; +} + +static void dp_display_hdcp_print_auth_state(struct dp_display_private *dp) +{ + u32 hdcp_auth_state; + int rc; + rc = dp->catalog->ctrl.read_hdcp_status(&dp->catalog->ctrl); if (rc >= 0) { hdcp_auth_state = (rc >> 20) & 0x3; DP_DEBUG("hdcp auth state %d\n", hdcp_auth_state); } +} + +static void dp_display_hdcp_process_state(struct dp_display_private *dp) +{ + struct dp_link_hdcp_status *status; + struct sde_hdcp_ops *ops; + void *data; + int rc = 0; + + status = &dp->link->hdcp_status; ops = dp->hdcp.ops; data = dp->hdcp.data; - DP_DEBUG("%s: %s\n", sde_hdcp_version(status->hdcp_version), - sde_hdcp_state_name(status->hdcp_state)); - - dp_display_update_hdcp_status(dp, false); - if (status->hdcp_state != HDCP_STATE_AUTHENTICATED && dp->debug->force_encryption && ops && ops->force_encryption) ops->force_encryption(data, dp->debug->force_encryption); @@ -472,6 +513,62 @@ static void dp_display_hdcp_cb_work(struct work_struct *work) } } +static void dp_display_abort_hdcp(struct dp_display_private *dp, + bool abort) +{ + u8 i = HDCP_VERSION_2P2; + struct dp_hdcp_dev *dev = NULL; + + while (i) { + dev = &dp->hdcp.dev[i]; + i >>= 1; + if (!(dp->hdcp.source_cap & dev->ver)) + continue; + + dev->ops->abort(dev->fd, abort); + } +} + +static void dp_display_hdcp_cb_work(struct work_struct *work) +{ + struct dp_display_private *dp; + struct delayed_work *dw = to_delayed_work(work); + struct dp_link_hdcp_status *status; + int rc = 0; + + dp = container_of(dw, struct dp_display_private, hdcp_cb_work); + + if (!dp_display_state_is(DP_STATE_ENABLED | DP_STATE_CONNECTED) || + dp_display_state_is(DP_STATE_ABORTED | DP_STATE_HDCP_ABORTED)) + return; + + if (dp_display_state_is(DP_STATE_SUSPENDED)) { + DP_DEBUG("System suspending. Delay HDCP operations\n"); + queue_delayed_work(dp->wq, &dp->hdcp_cb_work, HZ); + return; + } + + dp_display_hdcp_process_delayed_off(dp); + + rc = dp_display_hdcp_process_sink_sync(dp); + if (rc) + return; + + rc = dp_display_hdcp_start(dp); + if (!rc) + return; + + dp_display_hdcp_print_auth_state(dp); + + status = &dp->link->hdcp_status; + DP_DEBUG("%s: %s\n", sde_hdcp_version(status->hdcp_version), + sde_hdcp_state_name(status->hdcp_state)); + + dp_display_update_hdcp_status(dp, false); + + dp_display_hdcp_process_state(dp); +} + static void dp_display_notify_hdcp_status_cb(void *ptr, enum sde_hdcp_state state) { @@ -680,6 +777,25 @@ static int dp_display_send_hpd_notification(struct dp_display_private *dp) int ret = 0; bool hpd = !!dp_display_state_is(DP_STATE_CONNECTED); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state, hpd); + + /* + * Send the notification only if there is any change. This check is + * necessary since it is possible that the connect_work may or may not + * skip sending the notification in order to respond to a pending + * attention message. Attention work thread will always attempt to + * send the notification after successfully handling the attention + * message. This check here will avoid any unintended duplicate + * notifications. + */ + if (dp_display_state_is(DP_STATE_CONNECT_NOTIFIED) && hpd) { + DP_DEBUG("connection notified already, skip notification\n"); + goto skip_wait; + } else if (dp_display_state_is(DP_STATE_DISCONNECT_NOTIFIED) && !hpd) { + DP_DEBUG("disonnect notified already, skip notification\n"); + goto skip_wait; + } + dp->aux->state |= DP_STATE_NOTIFICATION_SENT; if (!dp->mst.mst_active) @@ -702,8 +818,10 @@ static int dp_display_send_hpd_notification(struct dp_display_private *dp) DP_WARN("%s timeout\n", hpd ? "connect" : "disconnect"); ret = -EINVAL; } + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state, hpd, ret); return ret; skip_wait: + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state, hpd, ret); return 0; } @@ -777,8 +895,10 @@ static void dp_display_host_init(struct dp_display_private *dp) bool flip = false; bool reset; - if (dp_display_state_is(DP_STATE_INITIALIZED)) + if (dp_display_state_is(DP_STATE_INITIALIZED)) { + dp_display_state_log("[already initialized]"); return; + } if (dp->hpd->orientation == ORIENTATION_CC2) flip = true; @@ -790,6 +910,7 @@ static void dp_display_host_init(struct dp_display_private *dp) dp->hpd->host_init(dp->hpd, &dp->catalog->hpd); dp->ctrl->init(dp->ctrl, flip, reset); enable_irq(dp->irq); + dp_display_abort_hdcp(dp, false); dp_display_state_add(DP_STATE_INITIALIZED); @@ -799,40 +920,75 @@ static void dp_display_host_init(struct dp_display_private *dp) static void dp_display_host_ready(struct dp_display_private *dp) { - if (!dp_display_state_is(DP_STATE_INITIALIZED)) + if (!dp_display_state_is(DP_STATE_INITIALIZED)) { + dp_display_state_show("[not initialized]"); return; + } - if (dp_display_state_is(DP_STATE_READY)) + if (dp_display_state_is(DP_STATE_READY)) { + dp_display_state_log("[already ready]"); return; + } + + /* + * Reset the aborted state for AUX and CTRL modules. This will + * allow these modules to execute normally in response to the + * cable connection event. + * + * One corner case still exists. While the execution flow ensures + * that cable disconnection flushes all pending work items on the DP + * workqueue, and waits for the user module to clean up the DP + * connection session, it is possible that the system delays can + * lead to timeouts in the connect path. As a result, the actual + * connection callback from user modules can come in late and can + * race against a subsequent connection event here which would have + * reset the aborted flags. There is no clear solution for this since + * the connect/disconnect notifications do not currently have any + * sessions IDs. + */ + dp->aux->abort(dp->aux, false); + dp->ctrl->abort(dp->ctrl, false); dp->aux->init(dp->aux, dp->parser->aux_cfg); dp->panel->init(dp->panel); dp_display_state_add(DP_STATE_READY); + /* log this as it results from user action of cable connection */ + DP_INFO("[OK]\n"); } static void dp_display_host_unready(struct dp_display_private *dp) { - if (!dp_display_state_is(DP_STATE_INITIALIZED)) + if (!dp_display_state_is(DP_STATE_INITIALIZED)) { + dp_display_state_show("[not initialized]"); return; + } - if (!dp_display_state_is(DP_STATE_READY)) + if (!dp_display_state_is(DP_STATE_READY)) { + dp_display_state_show("[not ready]"); return; + } dp_display_state_remove(DP_STATE_READY); dp->aux->deinit(dp->aux); + /* log this as it results from user action of cable disconnection */ + DP_INFO("[OK]\n"); } static void dp_display_host_deinit(struct dp_display_private *dp) { if (dp->active_stream_cnt) { + SDE_EVT32_EXTERNAL(dp->state, dp->active_stream_cnt); DP_DEBUG("active stream present\n"); return; } - if (!dp_display_state_is(DP_STATE_INITIALIZED)) + if (!dp_display_state_is(DP_STATE_INITIALIZED)) { + dp_display_state_show("[not initialized]"); return; + } + dp_display_abort_hdcp(dp, true); dp->ctrl->deinit(dp->ctrl); dp->hpd->host_deinit(dp->hpd, &dp->catalog->hpd); dp->power->deinit(dp->power); @@ -849,6 +1005,7 @@ static int dp_display_process_hpd_high(struct dp_display_private *dp) { int rc = -EINVAL; + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state); mutex_lock(&dp->session_lock); if (dp_display_state_is(DP_STATE_CONNECTED)) { @@ -861,7 +1018,26 @@ static int dp_display_process_hpd_high(struct dp_display_private *dp) dp->dp_display.max_pclk_khz = min(dp->parser->max_pclk_khz, dp->debug->max_pclk_khz); - dp_display_host_init(dp); + + /* + * If dp video session is not restored from a previous session teardown + * by userspace, ensure the host_init is executed, in such a scenario, + * so that all the required DP resources are enabled. + * + * Below is one of the sequences of events which describe the above + * scenario: + * a. Source initiated power down resulting in host_deinit. + * b. Sink issues hpd low attention without physical cable disconnect. + * c. Source initiated power up sequence returns early because hpd is + * not high. + * d. Sink issues a hpd high attention event. + */ + if (dp_display_state_is(DP_STATE_SRC_PWRDN) && + dp_display_state_is(DP_STATE_CONFIGURED)) { + dp_display_host_init(dp); + dp_display_state_remove(DP_STATE_SRC_PWRDN); + } + dp_display_host_ready(dp); dp->link->psm_config(dp->link, &dp->panel->link_info, false); @@ -899,9 +1075,37 @@ static int dp_display_process_hpd_high(struct dp_display_private *dp) end: mutex_unlock(&dp->session_lock); + /* + * Delay the HPD connect notification to see if sink generates any + * IRQ HPDs immediately after the HPD high. + */ + usleep_range(10000, 10100); + + /* + * If an IRQ HPD is pending, then do not send a connect notification. + * Once this work returns, the IRQ HPD would be processed and any + * required actions (such as link maintenance) would be done which + * will subsequently send the HPD notification. To keep things simple, + * do this only for SST use-cases. MST use cases require additional + * care in order to handle the side-band communications as well. + * + * One of the main motivations for this is DP LL 1.4 CTS use case + * where it is possible that we could get a test request right after + * a connection, and the strict timing requriements of the test can + * only be met if we do not wait for the e2e connection to be set up. + */ + if (!dp->mst.mst_active && + (work_busy(&dp->attention_work) == WORK_BUSY_PENDING)) { + SDE_EVT32_EXTERNAL(dp->state, 99); + DP_DEBUG("Attention pending, skip HPD notification\n"); + goto skip_notify; + } + if (!rc && !dp_display_state_is(DP_STATE_ABORTED)) dp_display_send_hpd_notification(dp); +skip_notify: + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state, rc); return rc; } @@ -928,7 +1132,7 @@ static int dp_display_process_hpd_low(struct dp_display_private *dp) dp_display_state_remove(DP_STATE_CONNECTED); dp->process_hpd_connect = false; - + dp_audio_enable(dp, false); dp_display_process_mst_hpd_low(dp); if ((dp_display_state_is(DP_STATE_CONNECT_NOTIFIED) || @@ -1013,36 +1217,14 @@ static void dp_display_stream_disable(struct dp_display_private *dp, dp->active_stream_cnt--; } -static void dp_audio_enable(struct dp_display_private *dp, bool enable) -{ - struct dp_panel *dp_panel; - int idx; - - for (idx = DP_STREAM_0; idx < DP_STREAM_MAX; idx++) { - if (!dp->active_panels[idx]) - continue; - dp_panel = dp->active_panels[idx]; - - if (dp_panel->audio_supported) { - if (enable) { - dp_panel->audio->bw_code = - dp->link->link_params.bw_code; - dp_panel->audio->lane_count = - dp->link->link_params.lane_count; - dp_panel->audio->on(dp->panel->audio); - } else { - dp_panel->audio->off(dp_panel->audio); - } - } - } -} - static void dp_display_clean(struct dp_display_private *dp) { int idx; struct dp_panel *dp_panel; struct dp_link_hdcp_status *status = &dp->link->hdcp_status; + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state); + if (dp_display_is_hdcp_enabled(dp) && status->hdcp_state != HDCP_STATE_INACTIVE) { cancel_delayed_work_sync(&dp->hdcp_cb_work); @@ -1068,17 +1250,19 @@ static void dp_display_clean(struct dp_display_private *dp) dp_display_state_remove(DP_STATE_ENABLED | DP_STATE_CONNECTED); dp->ctrl->off(dp->ctrl); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state); } static int dp_display_handle_disconnect(struct dp_display_private *dp) { int rc; + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state); rc = dp_display_process_hpd_low(dp); if (rc) { /* cancel any pending request */ - dp->ctrl->abort(dp->ctrl); - dp->aux->abort(dp->aux); + dp->ctrl->abort(dp->ctrl, true); + dp->aux->abort(dp->aux, true); } mutex_lock(&dp->session_lock); @@ -1086,10 +1270,10 @@ static int dp_display_handle_disconnect(struct dp_display_private *dp) dp_display_clean(dp); dp_display_host_unready(dp); - dp_display_host_deinit(dp); mutex_unlock(&dp->session_lock); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state); return rc; } @@ -1098,8 +1282,8 @@ static void dp_display_disconnect_sync(struct dp_display_private *dp) /* cancel any pending request */ dp_display_state_add(DP_STATE_ABORTED); - dp->ctrl->abort(dp->ctrl); - dp->aux->abort(dp->aux); + dp->ctrl->abort(dp->ctrl, true); + dp->aux->abort(dp->aux, true); /* wait for idle state */ cancel_work_sync(&dp->connect_work); @@ -1127,16 +1311,24 @@ static int dp_display_usbpd_disconnect_cb(struct device *dev) goto end; } - dp_display_state_remove(DP_STATE_CONFIGURED); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state, + dp->debug->psm_enabled); if (dp->debug->psm_enabled && dp_display_state_is(DP_STATE_READY)) dp->link->psm_config(dp->link, &dp->panel->link_info, true); dp_display_disconnect_sync(dp); + mutex_lock(&dp->session_lock); + dp_display_host_deinit(dp); + dp_display_state_remove(DP_STATE_CONFIGURED); + mutex_unlock(&dp->session_lock); + if (!dp->debug->sim_mode && !dp->parser->no_aux_switch && !dp->parser->gpio_aux_switch) dp->aux->aux_switch(dp->aux, false, ORIENTATION_NONE); + + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state); end: return rc; } @@ -1185,7 +1377,9 @@ static void dp_display_attention_work(struct work_struct *work) struct dp_display_private *dp = container_of(work, struct dp_display_private, attention_work); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state); mutex_lock(&dp->session_lock); + SDE_EVT32_EXTERNAL(dp->state); if (dp->debug->mst_hpd_sim || !dp_display_state_is(DP_STATE_READY)) { mutex_unlock(&dp->session_lock); @@ -1198,11 +1392,17 @@ static void dp_display_attention_work(struct work_struct *work) } mutex_unlock(&dp->session_lock); + SDE_EVT32_EXTERNAL(dp->state, dp->link->sink_request); if (dp->link->sink_request & DS_PORT_STATUS_CHANGED) { + SDE_EVT32_EXTERNAL(dp->state, DS_PORT_STATUS_CHANGED); if (dp_display_is_sink_count_zero(dp)) { dp_display_handle_disconnect(dp); } else { + /* + * connect work should take care of sending + * the HPD notification. + */ if (!dp->mst.mst_active) queue_work(dp->wq, &dp->connect_work); } @@ -1211,9 +1411,14 @@ static void dp_display_attention_work(struct work_struct *work) } if (dp->link->sink_request & DP_TEST_LINK_VIDEO_PATTERN) { + SDE_EVT32_EXTERNAL(dp->state, DP_TEST_LINK_VIDEO_PATTERN); dp_display_handle_disconnect(dp); dp->panel->video_test = true; + /* + * connect work should take care of sending + * the HPD notification. + */ queue_work(dp->wq, &dp->connect_work); goto mst_attention; @@ -1224,20 +1429,24 @@ static void dp_display_attention_work(struct work_struct *work) mutex_lock(&dp->session_lock); dp_audio_enable(dp, false); - mutex_unlock(&dp->session_lock); - if (dp->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) + if (dp->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { + SDE_EVT32_EXTERNAL(dp->state, + DP_TEST_LINK_PHY_TEST_PATTERN); dp->ctrl->process_phy_test_request(dp->ctrl); + } if (dp->link->sink_request & DP_TEST_LINK_TRAINING) { + SDE_EVT32_EXTERNAL(dp->state, DP_TEST_LINK_TRAINING); dp->link->send_test_response(dp->link); dp->ctrl->link_maintenance(dp->ctrl); } - if (dp->link->sink_request & DP_LINK_STATUS_UPDATED) + if (dp->link->sink_request & DP_LINK_STATUS_UPDATED) { + SDE_EVT32_EXTERNAL(dp->state, DP_LINK_STATUS_UPDATED); dp->ctrl->link_maintenance(dp->ctrl); + } - mutex_lock(&dp->session_lock); dp_audio_enable(dp, true); mutex_unlock(&dp->session_lock); @@ -1249,8 +1458,21 @@ static void dp_display_attention_work(struct work_struct *work) cp_irq: if (dp_display_is_hdcp_enabled(dp) && dp->hdcp.ops->cp_irq) dp->hdcp.ops->cp_irq(dp->hdcp.data); + + if (!dp->mst.mst_active) { + /* + * It is possible that the connect_work skipped sending + * the HPD notification if the attention message was + * already pending. Send the notification here to + * account for that. This is not needed if this + * attention work was handling a test request + */ + dp_display_send_hpd_notification(dp); + } + mst_attention: dp_display_mst_attention(dp); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state); } static int dp_display_usbpd_attention_cb(struct device *dev) @@ -1272,6 +1494,9 @@ static int dp_display_usbpd_attention_cb(struct device *dev) dp->hpd->hpd_irq, dp->hpd->hpd_high, !!dp_display_state_is(DP_STATE_ENABLED), !!dp_display_state_is(DP_STATE_CONNECTED)); + SDE_EVT32_EXTERNAL(dp->state, dp->hpd->hpd_irq, dp->hpd->hpd_high, + !!dp_display_state_is(DP_STATE_ENABLED), + !!dp_display_state_is(DP_STATE_CONNECTED)); if (!dp->hpd->hpd_high) { dp_display_disconnect_sync(dp); @@ -1656,16 +1881,48 @@ static int dp_display_prepare(struct dp_display *dp_display, void *panel) dp = container_of(dp_display, struct dp_display_private, dp_display); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state); mutex_lock(&dp->session_lock); - if (dp_display_state_is(DP_STATE_ABORTED | DP_STATE_ENABLED)) - goto end; + /* + * If DP video session is restored by the userspace after display + * disconnect notification from dongle i.e. typeC cable connected to + * source but disconnected at the display side, the DP controller is + * not restored to the desired configured state. So, ensure host_init + * is executed in such a scenario so that all the DP controller + * resources are enabled for the next connection event. + */ + if (dp_display_state_is(DP_STATE_SRC_PWRDN) && + dp_display_state_is(DP_STATE_CONFIGURED)) { + dp_display_host_init(dp); + dp_display_state_remove(DP_STATE_SRC_PWRDN); + } - if (!dp_display_is_ready(dp)) + /* + * If the physical connection to the sink is already lost by the time + * we try to set up the connection, we can just skip all the steps + * here safely. + */ + if (dp_display_state_is(DP_STATE_ABORTED)) { + dp_display_state_log("[aborted]"); goto end; + } + + /* + * If DP_STATE_ENABLED, there is nothing left to do. + * However, this should not happen ideally. So, log this. + */ + if (dp_display_state_is(DP_STATE_ENABLED)) { + dp_display_state_show("[already enabled]"); + goto end; + } + + if (!dp_display_is_ready(dp)) { + dp_display_state_show("[not ready]"); + goto end; + } /* For supporting DP_PANEL_SRC_INITIATED_POWER_DOWN case */ - dp_display_host_init(dp); dp_display_host_ready(dp); if (dp->debug->psm_enabled) { @@ -1691,6 +1948,7 @@ static int dp_display_prepare(struct dp_display *dp_display, void *panel) end: mutex_unlock(&dp->session_lock); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state); return 0; } @@ -1772,15 +2030,28 @@ static int dp_display_enable(struct dp_display *dp_display, void *panel) dp = container_of(dp_display, struct dp_display_private, dp_display); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state); mutex_lock(&dp->session_lock); + /* + * If DP_STATE_READY is not set, we should not do any HW + * programming. + */ if (!dp_display_state_is(DP_STATE_READY)) { dp_display_state_show("[host not ready]"); goto end; } + /* + * It is possible that by the time we get call back to establish + * the DP pipeline e2e, the physical DP connection to the sink is + * already lost. In such cases, the DP_STATE_ABORTED would be set. + * However, it is necessary to NOT abort the display setup here so as + * to ensure that the rest of the system is in a stable state prior to + * handling the disconnect notification. + */ if (dp_display_state_is(DP_STATE_ABORTED)) - goto end; + dp_display_state_log("[aborted, but continue on]"); rc = dp_display_stream_enable(dp, panel); if (rc) @@ -1790,6 +2061,7 @@ static int dp_display_enable(struct dp_display *dp_display, void *panel) dp_display_state_add(DP_STATE_ENABLED); end: mutex_unlock(&dp->session_lock); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state); return rc; } @@ -1813,15 +2085,27 @@ static int dp_display_post_enable(struct dp_display *dp_display, void *panel) dp = container_of(dp_display, struct dp_display_private, dp_display); dp_panel = panel; + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state); mutex_lock(&dp->session_lock); + /* + * If DP_STATE_READY is not set, we should not do any HW + * programming. + */ if (!dp_display_state_is(DP_STATE_ENABLED)) { dp_display_state_show("[not enabled]"); goto end; } - if (dp_display_state_is(DP_STATE_ABORTED)) + /* + * If the physical connection to the sink is already lost by the time + * we try to set up the connection, we can just skip all the steps + * here safely. + */ + if (dp_display_state_is(DP_STATE_ABORTED)) { + dp_display_state_log("[aborted]"); goto end; + } if (!dp_display_is_ready(dp) || !dp_display_state_is(DP_STATE_READY)) { dp_display_state_show("[not ready]"); @@ -1843,6 +2127,7 @@ end: complete_all(&dp->notification_comp); mutex_unlock(&dp->session_lock); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state); return 0; } @@ -1871,6 +2156,7 @@ static int dp_display_pre_disable(struct dp_display *dp_display, void *panel) dp = container_of(dp_display, struct dp_display_private, dp_display); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state); mutex_lock(&dp->session_lock); status = &dp->link->hdcp_status; @@ -1922,6 +2208,7 @@ clean: end: mutex_unlock(&dp->session_lock); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state); return 0; } @@ -1941,6 +2228,7 @@ static int dp_display_disable(struct dp_display *dp_display, void *panel) dp_panel = panel; status = &dp->link->hdcp_status; + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state); mutex_lock(&dp->session_lock); if (!dp_display_state_is(DP_STATE_ENABLED)) { @@ -1967,6 +2255,7 @@ static int dp_display_disable(struct dp_display *dp_display, void *panel) } end: mutex_unlock(&dp->session_lock); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state); return 0; } @@ -2028,6 +2317,7 @@ static int dp_display_unprepare(struct dp_display *dp_display, void *panel) dp = container_of(dp_display, struct dp_display_private, dp_display); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state); mutex_lock(&dp->session_lock); /* @@ -2049,6 +2339,7 @@ static int dp_display_unprepare(struct dp_display *dp_display, void *panel) dp->ctrl->off(dp->ctrl); dp_display_host_unready(dp); dp_display_host_deinit(dp); + dp_display_state_add(DP_STATE_SRC_PWRDN); } dp_display_state_remove(DP_STATE_ENABLED); @@ -2061,29 +2352,155 @@ static int dp_display_unprepare(struct dp_display *dp_display, void *panel) end: dp_panel->deinit(dp_panel, flags); mutex_unlock(&dp->session_lock); + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state); return 0; } +static int dp_display_validate_link_clock(struct dp_display_private *dp, + struct drm_display_mode *mode, struct dp_display_mode dp_mode) +{ + struct drm_dp_link *link_info; + u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; + bool dsc_en; + int rate; + + link_info = &dp->panel->link_info; + + dsc_en = (dp_mode.timing.comp_info.comp_ratio > 1) ? true : false; + mode_bpp = dsc_en ? + DSC_BPP(dp_mode.timing.comp_info.dsc_info.config) + : dp_mode.timing.bpp; + + mode_rate_khz = mode->clock * mode_bpp; + rate = drm_dp_bw_code_to_link_rate(dp->link->link_params.bw_code); + supported_rate_khz = link_info->num_lanes * rate * 8; + + if (mode_rate_khz > supported_rate_khz) { + DP_DEBUG("mode_rate: %d kHz, supported_rate: %d kHz\n", + mode_rate_khz, supported_rate_khz); + return -EPERM; + } + + return 0; +} + +static int dp_display_validate_pixel_clock(struct dp_display_mode dp_mode, + u32 max_pclk_khz) +{ + u32 pclk_khz = dp_mode.timing.widebus_en ? + (dp_mode.timing.pixel_clk_khz >> 1) : + dp_mode.timing.pixel_clk_khz; + + if (pclk_khz > max_pclk_khz) { + DP_DEBUG("clk: %d kHz, max: %d kHz\n", pclk_khz, max_pclk_khz); + return -EPERM; + } + + return 0; +} + +static int dp_display_validate_mixers(struct msm_drm_private *priv, + struct drm_display_mode *mode, + const struct msm_resource_caps_info *avail_res) +{ + int rc; + u32 num_lm = 0; + + rc = msm_get_mixer_count(priv, mode, avail_res, &num_lm); + if (rc) { + DP_ERR("error getting mixer count. rc:%d\n", rc); + return rc; + } + + if (num_lm > avail_res->num_lm || + (num_lm == 2 && !avail_res->num_3dmux)) { + DP_DEBUG("num_lm:%d, req lm:%d 3dmux:%d\n", num_lm, + avail_res->num_lm, avail_res->num_3dmux); + return -EPERM; + } + + return 0; +} + +static void dp_display_validate_mst_connectors(struct dp_debug *debug, + struct dp_panel *dp_panel, struct drm_display_mode *mode, + enum drm_mode_status *mode_status, bool *use_default) +{ + struct dp_mst_connector *mst_connector; + int hdis, vdis, vref, ar, _hdis, _vdis, _vref, _ar; + bool in_list = false; + + /* + * If the connector exists in the mst connector list and if debug is + * enabled for that connector, use the mst connector settings from the + * list for validation. Otherwise, use non-mst default settings. + */ + mutex_lock(&debug->dp_mst_connector_list.lock); + + if (list_empty(&debug->dp_mst_connector_list.list)) { + mutex_unlock(&debug->dp_mst_connector_list.lock); + *use_default = true; + return; + } + + list_for_each_entry(mst_connector, &debug->dp_mst_connector_list.list, + list) { + if (mst_connector->con_id != dp_panel->connector->base.id) + continue; + + in_list = true; + + if (!mst_connector->debug_en) { + mutex_unlock(&debug->dp_mst_connector_list.lock); + *use_default = false; + *mode_status = MODE_OK; + return; + } + + hdis = mst_connector->hdisplay; + vdis = mst_connector->vdisplay; + vref = mst_connector->vrefresh; + ar = mst_connector->aspect_ratio; + + _hdis = mode->hdisplay; + _vdis = mode->vdisplay; + _vref = mode->vrefresh; + _ar = mode->picture_aspect_ratio; + + if (hdis == _hdis && vdis == _vdis && vref == _vref && + ar == _ar) { + mutex_unlock(&debug->dp_mst_connector_list.lock); + *use_default = false; + *mode_status = MODE_OK; + return; + } + + break; + } + + mutex_unlock(&debug->dp_mst_connector_list.lock); + + if (in_list) { + *use_default = false; + return; + } + + *use_default = true; +} + static enum drm_mode_status dp_display_validate_mode( struct dp_display *dp_display, void *panel, struct drm_display_mode *mode, const struct msm_resource_caps_info *avail_res) { struct dp_display_private *dp; - struct drm_dp_link *link_info; - u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; struct dp_panel *dp_panel; struct dp_debug *debug; enum drm_mode_status mode_status = MODE_BAD; - bool in_list = false; - struct dp_mst_connector *mst_connector; - int hdis, vdis, vref, ar, _hdis, _vdis, _vref, _ar, rate; struct dp_display_mode dp_mode; - bool dsc_en; - u32 num_lm = 0; int rc = 0; - u32 pclk_khz = 0; + bool use_default = true; if (!dp_display || !mode || !panel || !avail_res || !avail_res->max_mixer_width) { @@ -2101,104 +2518,29 @@ static enum drm_mode_status dp_display_validate_mode( goto end; } - link_info = &dp->panel->link_info; - debug = dp->debug; if (!debug) goto end; dp_display->convert_to_dp_mode(dp_display, panel, mode, &dp_mode); - dsc_en = (dp_mode.timing.comp_info.comp_ratio > 1) ? true : false; - mode_bpp = dsc_en ? - DSC_BPP(dp_mode.timing.comp_info.dsc_info.config) - : dp_mode.timing.bpp; - - mode_rate_khz = mode->clock * mode_bpp; - rate = drm_dp_bw_code_to_link_rate(dp->link->link_params.bw_code); - supported_rate_khz = link_info->num_lanes * rate * 8; - - if (mode_rate_khz > supported_rate_khz) { - DP_MST_DEBUG("pclk:%d, supported_rate:%d\n", - mode->clock, supported_rate_khz); - goto end; - } - - pclk_khz = dp_mode.timing.widebus_en ? - (dp_mode.timing.pixel_clk_khz >> 1) : - (dp_mode.timing.pixel_clk_khz); - - if (pclk_khz > dp_display->max_pclk_khz) { - DP_MST_DEBUG("clk:%d, max:%d\n", pclk_khz, - dp_display->max_pclk_khz); - goto end; - } - - rc = msm_get_mixer_count(dp->priv, mode, avail_res, &num_lm); - if (rc) { - DP_ERR("error getting mixer count. rc:%d\n", rc); - goto end; - } - - if (num_lm > avail_res->num_lm || - (num_lm == 2 && !avail_res->num_3dmux)) { - DP_MST_DEBUG("num_lm:%d, req lm:%d 3dmux:%d\n", num_lm, - avail_res->num_lm, avail_res->num_3dmux); - goto end; - } - - /* - * If the connector exists in the mst connector list and if debug is - * enabled for that connector, use the mst connector settings from the - * list for validation. Otherwise, use non-mst default settings. - */ - mutex_lock(&debug->dp_mst_connector_list.lock); - - if (list_empty(&debug->dp_mst_connector_list.list)) { - mutex_unlock(&debug->dp_mst_connector_list.lock); - goto verify_default; - } - - list_for_each_entry(mst_connector, &debug->dp_mst_connector_list.list, - list) { - if (mst_connector->con_id == dp_panel->connector->base.id) { - in_list = true; - - if (!mst_connector->debug_en) { - mode_status = MODE_OK; - mutex_unlock( - &debug->dp_mst_connector_list.lock); - goto end; - } - - hdis = mst_connector->hdisplay; - vdis = mst_connector->vdisplay; - vref = mst_connector->vrefresh; - ar = mst_connector->aspect_ratio; - - _hdis = mode->hdisplay; - _vdis = mode->vdisplay; - _vref = mode->vrefresh; - _ar = mode->picture_aspect_ratio; - - if (hdis == _hdis && vdis == _vdis && vref == _vref && - ar == _ar) { - mode_status = MODE_OK; - mutex_unlock( - &debug->dp_mst_connector_list.lock); - goto end; - } - - break; - } - } - - mutex_unlock(&debug->dp_mst_connector_list.lock); - - if (in_list) + rc = dp_display_validate_link_clock(dp, mode, dp_mode); + if (rc) + goto end; + + rc = dp_display_validate_pixel_clock(dp_mode, dp_display->max_pclk_khz); + if (rc) + goto end; + + rc = dp_display_validate_mixers(dp->priv, mode, avail_res); + if (rc) + goto end; + + dp_display_validate_mst_connectors(debug, dp_panel, mode, &mode_status, + &use_default); + if (!use_default) goto end; -verify_default: if (debug->debug_en && (mode->hdisplay != debug->hdisplay || mode->vdisplay != debug->vdisplay || mode->vrefresh != debug->vrefresh || @@ -2936,9 +3278,25 @@ static int dp_pm_prepare(struct device *dev) struct dp_display_private *dp = container_of(g_dp_display, struct dp_display_private, dp_display); + mutex_lock(&dp->session_lock); dp_display_set_mst_state(g_dp_display, PM_SUSPEND); + /* + * There are a few instances where the DP is hotplugged when the device + * is in PM suspend state. After hotplug, it is observed the device + * enters and exits the PM suspend multiple times while aux transactions + * are taking place. This may sometimes cause an unclocked register + * access error. So, abort aux transactions when such a situation + * arises i.e. when DP is connected but display not enabled yet. + */ + if (dp_display_state_is(DP_STATE_CONNECTED) && + !dp_display_state_is(DP_STATE_ENABLED)) { + dp->aux->abort(dp->aux, true); + dp->ctrl->abort(dp->ctrl, true); + } + dp_display_state_add(DP_STATE_SUSPENDED); + mutex_unlock(&dp->session_lock); return 0; } @@ -2948,9 +3306,25 @@ static void dp_pm_complete(struct device *dev) struct dp_display_private *dp = container_of(g_dp_display, struct dp_display_private, dp_display); + mutex_lock(&dp->session_lock); dp_display_set_mst_state(g_dp_display, PM_DEFAULT); + /* + * There are multiple PM suspend entry and exits observed before + * the connect uevent is issued to userspace. The aux transactions are + * aborted during PM suspend entry in dp_pm_prepare to prevent unclocked + * register access. On PM suspend exit, there will be no host_init call + * to reset the abort flags for ctrl and aux incase DP is connected + * but display not enabled. So, resetting abort flags for aux and ctrl. + */ + if (dp_display_state_is(DP_STATE_CONNECTED) && + !dp_display_state_is(DP_STATE_ENABLED)) { + dp->aux->abort(dp->aux, false); + dp->ctrl->abort(dp->ctrl, false); + } + dp_display_state_remove(DP_STATE_SUSPENDED); + mutex_unlock(&dp->session_lock); } static const struct dev_pm_ops dp_pm_ops = { diff --git a/msm/dp/dp_hdcp2p2.c b/msm/dp/dp_hdcp2p2.c index b87cfee52657..fcbec753ccfc 100644 --- a/msm/dp/dp_hdcp2p2.c +++ b/msm/dp/dp_hdcp2p2.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. */ #include @@ -31,6 +31,7 @@ struct dp_hdcp2p2_ctrl { DECLARE_KFIFO(cmd_q, enum hdcp_transport_wakeup_cmd, 8); wait_queue_head_t wait_q; atomic_t auth_state; + atomic_t abort; enum dp_hdcp2p2_sink_status sink_status; /* Is sink connected */ struct dp_hdcp2p2_interrupts *intr; struct sde_hdcp_init_data init_data; @@ -44,7 +45,8 @@ struct dp_hdcp2p2_ctrl { struct hdcp2_buffer response; struct hdcp2_buffer request; uint32_t total_message_length; - uint32_t timeout; + uint32_t transaction_delay; + uint32_t transaction_timeout; struct sde_hdcp_2x_msg_part msg_part[HDCP_MAX_MESSAGE_PARTS]; u8 sink_rx_status; u8 rx_status; @@ -108,7 +110,6 @@ static int dp_hdcp2p2_copy_buf(struct dp_hdcp2p2_ctrl *ctrl, mutex_lock(&ctrl->msg_lock); - ctrl->timeout = data->timeout; num_messages = data->message_data->num_messages; ctrl->total_message_length = 0; /* Total length of all messages */ @@ -132,6 +133,9 @@ static int dp_hdcp2p2_copy_buf(struct dp_hdcp2p2_ctrl *ctrl, ctrl->request.data = data->buf; ctrl->request.length = ctrl->total_message_length; + ctrl->transaction_delay = data->transaction_delay; + ctrl->transaction_timeout = data->transaction_timeout; + mutex_unlock(&ctrl->msg_lock); return 0; @@ -148,6 +152,9 @@ static void dp_hdcp2p2_set_interrupts(struct dp_hdcp2p2_ctrl *ctrl, bool enable) void __iomem *base = ctrl->init_data.dp_ahb->base; struct dp_hdcp2p2_interrupts *intr = ctrl->intr; + if (atomic_read(&ctrl->abort)) + return; + while (intr && intr->reg) { struct dp_hdcp2p2_int_set *int_set = intr->int_set; u32 interrupts = 0; @@ -170,7 +177,6 @@ static void dp_hdcp2p2_set_interrupts(struct dp_hdcp2p2_ctrl *ctrl, bool enable) static int dp_hdcp2p2_wakeup(struct hdcp_transport_wakeup_data *data) { struct dp_hdcp2p2_ctrl *ctrl; - u32 const default_timeout_us = 500; if (!data) { DP_ERR("invalid input\n"); @@ -183,11 +189,6 @@ static int dp_hdcp2p2_wakeup(struct hdcp_transport_wakeup_data *data) return -EINVAL; } - if (data->timeout) - ctrl->timeout = (data->timeout) * 2; - else - ctrl->timeout = default_timeout_us; - if (dp_hdcp2p2_copy_buf(ctrl, data)) goto exit; @@ -349,11 +350,6 @@ static void dp_hdcp2p2_min_level_change(void *client_ctx, return; } - if (!dp_hdcp2p2_is_valid_state(ctrl)) { - DP_ERR("invalid state\n"); - return; - } - cdata.context = ctrl->lib_ctx; cdata.min_enc_level = min_enc_level; dp_hdcp2p2_wakeup_lib(ctrl, &cdata); @@ -364,6 +360,8 @@ static int dp_hdcp2p2_aux_read_message(struct dp_hdcp2p2_ctrl *ctrl) int rc = 0, max_size = 16, read_size = 0, bytes_read = 0; int size = ctrl->request.length, offset = ctrl->msg_part->offset; u8 *buf = ctrl->request.data; + s64 diff_ms; + ktime_t start_read, finish_read; if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE || atomic_read(&ctrl->auth_state) == HDCP_STATE_AUTH_FAIL) { @@ -380,6 +378,7 @@ static int dp_hdcp2p2_aux_read_message(struct dp_hdcp2p2_ctrl *ctrl) DP_DEBUG("offset(0x%x), size(%d)\n", offset, size); + start_read = ktime_get(); do { read_size = min(size, max_size); @@ -396,7 +395,14 @@ static int dp_hdcp2p2_aux_read_message(struct dp_hdcp2p2_ctrl *ctrl) offset += read_size; size -= read_size; } while (size > 0); + finish_read = ktime_get(); + diff_ms = ktime_ms_delta(finish_read, start_read); + if (ctrl->transaction_timeout && diff_ms > ctrl->transaction_timeout) { + DP_ERR("HDCP read timeout exceeded (%dms > %dms)\n", diff_ms, + ctrl->transaction_timeout); + rc = -ETIMEDOUT; + } exit: return rc; } @@ -485,7 +491,7 @@ static void dp_hdcp2p2_send_msg(struct dp_hdcp2p2_ctrl *ctrl) rc = dp_hdcp2p2_aux_write_message(ctrl, ctrl->response.data, ctrl->response.length, ctrl->msg_part->offset, - ctrl->timeout); + ctrl->transaction_delay); if (rc) { DP_ERR("Error sending msg to sink %d\n", rc); mutex_unlock(&ctrl->msg_lock); @@ -493,7 +499,7 @@ static void dp_hdcp2p2_send_msg(struct dp_hdcp2p2_ctrl *ctrl) } cdata.cmd = HDCP_2X_CMD_MSG_SEND_SUCCESS; - cdata.timeout = ctrl->timeout; + cdata.timeout = ctrl->transaction_delay; mutex_unlock(&ctrl->msg_lock); exit: @@ -519,7 +525,7 @@ static int dp_hdcp2p2_get_msg_from_sink(struct dp_hdcp2p2_ctrl *ctrl) } cdata.total_message_length = ctrl->total_message_length; - cdata.timeout = ctrl->timeout; + cdata.timeout = ctrl->transaction_delay; exit: if (rc == -ETIMEDOUT) cdata.cmd = HDCP_2X_CMD_MSG_RECV_TIMEOUT; @@ -544,6 +550,9 @@ static void dp_hdcp2p2_recv_msg(struct dp_hdcp2p2_ctrl *ctrl) return; } + if (ctrl->transaction_delay) + msleep(ctrl->transaction_delay); + dp_hdcp2p2_get_msg_from_sink(ctrl); } @@ -877,6 +886,13 @@ static int dp_hdcp2p2_main(void *data) return 0; } +static void dp_hdcp2p2_abort(void *input, bool abort) +{ + struct dp_hdcp2p2_ctrl *ctrl = input; + + atomic_set(&ctrl->abort, abort); +} + void *sde_dp_hdcp2p2_init(struct sde_hdcp_init_data *init_data) { int rc; @@ -891,6 +907,7 @@ void *sde_dp_hdcp2p2_init(struct sde_hdcp_init_data *init_data) .set_mode = dp_hdcp2p2_register, .on = dp_hdcp2p2_on, .off = dp_hdcp2p2_off, + .abort = dp_hdcp2p2_abort, .cp_irq = dp_hdcp2p2_cp_irq, .register_streams = dp_hdcp2p2_register_streams, .deregister_streams = dp_hdcp2p2_deregister_streams, diff --git a/msm/dp/dp_panel.c b/msm/dp/dp_panel.c index b9ee5cb9a349..6d92a6ae19c8 100644 --- a/msm/dp/dp_panel.c +++ b/msm/dp/dp_panel.c @@ -3069,6 +3069,7 @@ error: void dp_panel_put(struct dp_panel *dp_panel) { struct dp_panel_private *panel; + struct sde_connector *sde_conn; if (!dp_panel) return; @@ -3076,5 +3077,9 @@ void dp_panel_put(struct dp_panel *dp_panel) panel = container_of(dp_panel, struct dp_panel_private, dp_panel); dp_panel_edid_deregister(panel); + sde_conn = to_sde_connector(dp_panel->connector); + if (sde_conn) + sde_conn->drv_panel = NULL; + devm_kfree(panel->dev, panel); } diff --git a/msm/sde_dbg.h b/msm/sde_dbg.h index 9bec29cecf44..da7fde80b046 100644 --- a/msm/sde_dbg.h +++ b/msm/sde_dbg.h @@ -34,6 +34,7 @@ enum sde_dbg_evtlog_flag { SDE_EVTLOG_CRITICAL = BIT(0), SDE_EVTLOG_IRQ = BIT(1), SDE_EVTLOG_VERBOSE = BIT(2), + SDE_EVTLOG_EXTERNAL = BIT(3), SDE_EVTLOG_ALWAYS = -1 }; @@ -48,7 +49,8 @@ enum sde_dbg_dump_context { SDE_DBG_DUMP_CLK_ENABLED_CTX, }; -#define SDE_EVTLOG_DEFAULT_ENABLE (SDE_EVTLOG_CRITICAL | SDE_EVTLOG_IRQ) +#define SDE_EVTLOG_DEFAULT_ENABLE (SDE_EVTLOG_CRITICAL | SDE_EVTLOG_IRQ | \ + SDE_EVTLOG_EXTERNAL) /* * evtlog will print this number of entries when it is called through @@ -62,7 +64,7 @@ enum sde_dbg_dump_context { * number must be greater than print entry to prevent out of bound evtlog * entry array access. */ -#define SDE_EVTLOG_ENTRY (SDE_EVTLOG_PRINT_ENTRY * 8) +#define SDE_EVTLOG_ENTRY (SDE_EVTLOG_PRINT_ENTRY * 32) #define SDE_EVTLOG_MAX_DATA 15 #define SDE_EVTLOG_BUF_MAX 512 #define SDE_EVTLOG_BUF_ALIGN 32 @@ -125,6 +127,14 @@ extern struct sde_dbg_evtlog *sde_dbg_base_evtlog; __LINE__, SDE_EVTLOG_IRQ, ##__VA_ARGS__, \ SDE_EVTLOG_DATA_LIMITER) +/** + * SDE_EVT32_EXTERNAL - Write a list of 32bit values for external display events + * ... - variable arguments + */ +#define SDE_EVT32_EXTERNAL(...) sde_evtlog_log(sde_dbg_base_evtlog, __func__, \ + __LINE__, SDE_EVTLOG_EXTERNAL, ##__VA_ARGS__, \ + SDE_EVTLOG_DATA_LIMITER) + /** * SDE_DBG_DUMP - trigger dumping of all sde_dbg facilities * @va_args: list of named register dump ranges and regions to dump, as diff --git a/msm/sde_hdcp.h b/msm/sde_hdcp.h index fd7768b1ff9c..d386eb76682e 100644 --- a/msm/sde_hdcp.h +++ b/msm/sde_hdcp.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2012, 2014-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2012, 2014-2020, The Linux Foundation. All rights reserved. */ #ifndef __SDE_HDCP_H__ @@ -81,6 +81,7 @@ struct sde_hdcp_ops { bool (*feature_supported)(void *input); void (*force_encryption)(void *input, bool enable); bool (*sink_support)(void *input); + void (*abort)(void *input, bool abort); int (*set_mode)(void *input, bool mst_enabled); int (*on)(void *input); void (*off)(void *hdcp_ctrl); diff --git a/msm/sde_hdcp_1x.c b/msm/sde_hdcp_1x.c index 7a74bca2ae6b..e321344ddf23 100644 --- a/msm/sde_hdcp_1x.c +++ b/msm/sde_hdcp_1x.c @@ -207,6 +207,7 @@ struct sde_hdcp_1x { bool reauth; bool ksv_ready; bool force_encryption; + atomic_t abort; enum sde_hdcp_state hdcp_state; struct HDCP_V2V1_MSG_TOPOLOGY current_tp; struct delayed_work hdcp_auth_work; @@ -1040,7 +1041,7 @@ static void sde_hdcp_1x_update_auth_status(struct sde_hdcp_1x *hdcp) static void sde_hdcp_1x_auth_work(struct work_struct *work) { - int rc; + int rc = 0; struct delayed_work *dw = to_delayed_work(work); struct sde_hdcp_1x *hdcp = container_of(dw, struct sde_hdcp_1x, hdcp_auth_work); @@ -1056,6 +1057,9 @@ static void sde_hdcp_1x_auth_work(struct work_struct *work) return; } + if (atomic_read(&hdcp->abort)) + goto end; + hdcp->sink_r0_ready = false; hdcp->reauth = false; hdcp->ksv_ready = false; @@ -1484,6 +1488,15 @@ irq_not_handled: return -EINVAL; } +static void sde_hdcp_1x_abort(void *data, bool abort) +{ + struct sde_hdcp_1x *hdcp = data; + + atomic_set(&hdcp->abort, abort); + cancel_delayed_work_sync(&hdcp->hdcp_auth_work); + flush_workqueue(hdcp->workq); +} + void *sde_hdcp_1x_init(struct sde_hdcp_init_data *init_data) { struct sde_hdcp_1x *hdcp = NULL; @@ -1496,6 +1509,7 @@ void *sde_hdcp_1x_init(struct sde_hdcp_init_data *init_data) .feature_supported = sde_hdcp_1x_feature_supported, .force_encryption = sde_hdcp_1x_force_encryption, .sink_support = sde_hdcp_1x_sink_support, + .abort = sde_hdcp_1x_abort, .off = sde_hdcp_1x_off }; diff --git a/msm/sde_hdcp_2x.c b/msm/sde_hdcp_2x.c index a3bc7b61854e..30db5cb70c36 100644 --- a/msm/sde_hdcp_2x.c +++ b/msm/sde_hdcp_2x.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2015-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "[sde-hdcp-2x] %s: " fmt, __func__ @@ -42,6 +42,9 @@ #define REAUTH_REQ BIT(3) #define LINK_INTEGRITY_FAILURE BIT(4) +/* Temporary define to override wrong TZ value */ +#define AKE_SEND_CERT_MSG_DELAY 100 + struct sde_hdcp_2x_ctrl { DECLARE_KFIFO(cmd_q, enum sde_hdcp_2x_wakeup_cmd, 8); wait_queue_head_t wait_q; @@ -100,49 +103,49 @@ static const struct sde_hdcp_2x_msg_data hdcp_msg_lookup[HDCP2P2_MAX_MESSAGES] = { [AKE_INIT] = { 2, { {"rtx", 0x69000, 8}, {"TxCaps", 0x69008, 3} }, - 0 }, + 0, 0 }, [AKE_SEND_CERT] = { 3, { {"cert-rx", 0x6900B, 522}, {"rrx", 0x69215, 8}, {"RxCaps", 0x6921D, 3} }, - 0 }, + 0, 110 }, [AKE_NO_STORED_KM] = { 1, { {"Ekpub_km", 0x69220, 128} }, - 0 }, + 0, 0 }, [AKE_STORED_KM] = { 2, { {"Ekh_km", 0x692A0, 16}, {"m", 0x692B0, 16} }, - 0 }, + 0, 0 }, [AKE_SEND_H_PRIME] = { 1, { {"H'", 0x692C0, 32} }, - (1 << 1) }, + (1 << 1), 7 }, [AKE_SEND_PAIRING_INFO] = { 1, { {"Ekh_km", 0x692E0, 16} }, - (1 << 2) }, + (1 << 2), 5 }, [LC_INIT] = { 1, { {"rn", 0x692F0, 8} }, - 0 }, + 0, 0 }, [LC_SEND_L_PRIME] = { 1, { {"L'", 0x692F8, 32} }, - 0 }, + 0, 0 }, [SKE_SEND_EKS] = { 2, { {"Edkey_ks", 0x69318, 16}, {"riv", 0x69328, 8} }, - 0 }, + 0, 0 }, [SKE_SEND_TYPE_ID] = { 1, { {"type", 0x69494, 1} }, - 0 }, + 0, 0 }, [REP_SEND_RECV_ID_LIST] = { 4, { {"RxInfo", 0x69330, 2}, {"seq_num_V", 0x69332, 3}, {"V'", 0x69335, 16}, {"ridlist", 0x69345, 155} }, - (1 << 0) }, + (1 << 0), 0 }, [REP_SEND_ACK] = { 1, { {"V", 0x693E0, 16} }, - 0 }, + 0, 0 }, [REP_STREAM_MANAGE] = { 3, { {"seq_num_M", 0x693F0, 3}, {"k", 0x693F3, 2}, {"streamID_Type", 0x693F5, 126} }, - 0 }, + 0, 0 }, [REP_STREAM_READY] = { 1, { {"M'", 0x69473, 32} }, - 0 }, + 0, 7 }, }; static int sde_hdcp_2x_get_next_message(struct sde_hdcp_2x_ctrl *hdcp, @@ -247,6 +250,29 @@ static void sde_hdcp_2x_wait_for_response(struct sde_hdcp_2x_ctrl *hdcp) hdcp->wait_timeout_ms = 0; } +static void sde_hdcp_2x_adjust_transaction_params( + struct sde_hdcp_2x_ctrl *hdcp, + struct hdcp_transport_wakeup_data *data) +{ + switch (hdcp->last_msg) { + case AKE_SEND_CERT: + data->transaction_delay = AKE_SEND_CERT_MSG_DELAY; + break; + case REP_STREAM_READY: + break; + default: + data->transaction_delay = 0; + break; + } + + data->transaction_timeout = + hdcp_msg_lookup[hdcp->last_msg].transaction_timeout; + + pr_debug("%s: transaction delay: %ums, transaction timeout: %ums\n", + sde_hdcp_2x_message_name(hdcp->last_msg), + data->transaction_delay, data->transaction_timeout); +} + static void sde_hdcp_2x_wakeup_client(struct sde_hdcp_2x_ctrl *hdcp, struct hdcp_transport_wakeup_data *data) { @@ -271,6 +297,8 @@ static void sde_hdcp_2x_wakeup_client(struct sde_hdcp_2x_ctrl *hdcp, data->message_data = &hdcp_msg_lookup[hdcp->last_msg]; } + sde_hdcp_2x_adjust_transaction_params(hdcp, data); + rc = hdcp->client_ops->wakeup(data); if (rc) pr_err("error sending %s to client\n", @@ -285,7 +313,7 @@ static inline void sde_hdcp_2x_send_message(struct sde_hdcp_2x_ctrl *hdcp) HDCP_TRANSPORT_CMD_SEND_MESSAGE }; cdata.context = hdcp->client_data; - cdata.timeout = hdcp->app_data.timeout; + cdata.transaction_delay = hdcp->app_data.timeout; cdata.buf_len = hdcp->app_data.response.length; /* ignore the first byte as it contains the message id */ @@ -436,7 +464,7 @@ static void sde_hdcp_2x_initialize_command(struct sde_hdcp_2x_ctrl *hdcp, struct hdcp_transport_wakeup_data *cdata) { cdata->cmd = cmd; - cdata->timeout = hdcp->timeout_left; + cdata->transaction_delay = hdcp->timeout_left; cdata->buf = hdcp->app_data.request.data + 1; } @@ -492,7 +520,7 @@ static void sde_hdcp_2x_msg_sent(struct sde_hdcp_2x_ctrl *hdcp) break; default: cdata.cmd = HDCP_TRANSPORT_CMD_RECV_MESSAGE; - cdata.timeout = hdcp->timeout_left; + cdata.transaction_delay = hdcp->app_data.timeout; cdata.buf = hdcp->app_data.request.data + 1; } @@ -593,7 +621,7 @@ static void sde_hdcp_2x_msg_recvd(struct sde_hdcp_2x_ctrl *hdcp) if (msg[0] == AKE_SEND_H_PRIME && hdcp->no_stored_km) { cdata.cmd = HDCP_TRANSPORT_CMD_RECV_MESSAGE; - cdata.timeout = hdcp->app_data.timeout; + cdata.transaction_delay = hdcp->app_data.timeout; cdata.buf = hdcp->app_data.request.data + 1; goto exit; } @@ -653,7 +681,7 @@ static void sde_hdcp_2x_msg_recvd(struct sde_hdcp_2x_ctrl *hdcp) cdata.cmd = HDCP_TRANSPORT_CMD_SEND_MESSAGE; cdata.buf = hdcp->app_data.response.data + 1; cdata.buf_len = hdcp->app_data.response.length; - cdata.timeout = hdcp->app_data.timeout; + cdata.transaction_delay = hdcp->app_data.timeout; } exit: sde_hdcp_2x_wakeup_client(hdcp, &cdata); @@ -876,8 +904,10 @@ static int sde_hdcp_2x_wakeup(struct sde_hdcp_2x_wakeup_data *data) break; case HDCP_2X_CMD_MIN_ENC_LEVEL: hdcp->min_enc_level = data->min_enc_level; - kfifo_put(&hdcp->cmd_q, data->cmd); - wake_up(&hdcp->wait_q); + if (hdcp->authenticated) { + kfifo_put(&hdcp->cmd_q, data->cmd); + wake_up(&hdcp->wait_q); + } break; default: kfifo_put(&hdcp->cmd_q, data->cmd); diff --git a/msm/sde_hdcp_2x.h b/msm/sde_hdcp_2x.h index 4669564dec13..a8c459c7fa0c 100644 --- a/msm/sde_hdcp_2x.h +++ b/msm/sde_hdcp_2x.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2015-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. */ #ifndef __SDE_HDCP_2X_H__ @@ -114,32 +114,37 @@ struct sde_hdcp_2x_msg_part { /** * struct sde_hdcp_2x_msg_data - HDCP 2.2 message containing one or more parts - * @num_messages: total number of parts in a full message - * @messages: array containing num_messages parts - * @rx_status: value of rx_status register + * @num_messages: total number of parts in a full message + * @messages: array containing num_messages parts + * @rx_status: value of rx_status register + * @transaction_timeout: maximum duration to read/write message from/to sink */ struct sde_hdcp_2x_msg_data { uint32_t num_messages; struct sde_hdcp_2x_msg_part messages[HDCP_MAX_MESSAGE_PARTS]; uint8_t rx_status; + uint32_t transaction_timeout; }; /** * struct hdcp_transport_wakeup_data - data sent to display transport layer - * @cmd: command type - * @context: void pointer to the display transport layer - * @send_msg_buf: buffer containing message to be sent to sink - * @send_msg_len: length of the message to be sent to sink - * @timeout: timeout value for timed transactions - * @abort_mask: mask used to determine whether HDCP link is valid - * @message_data: a pointer to the message description + * @cmd: command type + * @context: void pointer to the display transport layer + * @send_msg_buf: buffer containing message to be sent to sink + * @send_msg_len: length of the message to be sent to sink + * @timeout: timeout value for timed transactions + * @abort_mask: mask used to determine whether HDCP link is valid + * @message_data: a pointer to the message description + * @transaction_delay: amount of time to delay before performing transaction + * @transaction_timeout: maximum duration to read/write message from/to sink */ struct hdcp_transport_wakeup_data { enum hdcp_transport_wakeup_cmd cmd; void *context; unsigned char *buf; u32 buf_len; - u32 timeout; + u32 transaction_delay; + u32 transaction_timeout; u8 abort_mask; const struct sde_hdcp_2x_msg_data *message_data; };