drm/amd/display: Wake DMCUB before sending a command
[linux-2.6-block.git] / drivers / gpu / drm / amd / display / dc / dc_dmub_srv.c
index 0e07699c1e83529acfbf48ba2f36bea06a0cd884..fea13bcd4dc75e42f12022cf1d3ecc1ff9b0aa36 100644 (file)
@@ -140,7 +140,10 @@ bool dc_dmub_srv_cmd_list_queue_execute(struct dc_dmub_srv *dc_dmub_srv,
 
                if (status == DMUB_STATUS_QUEUE_FULL) {
                        /* Execute and wait for queue to become empty again. */
-                       dmub_srv_cmd_execute(dmub);
+                       status = dmub_srv_cmd_execute(dmub);
+                       if (status == DMUB_STATUS_POWER_STATE_D3)
+                               return false;
+
                        dmub_srv_wait_for_idle(dmub, 100000);
 
                        /* Requeue the command. */
@@ -148,16 +151,20 @@ bool dc_dmub_srv_cmd_list_queue_execute(struct dc_dmub_srv *dc_dmub_srv,
                }
 
                if (status != DMUB_STATUS_OK) {
-                       DC_ERROR("Error queueing DMUB command: status=%d\n", status);
-                       dc_dmub_srv_log_diagnostic_data(dc_dmub_srv);
+                       if (status != DMUB_STATUS_POWER_STATE_D3) {
+                               DC_ERROR("Error queueing DMUB command: status=%d\n", status);
+                               dc_dmub_srv_log_diagnostic_data(dc_dmub_srv);
+                       }
                        return false;
                }
        }
 
        status = dmub_srv_cmd_execute(dmub);
        if (status != DMUB_STATUS_OK) {
-               DC_ERROR("Error starting DMUB execution: status=%d\n", status);
-               dc_dmub_srv_log_diagnostic_data(dc_dmub_srv);
+               if (status != DMUB_STATUS_POWER_STATE_D3) {
+                       DC_ERROR("Error starting DMUB execution: status=%d\n", status);
+                       dc_dmub_srv_log_diagnostic_data(dc_dmub_srv);
+               }
                return false;
        }
 
@@ -218,7 +225,10 @@ bool dc_dmub_srv_cmd_run_list(struct dc_dmub_srv *dc_dmub_srv, unsigned int coun
 
                if (status == DMUB_STATUS_QUEUE_FULL) {
                        /* Execute and wait for queue to become empty again. */
-                       dmub_srv_cmd_execute(dmub);
+                       status = dmub_srv_cmd_execute(dmub);
+                       if (status == DMUB_STATUS_POWER_STATE_D3)
+                               return false;
+
                        dmub_srv_wait_for_idle(dmub, 100000);
 
                        /* Requeue the command. */
@@ -226,22 +236,31 @@ bool dc_dmub_srv_cmd_run_list(struct dc_dmub_srv *dc_dmub_srv, unsigned int coun
                }
 
                if (status != DMUB_STATUS_OK) {
-                       DC_ERROR("Error queueing DMUB command: status=%d\n", status);
-                       dc_dmub_srv_log_diagnostic_data(dc_dmub_srv);
+                       if (status != DMUB_STATUS_POWER_STATE_D3) {
+                               DC_ERROR("Error queueing DMUB command: status=%d\n", status);
+                               dc_dmub_srv_log_diagnostic_data(dc_dmub_srv);
+                       }
                        return false;
                }
        }
 
        status = dmub_srv_cmd_execute(dmub);
        if (status != DMUB_STATUS_OK) {
-               DC_ERROR("Error starting DMUB execution: status=%d\n", status);
-               dc_dmub_srv_log_diagnostic_data(dc_dmub_srv);
+               if (status != DMUB_STATUS_POWER_STATE_D3) {
+                       DC_ERROR("Error starting DMUB execution: status=%d\n", status);
+                       dc_dmub_srv_log_diagnostic_data(dc_dmub_srv);
+               }
                return false;
        }
 
        // Wait for DMUB to process command
        if (wait_type != DM_DMUB_WAIT_TYPE_NO_WAIT) {
-               status = dmub_srv_wait_for_idle(dmub, 100000);
+               if (dc_dmub_srv->ctx->dc->debug.disable_timeout) {
+                       do {
+                               status = dmub_srv_wait_for_idle(dmub, 100000);
+                       } while (status != DMUB_STATUS_OK);
+               } else
+                       status = dmub_srv_wait_for_idle(dmub, 100000);
 
                if (status != DMUB_STATUS_OK) {
                        DC_LOG_DEBUG("No reply for DMUB command: status=%d\n", status);
@@ -341,7 +360,7 @@ void dc_dmub_srv_drr_update_cmd(struct dc *dc, uint32_t tg_inst, uint32_t vtotal
        cmd.drr_update.header.payload_bytes = sizeof(cmd.drr_update) - sizeof(cmd.drr_update.header);
 
        // Send the command to the DMCUB.
-       dm_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
+       dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
 }
 
 void dc_dmub_srv_set_drr_manual_trigger_cmd(struct dc *dc, uint32_t tg_inst)
@@ -355,7 +374,7 @@ void dc_dmub_srv_set_drr_manual_trigger_cmd(struct dc *dc, uint32_t tg_inst)
        cmd.drr_update.header.payload_bytes = sizeof(cmd.drr_update) - sizeof(cmd.drr_update.header);
 
        // Send the command to the DMCUB.
-       dm_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
+       dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
 }
 
 static uint8_t dc_dmub_srv_get_pipes_for_stream(struct dc *dc, struct dc_stream_state *stream)
@@ -448,7 +467,7 @@ bool dc_dmub_srv_p_state_delegate(struct dc *dc, bool should_manage_pstate, stru
                sizeof(cmd.fw_assisted_mclk_switch) - sizeof(cmd.fw_assisted_mclk_switch.header);
 
        // Send the command to the DMCUB.
-       dm_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
+       dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
 
        return true;
 }
@@ -469,7 +488,7 @@ void dc_dmub_srv_query_caps_cmd(struct dc_dmub_srv *dc_dmub_srv)
        cmd.query_feature_caps.header.payload_bytes = sizeof(struct dmub_cmd_query_feature_caps_data);
 
        /* If command was processed, copy feature caps to dmub srv */
-       if (dm_execute_dmub_cmd(dc_dmub_srv->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY) &&
+       if (dc_wake_and_execute_dmub_cmd(dc_dmub_srv->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY) &&
            cmd.query_feature_caps.header.ret_status == 0) {
                memcpy(&dc_dmub_srv->dmub->feature_caps,
                       &cmd.query_feature_caps.query_feature_caps_data,
@@ -494,7 +513,7 @@ void dc_dmub_srv_get_visual_confirm_color_cmd(struct dc *dc, struct pipe_ctx *pi
        cmd.visual_confirm_color.visual_confirm_color_data.visual_confirm_color.panel_inst = panel_inst;
 
        // If command was processed, copy feature caps to dmub srv
-       if (dm_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY) &&
+       if (dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY) &&
                cmd.visual_confirm_color.header.ret_status == 0) {
                memcpy(&dc->ctx->dmub_srv->dmub->visual_confirm_color,
                        &cmd.visual_confirm_color.visual_confirm_color_data,
@@ -856,7 +875,7 @@ void dc_dmub_setup_subvp_dmub_command(struct dc *dc,
                cmd.fw_assisted_mclk_switch_v2.config_data.watermark_a_cache = wm_val_refclk < 0xFFFF ? wm_val_refclk : 0xFFFF;
        }
 
-       dm_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
+       dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
 }
 
 bool dc_dmub_srv_get_diagnostic_data(struct dc_dmub_srv *dc_dmub_srv, struct dmub_diagnostic_data *diag_data)
@@ -1093,7 +1112,7 @@ void dc_send_update_cursor_info_to_dmu(
                                pipe_idx, pCtx->plane_res.hubp, pCtx->plane_res.dpp);
 
                /* Combine 2nd cmds update_curosr_info to DMU */
-               dm_execute_dmub_cmd_list(pCtx->stream->ctx, 2, cmd, DM_DMUB_WAIT_TYPE_WAIT);
+               dc_wake_and_execute_dmub_cmd_list(pCtx->stream->ctx, 2, cmd, DM_DMUB_WAIT_TYPE_WAIT);
        }
 }
 
@@ -1143,14 +1162,23 @@ bool dc_dmub_srv_is_hw_pwr_up(struct dc_dmub_srv *dc_dmub_srv, bool wait)
        struct dc_context *dc_ctx = dc_dmub_srv->ctx;
        enum dmub_status status;
 
+       if (!dc_dmub_srv || !dc_dmub_srv->dmub)
+               return true;
+
        if (dc_dmub_srv->ctx->dc->debug.dmcub_emulation)
                return true;
 
        if (wait) {
-               status = dmub_srv_wait_for_hw_pwr_up(dc_dmub_srv->dmub, 500000);
-               if (status != DMUB_STATUS_OK) {
-                       DC_ERROR("Error querying DMUB hw power up status: error=%d\n", status);
-                       return false;
+               if (dc_dmub_srv->ctx->dc->debug.disable_timeout) {
+                       do {
+                               status = dmub_srv_wait_for_hw_pwr_up(dc_dmub_srv->dmub, 500000);
+                       } while (status != DMUB_STATUS_OK);
+               } else {
+                       status = dmub_srv_wait_for_hw_pwr_up(dc_dmub_srv->dmub, 500000);
+                       if (status != DMUB_STATUS_OK) {
+                               DC_ERROR("Error querying DMUB hw power up status: error=%d\n", status);
+                               return false;
+                       }
                }
        } else
                return dmub_srv_is_hw_pwr_up(dc_dmub_srv->dmub);
@@ -1158,7 +1186,7 @@ bool dc_dmub_srv_is_hw_pwr_up(struct dc_dmub_srv *dc_dmub_srv, bool wait)
        return true;
 }
 
-void dc_dmub_srv_notify_idle(const struct dc *dc, bool allow_idle)
+static void dc_dmub_srv_notify_idle(const struct dc *dc, bool allow_idle)
 {
        union dmub_rb_cmd cmd = {0};
 
@@ -1179,15 +1207,16 @@ void dc_dmub_srv_notify_idle(const struct dc *dc, bool allow_idle)
                        dc->hwss.set_idle_state(dc, true);
        }
 
+       /* NOTE: This does not use the "wake" interface since this is part of the wake path. */
        dm_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
 }
 
-void dc_dmub_srv_exit_low_power_state(const struct dc *dc)
+static void dc_dmub_srv_exit_low_power_state(const struct dc *dc)
 {
        const uint32_t max_num_polls = 10000;
        uint32_t allow_state = 0;
        uint32_t commit_state = 0;
-       uint32_t i;
+       int i;
 
        if (dc->debug.dmcub_emulation)
                return;
@@ -1195,6 +1224,9 @@ void dc_dmub_srv_exit_low_power_state(const struct dc *dc)
        if (!dc->idle_optimizations_allowed)
                return;
 
+       if (!dc->ctx->dmub_srv || !dc->ctx->dmub_srv->dmub)
+               return;
+
        if (dc->hwss.get_idle_state &&
                dc->hwss.set_idle_state &&
                dc->clk_mgr->funcs->exit_low_power_state) {
@@ -1220,6 +1252,9 @@ void dc_dmub_srv_exit_low_power_state(const struct dc *dc)
                                                break;
 
                                        udelay(1);
+
+                                       if (dc->debug.disable_timeout)
+                                               i--;
                                }
                                ASSERT(i < max_num_polls);
 
@@ -1242,6 +1277,9 @@ void dc_dmub_srv_exit_low_power_state(const struct dc *dc)
                                        break;
 
                                udelay(1);
+
+                               if (dc->debug.disable_timeout)
+                                       i--;
                        }
                        ASSERT(i < max_num_polls);
                }
@@ -1251,3 +1289,82 @@ void dc_dmub_srv_exit_low_power_state(const struct dc *dc)
                ASSERT(0);
 }
 
+void dc_dmub_srv_set_power_state(struct dc_dmub_srv *dc_dmub_srv, enum dc_acpi_cm_power_state powerState)
+{
+       struct dmub_srv *dmub;
+
+       if (!dc_dmub_srv)
+               return;
+
+       dmub = dc_dmub_srv->dmub;
+
+       if (powerState == DC_ACPI_CM_POWER_STATE_D0)
+               dmub_srv_set_power_state(dmub, DMUB_POWER_STATE_D0);
+       else
+               dmub_srv_set_power_state(dmub, DMUB_POWER_STATE_D3);
+}
+
+void dc_dmub_srv_apply_idle_power_optimizations(const struct dc *dc, bool allow_idle)
+{
+       struct dc_dmub_srv *dc_dmub_srv = dc->ctx->dmub_srv;
+
+       if (!dc_dmub_srv || !dc_dmub_srv->dmub)
+               return;
+
+       if (dc_dmub_srv->idle_allowed == allow_idle)
+               return;
+
+       /*
+        * Entering a low power state requires a driver notification.
+        * Powering up the hardware requires notifying PMFW and DMCUB.
+        * Clearing the driver idle allow requires a DMCUB command.
+        * DMCUB commands requires the DMCUB to be powered up and restored.
+        *
+        * Exit out early to prevent an infinite loop of DMCUB commands
+        * triggering exit low power - use software state to track this.
+        */
+       dc_dmub_srv->idle_allowed = allow_idle;
+
+       if (!allow_idle)
+               dc_dmub_srv_exit_low_power_state(dc);
+       else
+               dc_dmub_srv_notify_idle(dc, allow_idle);
+}
+
+bool dc_wake_and_execute_dmub_cmd(const struct dc_context *ctx, union dmub_rb_cmd *cmd,
+                                 enum dm_dmub_wait_type wait_type)
+{
+       return dc_wake_and_execute_dmub_cmd_list(ctx, 1, cmd, wait_type);
+}
+
+bool dc_wake_and_execute_dmub_cmd_list(const struct dc_context *ctx, unsigned int count,
+                                      union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type)
+{
+       struct dc_dmub_srv *dc_dmub_srv = ctx->dmub_srv;
+       bool result = false, reallow_idle = false;
+
+       if (!dc_dmub_srv || !dc_dmub_srv->dmub)
+               return false;
+
+       if (count == 0)
+               return true;
+
+       if (dc_dmub_srv->idle_allowed) {
+               dc_dmub_srv_apply_idle_power_optimizations(ctx->dc, false);
+               reallow_idle = true;
+       }
+
+       /*
+        * These may have different implementations in DM, so ensure
+        * that we guide it to the expected helper.
+        */
+       if (count > 1)
+               result = dm_execute_dmub_cmd_list(ctx, count, cmd, wait_type);
+       else
+               result = dm_execute_dmub_cmd(ctx, cmd, wait_type);
+
+       if (result && reallow_idle)
+               dc_dmub_srv_apply_idle_power_optimizations(ctx->dc, true);
+
+       return result;
+}