| /* |
| * Copyright 2022 Advanced Micro Devices, Inc. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR |
| * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
| * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
| * OTHER DEALINGS IN THE SOFTWARE. |
| * |
| * Authors: AMD |
| * |
| */ |
| |
| /* FILE POLICY AND INTENDED USAGE: |
| * This file implements DP HPD short pulse handling sequence according to DP |
| * specifications |
| * |
| */ |
| |
| #include "link_dp_irq_handler.h" |
| #include "link_dpcd.h" |
| #include "link_dp_training.h" |
| #include "link_dp_capability.h" |
| #include "link_edp_panel_control.h" |
| #include "link/accessories/link_dp_trace.h" |
| #include "link/link_dpms.h" |
| #include "dm_helpers.h" |
| |
| #define DC_LOGGER \ |
| link->ctx->logger |
| #define DC_LOGGER_INIT(logger) |
| |
| bool dp_parse_link_loss_status( |
| struct dc_link *link, |
| union hpd_irq_data *hpd_irq_dpcd_data) |
| { |
| uint8_t irq_reg_rx_power_state = 0; |
| enum dc_status dpcd_result = DC_ERROR_UNEXPECTED; |
| union lane_status lane_status; |
| uint32_t lane; |
| bool sink_status_changed; |
| bool return_code; |
| |
| sink_status_changed = false; |
| return_code = false; |
| |
| if (link->cur_link_settings.lane_count == 0) |
| return return_code; |
| |
| /*1. Check that Link Status changed, before re-training.*/ |
| |
| /*parse lane status*/ |
| for (lane = 0; lane < link->cur_link_settings.lane_count; lane++) { |
| /* check status of lanes 0,1 |
| * changed DpcdAddress_Lane01Status (0x202) |
| */ |
| lane_status.raw = dp_get_nibble_at_index( |
| &hpd_irq_dpcd_data->bytes.lane01_status.raw, |
| lane); |
| |
| if (!lane_status.bits.CHANNEL_EQ_DONE_0 || |
| !lane_status.bits.CR_DONE_0 || |
| !lane_status.bits.SYMBOL_LOCKED_0) { |
| /* if one of the channel equalization, clock |
| * recovery or symbol lock is dropped |
| * consider it as (link has been |
| * dropped) dp sink status has changed |
| */ |
| sink_status_changed = true; |
| break; |
| } |
| } |
| |
| /* Check interlane align.*/ |
| if (link_dp_get_encoding_format(&link->cur_link_settings) == DP_128b_132b_ENCODING && |
| (!hpd_irq_dpcd_data->bytes.lane_status_updated.bits.EQ_INTERLANE_ALIGN_DONE_128b_132b || |
| !hpd_irq_dpcd_data->bytes.lane_status_updated.bits.CDS_INTERLANE_ALIGN_DONE_128b_132b)) { |
| sink_status_changed = true; |
| } else if (!hpd_irq_dpcd_data->bytes.lane_status_updated.bits.INTERLANE_ALIGN_DONE) { |
| sink_status_changed = true; |
| } |
| |
| if (sink_status_changed) { |
| |
| DC_LOG_HW_HPD_IRQ("%s: Link Status changed.\n", __func__); |
| |
| return_code = true; |
| |
| /*2. Check that we can handle interrupt: Not in FS DOS, |
| * Not in "Display Timeout" state, Link is trained. |
| */ |
| dpcd_result = core_link_read_dpcd(link, |
| DP_SET_POWER, |
| &irq_reg_rx_power_state, |
| sizeof(irq_reg_rx_power_state)); |
| |
| if (dpcd_result != DC_OK) { |
| DC_LOG_HW_HPD_IRQ("%s: DPCD read failed to obtain power state.\n", |
| __func__); |
| } else { |
| if (irq_reg_rx_power_state != DP_SET_POWER_D0) |
| return_code = false; |
| } |
| } |
| |
| return return_code; |
| } |
| |
| static bool handle_hpd_irq_psr_sink(struct dc_link *link) |
| { |
| union dpcd_psr_configuration psr_configuration; |
| |
| if (!link->psr_settings.psr_feature_enabled) |
| return false; |
| |
| dm_helpers_dp_read_dpcd( |
| link->ctx, |
| link, |
| 368,/*DpcdAddress_PSR_Enable_Cfg*/ |
| &psr_configuration.raw, |
| sizeof(psr_configuration.raw)); |
| |
| if (psr_configuration.bits.ENABLE) { |
| unsigned char dpcdbuf[3] = {0}; |
| union psr_error_status psr_error_status; |
| union psr_sink_psr_status psr_sink_psr_status; |
| |
| dm_helpers_dp_read_dpcd( |
| link->ctx, |
| link, |
| 0x2006, /*DpcdAddress_PSR_Error_Status*/ |
| (unsigned char *) dpcdbuf, |
| sizeof(dpcdbuf)); |
| |
| /*DPCD 2006h ERROR STATUS*/ |
| psr_error_status.raw = dpcdbuf[0]; |
| /*DPCD 2008h SINK PANEL SELF REFRESH STATUS*/ |
| psr_sink_psr_status.raw = dpcdbuf[2]; |
| |
| if (psr_error_status.bits.LINK_CRC_ERROR || |
| psr_error_status.bits.RFB_STORAGE_ERROR || |
| psr_error_status.bits.VSC_SDP_ERROR) { |
| bool allow_active; |
| |
| /* Acknowledge and clear error bits */ |
| dm_helpers_dp_write_dpcd( |
| link->ctx, |
| link, |
| 8198,/*DpcdAddress_PSR_Error_Status*/ |
| &psr_error_status.raw, |
| sizeof(psr_error_status.raw)); |
| |
| /* PSR error, disable and re-enable PSR */ |
| if (link->psr_settings.psr_allow_active) { |
| allow_active = false; |
| edp_set_psr_allow_active(link, &allow_active, true, false, NULL); |
| allow_active = true; |
| edp_set_psr_allow_active(link, &allow_active, true, false, NULL); |
| } |
| |
| return true; |
| } else if (psr_sink_psr_status.bits.SINK_SELF_REFRESH_STATUS == |
| PSR_SINK_STATE_ACTIVE_DISPLAY_FROM_SINK_RFB){ |
| /* No error is detect, PSR is active. |
| * We should return with IRQ_HPD handled without |
| * checking for loss of sync since PSR would have |
| * powered down main link. |
| */ |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static void handle_hpd_irq_replay_sink(struct dc_link *link) |
| { |
| union dpcd_replay_configuration replay_configuration; |
| /*AMD Replay version reuse DP_PSR_ERROR_STATUS for REPLAY_ERROR status.*/ |
| union psr_error_status replay_error_status; |
| |
| if (!link->replay_settings.replay_feature_enabled) |
| return; |
| |
| dm_helpers_dp_read_dpcd( |
| link->ctx, |
| link, |
| DP_SINK_PR_REPLAY_STATUS, |
| &replay_configuration.raw, |
| sizeof(replay_configuration.raw)); |
| |
| dm_helpers_dp_read_dpcd( |
| link->ctx, |
| link, |
| DP_PSR_ERROR_STATUS, |
| &replay_error_status.raw, |
| sizeof(replay_error_status.raw)); |
| |
| link->replay_settings.config.replay_error_status.bits.LINK_CRC_ERROR = |
| replay_error_status.bits.LINK_CRC_ERROR; |
| link->replay_settings.config.replay_error_status.bits.DESYNC_ERROR = |
| replay_configuration.bits.DESYNC_ERROR_STATUS; |
| link->replay_settings.config.replay_error_status.bits.STATE_TRANSITION_ERROR = |
| replay_configuration.bits.STATE_TRANSITION_ERROR_STATUS; |
| |
| if (link->replay_settings.config.replay_error_status.bits.LINK_CRC_ERROR || |
| link->replay_settings.config.replay_error_status.bits.DESYNC_ERROR || |
| link->replay_settings.config.replay_error_status.bits.STATE_TRANSITION_ERROR) { |
| bool allow_active; |
| |
| if (link->replay_settings.config.replay_error_status.bits.DESYNC_ERROR) |
| link->replay_settings.config.received_desync_error_hpd = 1; |
| |
| if (link->replay_settings.config.force_disable_desync_error_check) |
| return; |
| |
| /* Acknowledge and clear configuration bits */ |
| dm_helpers_dp_write_dpcd( |
| link->ctx, |
| link, |
| DP_SINK_PR_REPLAY_STATUS, |
| &replay_configuration.raw, |
| sizeof(replay_configuration.raw)); |
| |
| /* Acknowledge and clear error bits */ |
| dm_helpers_dp_write_dpcd( |
| link->ctx, |
| link, |
| DP_PSR_ERROR_STATUS,/*DpcdAddress_REPLAY_Error_Status*/ |
| &replay_error_status.raw, |
| sizeof(replay_error_status.raw)); |
| |
| /* Replay error, disable and re-enable Replay */ |
| if (link->replay_settings.replay_allow_active) { |
| allow_active = false; |
| edp_set_replay_allow_active(link, &allow_active, true, false, NULL); |
| allow_active = true; |
| edp_set_replay_allow_active(link, &allow_active, true, false, NULL); |
| } |
| } |
| } |
| |
| void dp_handle_link_loss(struct dc_link *link) |
| { |
| struct pipe_ctx *pipes[MAX_PIPES]; |
| struct dc_state *state = link->dc->current_state; |
| uint8_t count; |
| int i; |
| |
| link_get_master_pipes_with_dpms_on(link, state, &count, pipes); |
| |
| for (i = 0; i < count; i++) |
| link_set_dpms_off(pipes[i]); |
| |
| for (i = count - 1; i >= 0; i--) { |
| // Always use max settings here for DP 1.4a LL Compliance CTS |
| if (link->skip_fallback_on_link_loss) { |
| pipes[i]->link_config.dp_link_settings.lane_count = |
| link->verified_link_cap.lane_count; |
| pipes[i]->link_config.dp_link_settings.link_rate = |
| link->verified_link_cap.link_rate; |
| pipes[i]->link_config.dp_link_settings.link_spread = |
| link->verified_link_cap.link_spread; |
| } |
| link_set_dpms_on(link->dc->current_state, pipes[i]); |
| } |
| } |
| |
| static void read_dpcd204h_on_irq_hpd(struct dc_link *link, union hpd_irq_data *irq_data) |
| { |
| enum dc_status retval; |
| union lane_align_status_updated dpcd_lane_status_updated; |
| |
| retval = core_link_read_dpcd( |
| link, |
| DP_LANE_ALIGN_STATUS_UPDATED, |
| &dpcd_lane_status_updated.raw, |
| sizeof(union lane_align_status_updated)); |
| |
| if (retval == DC_OK) { |
| irq_data->bytes.lane_status_updated.bits.EQ_INTERLANE_ALIGN_DONE_128b_132b = |
| dpcd_lane_status_updated.bits.EQ_INTERLANE_ALIGN_DONE_128b_132b; |
| irq_data->bytes.lane_status_updated.bits.CDS_INTERLANE_ALIGN_DONE_128b_132b = |
| dpcd_lane_status_updated.bits.CDS_INTERLANE_ALIGN_DONE_128b_132b; |
| } |
| } |
| |
| enum dc_status dp_read_hpd_rx_irq_data( |
| struct dc_link *link, |
| union hpd_irq_data *irq_data) |
| { |
| static enum dc_status retval; |
| |
| /* The HW reads 16 bytes from 200h on HPD, |
| * but if we get an AUX_DEFER, the HW cannot retry |
| * and this causes the CTS tests 4.3.2.1 - 3.2.4 to |
| * fail, so we now explicitly read 6 bytes which is |
| * the req from the above mentioned test cases. |
| * |
| * For DP 1.4 we need to read those from 2002h range. |
| */ |
| if (link->dpcd_caps.dpcd_rev.raw < DPCD_REV_14) |
| retval = core_link_read_dpcd( |
| link, |
| DP_SINK_COUNT, |
| irq_data->raw, |
| sizeof(union hpd_irq_data)); |
| else { |
| /* Read 14 bytes in a single read and then copy only the required fields. |
| * This is more efficient than doing it in two separate AUX reads. */ |
| |
| uint8_t tmp[DP_SINK_STATUS_ESI - DP_SINK_COUNT_ESI + 1]; |
| |
| retval = core_link_read_dpcd( |
| link, |
| DP_SINK_COUNT_ESI, |
| tmp, |
| sizeof(tmp)); |
| |
| if (retval != DC_OK) |
| return retval; |
| |
| irq_data->bytes.sink_cnt.raw = tmp[DP_SINK_COUNT_ESI - DP_SINK_COUNT_ESI]; |
| irq_data->bytes.device_service_irq.raw = tmp[DP_DEVICE_SERVICE_IRQ_VECTOR_ESI0 - DP_SINK_COUNT_ESI]; |
| irq_data->bytes.lane01_status.raw = tmp[DP_LANE0_1_STATUS_ESI - DP_SINK_COUNT_ESI]; |
| irq_data->bytes.lane23_status.raw = tmp[DP_LANE2_3_STATUS_ESI - DP_SINK_COUNT_ESI]; |
| irq_data->bytes.lane_status_updated.raw = tmp[DP_LANE_ALIGN_STATUS_UPDATED_ESI - DP_SINK_COUNT_ESI]; |
| irq_data->bytes.sink_status.raw = tmp[DP_SINK_STATUS_ESI - DP_SINK_COUNT_ESI]; |
| |
| /* |
| * This display doesn't have correct values in DPCD200Eh. |
| * Read and check DPCD204h instead. |
| */ |
| if (link->wa_flags.read_dpcd204h_on_irq_hpd) |
| read_dpcd204h_on_irq_hpd(link, irq_data); |
| } |
| |
| return retval; |
| } |
| |
| /*************************Short Pulse IRQ***************************/ |
| bool dp_should_allow_hpd_rx_irq(const struct dc_link *link) |
| { |
| /* |
| * Don't handle RX IRQ unless one of following is met: |
| * 1) The link is established (cur_link_settings != unknown) |
| * 2) We know we're dealing with a branch device, SST or MST |
| */ |
| |
| if ((link->cur_link_settings.lane_count != LANE_COUNT_UNKNOWN) || |
| is_dp_branch_device(link)) |
| return true; |
| |
| return false; |
| } |
| |
| bool dp_handle_hpd_rx_irq(struct dc_link *link, |
| union hpd_irq_data *out_hpd_irq_dpcd_data, bool *out_link_loss, |
| bool defer_handling, bool *has_left_work) |
| { |
| union hpd_irq_data hpd_irq_dpcd_data = {0}; |
| union device_service_irq device_service_clear = {0}; |
| enum dc_status result; |
| bool status = false; |
| |
| if (out_link_loss) |
| *out_link_loss = false; |
| |
| if (has_left_work) |
| *has_left_work = false; |
| /* For use cases related to down stream connection status change, |
| * PSR and device auto test, refer to function handle_sst_hpd_irq |
| * in DAL2.1*/ |
| |
| DC_LOG_HW_HPD_IRQ("%s: Got short pulse HPD on link %d\n", |
| __func__, link->link_index); |
| |
| |
| /* All the "handle_hpd_irq_xxx()" methods |
| * should be called only after |
| * dal_dpsst_ls_read_hpd_irq_data |
| * Order of calls is important too |
| */ |
| result = dp_read_hpd_rx_irq_data(link, &hpd_irq_dpcd_data); |
| if (out_hpd_irq_dpcd_data) |
| *out_hpd_irq_dpcd_data = hpd_irq_dpcd_data; |
| |
| if (result != DC_OK) { |
| DC_LOG_HW_HPD_IRQ("%s: DPCD read failed to obtain irq data\n", |
| __func__); |
| return false; |
| } |
| |
| if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.AUTOMATED_TEST) { |
| // Workaround for DP 1.4a LL Compliance CTS as USB4 has to share encoders unlike DP and USBC |
| if (link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA) |
| link->skip_fallback_on_link_loss = true; |
| |
| device_service_clear.bits.AUTOMATED_TEST = 1; |
| core_link_write_dpcd( |
| link, |
| DP_DEVICE_SERVICE_IRQ_VECTOR, |
| &device_service_clear.raw, |
| sizeof(device_service_clear.raw)); |
| device_service_clear.raw = 0; |
| if (defer_handling && has_left_work) |
| *has_left_work = true; |
| else |
| dc_link_dp_handle_automated_test(link); |
| return false; |
| } |
| |
| if (!dp_should_allow_hpd_rx_irq(link)) { |
| DC_LOG_HW_HPD_IRQ("%s: skipping HPD handling on %d\n", |
| __func__, link->link_index); |
| return false; |
| } |
| |
| if (handle_hpd_irq_psr_sink(link)) |
| /* PSR-related error was detected and handled */ |
| return true; |
| |
| handle_hpd_irq_replay_sink(link); |
| |
| /* If PSR-related error handled, Main link may be off, |
| * so do not handle as a normal sink status change interrupt. |
| */ |
| |
| if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY) { |
| if (defer_handling && has_left_work) |
| *has_left_work = true; |
| return true; |
| } |
| |
| /* check if we have MST msg and return since we poll for it */ |
| if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) { |
| if (defer_handling && has_left_work) |
| *has_left_work = true; |
| return false; |
| } |
| |
| /* For now we only handle 'Downstream port status' case. |
| * If we got sink count changed it means |
| * Downstream port status changed, |
| * then DM should call DC to do the detection. |
| * NOTE: Do not handle link loss on eDP since it is internal link*/ |
| if ((link->connector_signal != SIGNAL_TYPE_EDP) && |
| dp_parse_link_loss_status( |
| link, |
| &hpd_irq_dpcd_data)) { |
| /* Connectivity log: link loss */ |
| CONN_DATA_LINK_LOSS(link, |
| hpd_irq_dpcd_data.raw, |
| sizeof(hpd_irq_dpcd_data), |
| "Status: "); |
| |
| if (defer_handling && has_left_work) |
| *has_left_work = true; |
| else |
| dp_handle_link_loss(link); |
| |
| status = false; |
| if (out_link_loss) |
| *out_link_loss = true; |
| |
| dp_trace_link_loss_increment(link); |
| } |
| |
| if (link->type == dc_connection_sst_branch && |
| hpd_irq_dpcd_data.bytes.sink_cnt.bits.SINK_COUNT |
| != link->dpcd_sink_count) |
| status = true; |
| |
| /* reasons for HPD RX: |
| * 1. Link Loss - ie Re-train the Link |
| * 2. MST sideband message |
| * 3. Automated Test - ie. Internal Commit |
| * 4. CP (copy protection) - (not interesting for DM???) |
| * 5. DRR |
| * 6. Downstream Port status changed |
| * -ie. Detect - this the only one |
| * which is interesting for DM because |
| * it must call dc_link_detect. |
| */ |
| return status; |
| } |