#include <linux/types.h>
#include <drm/drm_vblank.h>
#include "dc.h"
#include "amdgpu.h"
#include "amdgpu_dm_ism.h"
#include "amdgpu_dm_crtc.h"
#include "amdgpu_dm_trace.h"
#include "amdgpu_dm_kunit_helpers.h"
STATIC_IFN_KUNIT
bool dm_ism_next_state(enum amdgpu_dm_ism_state current_state,
enum amdgpu_dm_ism_event event,
enum amdgpu_dm_ism_state *next_state)
{
switch (STATE_EVENT(current_state, event)) {
case STATE_EVENT(DM_ISM_STATE_FULL_POWER_RUNNING,
DM_ISM_EVENT_ENTER_IDLE_REQUESTED):
*next_state = DM_ISM_STATE_HYSTERESIS_WAITING;
break;
case STATE_EVENT(DM_ISM_STATE_FULL_POWER_RUNNING,
DM_ISM_EVENT_BEGIN_CURSOR_UPDATE):
*next_state = DM_ISM_STATE_FULL_POWER_BUSY;
break;
case STATE_EVENT(DM_ISM_STATE_FULL_POWER_BUSY,
DM_ISM_EVENT_ENTER_IDLE_REQUESTED):
*next_state = DM_ISM_STATE_HYSTERESIS_BUSY;
break;
case STATE_EVENT(DM_ISM_STATE_FULL_POWER_BUSY,
DM_ISM_EVENT_END_CURSOR_UPDATE):
*next_state = DM_ISM_STATE_FULL_POWER_RUNNING;
break;
case STATE_EVENT(DM_ISM_STATE_HYSTERESIS_WAITING,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED):
*next_state = DM_ISM_STATE_TIMER_ABORTED;
break;
case STATE_EVENT(DM_ISM_STATE_HYSTERESIS_WAITING,
DM_ISM_EVENT_BEGIN_CURSOR_UPDATE):
*next_state = DM_ISM_STATE_HYSTERESIS_BUSY;
break;
case STATE_EVENT(DM_ISM_STATE_HYSTERESIS_WAITING,
DM_ISM_EVENT_TIMER_ELAPSED):
*next_state = DM_ISM_STATE_OPTIMIZED_IDLE;
break;
case STATE_EVENT(DM_ISM_STATE_HYSTERESIS_WAITING,
DM_ISM_EVENT_IMMEDIATE):
*next_state = DM_ISM_STATE_OPTIMIZED_IDLE;
break;
case STATE_EVENT(DM_ISM_STATE_HYSTERESIS_BUSY,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED):
*next_state = DM_ISM_STATE_FULL_POWER_BUSY;
break;
case STATE_EVENT(DM_ISM_STATE_HYSTERESIS_BUSY,
DM_ISM_EVENT_END_CURSOR_UPDATE):
*next_state = DM_ISM_STATE_HYSTERESIS_WAITING;
break;
case STATE_EVENT(DM_ISM_STATE_OPTIMIZED_IDLE,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED):
*next_state = DM_ISM_STATE_FULL_POWER_RUNNING;
break;
case STATE_EVENT(DM_ISM_STATE_OPTIMIZED_IDLE,
DM_ISM_EVENT_BEGIN_CURSOR_UPDATE):
*next_state = DM_ISM_STATE_HYSTERESIS_BUSY;
break;
case STATE_EVENT(DM_ISM_STATE_OPTIMIZED_IDLE,
DM_ISM_EVENT_SSO_TIMER_ELAPSED):
case STATE_EVENT(DM_ISM_STATE_OPTIMIZED_IDLE,
DM_ISM_EVENT_IMMEDIATE):
*next_state = DM_ISM_STATE_OPTIMIZED_IDLE_SSO;
break;
case STATE_EVENT(DM_ISM_STATE_OPTIMIZED_IDLE_SSO,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED):
*next_state = DM_ISM_STATE_FULL_POWER_RUNNING;
break;
case STATE_EVENT(DM_ISM_STATE_OPTIMIZED_IDLE_SSO,
DM_ISM_EVENT_BEGIN_CURSOR_UPDATE):
*next_state = DM_ISM_STATE_HYSTERESIS_BUSY;
break;
case STATE_EVENT(DM_ISM_STATE_TIMER_ABORTED,
DM_ISM_EVENT_IMMEDIATE):
*next_state = DM_ISM_STATE_FULL_POWER_RUNNING;
break;
default:
return false;
}
return true;
}
EXPORT_IF_KUNIT(dm_ism_next_state);
STATIC_IFN_KUNIT
uint64_t dm_ism_get_sso_delay(const struct amdgpu_dm_ism *ism,
const struct dc_stream_state *stream)
{
const struct amdgpu_dm_ism_config *config = &ism->config;
uint32_t v_total, h_total;
uint64_t one_frame_ns, sso_delay_ns;
if (!stream)
return 0;
if (!config->sso_num_frames)
return 0;
v_total = stream->timing.v_total;
h_total = stream->timing.h_total;
one_frame_ns = div64_u64(v_total * h_total * 10000000ull,
stream->timing.pix_clk_100hz);
sso_delay_ns = config->sso_num_frames * one_frame_ns;
return sso_delay_ns;
}
EXPORT_IF_KUNIT(dm_ism_get_sso_delay);
STATIC_IFN_KUNIT
uint64_t dm_ism_get_idle_allow_delay(const struct amdgpu_dm_ism *ism,
const struct dc_stream_state *stream)
{
const struct amdgpu_dm_ism_config *config = &ism->config;
uint32_t v_total, h_total;
uint64_t one_frame_ns, short_idle_ns, old_hist_ns;
uint32_t history_size;
int pos;
uint32_t short_idle_count = 0;
uint64_t ret_ns = 0;
if (!stream)
return 0;
if (!config->filter_num_frames)
return 0;
if (!config->filter_entry_count)
return 0;
if (!config->activation_num_delay_frames)
return 0;
v_total = stream->timing.v_total;
h_total = stream->timing.h_total;
one_frame_ns = div64_u64(v_total * h_total * 10000000ull,
stream->timing.pix_clk_100hz);
short_idle_ns = config->filter_num_frames * one_frame_ns;
old_hist_ns = config->filter_old_history_threshold * one_frame_ns;
history_size = min(
max(config->filter_history_size, config->filter_entry_count),
AMDGPU_DM_IDLE_HIST_LEN);
pos = ism->next_record_idx;
for (int k = 0; k < history_size; k++) {
if (pos <= 0 || pos > AMDGPU_DM_IDLE_HIST_LEN)
pos = AMDGPU_DM_IDLE_HIST_LEN;
pos -= 1;
if (ism->records[pos].duration_ns <= short_idle_ns)
short_idle_count += 1;
if (short_idle_count >= config->filter_entry_count)
break;
if (old_hist_ns > 0 &&
ism->last_idle_timestamp_ns - ism->records[pos].timestamp_ns > old_hist_ns)
break;
}
if (short_idle_count >= config->filter_entry_count)
ret_ns = config->activation_num_delay_frames * one_frame_ns;
return ret_ns;
}
EXPORT_IF_KUNIT(dm_ism_get_idle_allow_delay);
STATIC_IFN_KUNIT
void dm_ism_insert_record(struct amdgpu_dm_ism *ism)
{
struct amdgpu_dm_ism_record *record;
if (ism->next_record_idx < 0 ||
ism->next_record_idx >= AMDGPU_DM_IDLE_HIST_LEN)
ism->next_record_idx = 0;
record = &ism->records[ism->next_record_idx];
ism->next_record_idx += 1;
record->timestamp_ns = ktime_get_ns();
record->duration_ns =
record->timestamp_ns - ism->last_idle_timestamp_ns;
}
EXPORT_IF_KUNIT(dm_ism_insert_record);
STATIC_IFN_KUNIT
void dm_ism_set_last_idle_ts(struct amdgpu_dm_ism *ism)
{
ism->last_idle_timestamp_ns = ktime_get_ns();
}
EXPORT_IF_KUNIT(dm_ism_set_last_idle_ts);
STATIC_IFN_KUNIT
bool dm_ism_trigger_event(struct amdgpu_dm_ism *ism,
enum amdgpu_dm_ism_event event)
{
enum amdgpu_dm_ism_state next_state;
bool gotNextState = dm_ism_next_state(ism->current_state, event,
&next_state);
if (gotNextState) {
ism->previous_state = ism->current_state;
ism->current_state = next_state;
}
return gotNextState;
}
EXPORT_IF_KUNIT(dm_ism_trigger_event);
static void dm_ism_commit_idle_optimization_state(struct amdgpu_dm_ism *ism,
struct dc_stream_state *stream,
bool vblank_enabled,
bool allow_panel_sso)
{
struct amdgpu_crtc *acrtc = ism_to_amdgpu_crtc(ism);
struct amdgpu_device *adev = drm_to_adev(acrtc->base.dev);
struct amdgpu_display_manager *dm = &adev->dm;
trace_amdgpu_dm_ism_commit(dm->active_vblank_irq_count,
vblank_enabled,
allow_panel_sso);
if (vblank_enabled || allow_panel_sso)
dc_allow_idle_optimizations(dm->dc, false);
if (stream && stream->link) {
allow_panel_sso = allow_panel_sso && !vblank_enabled;
amdgpu_dm_crtc_set_static_screen_optimze(
dm, stream, allow_panel_sso,
acrtc->dm_irq_params.allow_sr_entry);
}
if (!vblank_enabled && dm->active_vblank_irq_count == 0) {
dc_post_update_surfaces_to_stream(dm->dc);
dc_allow_idle_optimizations(dm->dc, true);
}
}
STATIC_IFN_KUNIT
enum amdgpu_dm_ism_event dm_ism_dispatch_next_event(
enum amdgpu_dm_ism_state current_state,
uint64_t delay_ns,
uint64_t sso_delay_ns)
{
switch (current_state) {
case DM_ISM_STATE_HYSTERESIS_WAITING:
if (delay_ns == 0)
return DM_ISM_EVENT_IMMEDIATE;
break;
case DM_ISM_STATE_OPTIMIZED_IDLE:
if (sso_delay_ns == 0)
return DM_ISM_EVENT_IMMEDIATE;
break;
case DM_ISM_STATE_TIMER_ABORTED:
return DM_ISM_EVENT_IMMEDIATE;
default:
break;
}
return DM_ISM_NUM_EVENTS;
}
EXPORT_IF_KUNIT(dm_ism_dispatch_next_event);
static enum amdgpu_dm_ism_event dm_ism_dispatch_power_state(
struct amdgpu_dm_ism *ism,
struct dm_crtc_state *acrtc_state)
{
const struct amdgpu_dm_ism_config *config = &ism->config;
uint64_t delay_ns = 0, sso_delay_ns = 0;
switch (ism->previous_state) {
case DM_ISM_STATE_HYSTERESIS_WAITING:
if (ism->current_state != DM_ISM_STATE_OPTIMIZED_IDLE &&
ism->current_state != DM_ISM_STATE_OPTIMIZED_IDLE_SSO)
cancel_delayed_work(&ism->delayed_work);
break;
case DM_ISM_STATE_OPTIMIZED_IDLE:
if (ism->current_state == DM_ISM_STATE_OPTIMIZED_IDLE_SSO)
break;
cancel_delayed_work(&ism->sso_delayed_work);
dm_ism_insert_record(ism);
dm_ism_commit_idle_optimization_state(ism, acrtc_state->stream,
true, false);
break;
case DM_ISM_STATE_OPTIMIZED_IDLE_SSO:
dm_ism_insert_record(ism);
dm_ism_commit_idle_optimization_state(ism, acrtc_state->stream,
true, false);
break;
default:
break;
}
switch (ism->current_state) {
case DM_ISM_STATE_HYSTERESIS_WAITING:
dm_ism_set_last_idle_ts(ism);
delay_ns = dm_ism_get_idle_allow_delay(ism, acrtc_state->stream);
if (delay_ns > 0)
mod_delayed_work(system_dfl_wq, &ism->delayed_work,
nsecs_to_jiffies(delay_ns));
break;
case DM_ISM_STATE_OPTIMIZED_IDLE:
sso_delay_ns = dm_ism_get_sso_delay(ism, acrtc_state->stream);
if (sso_delay_ns > 0) {
if (config->sso_num_frames >= config->filter_num_frames)
dm_ism_commit_idle_optimization_state(ism, acrtc_state->stream,
false, false);
mod_delayed_work(system_dfl_wq,
&ism->sso_delayed_work,
nsecs_to_jiffies(sso_delay_ns));
}
break;
case DM_ISM_STATE_OPTIMIZED_IDLE_SSO:
dm_ism_commit_idle_optimization_state(ism, acrtc_state->stream,
false, true);
break;
case DM_ISM_STATE_TIMER_ABORTED:
dm_ism_insert_record(ism);
dm_ism_commit_idle_optimization_state(ism, acrtc_state->stream,
true, false);
break;
default:
break;
}
return dm_ism_dispatch_next_event(ism->current_state, delay_ns, sso_delay_ns);
}
static char *dm_ism_events_str[DM_ISM_NUM_EVENTS] = {
[DM_ISM_EVENT_IMMEDIATE] = "IMMEDIATE",
[DM_ISM_EVENT_ENTER_IDLE_REQUESTED] = "ENTER_IDLE_REQUESTED",
[DM_ISM_EVENT_EXIT_IDLE_REQUESTED] = "EXIT_IDLE_REQUESTED",
[DM_ISM_EVENT_BEGIN_CURSOR_UPDATE] = "BEGIN_CURSOR_UPDATE",
[DM_ISM_EVENT_END_CURSOR_UPDATE] = "END_CURSOR_UPDATE",
[DM_ISM_EVENT_TIMER_ELAPSED] = "TIMER_ELAPSED",
[DM_ISM_EVENT_SSO_TIMER_ELAPSED] = "SSO_TIMER_ELAPSED",
};
static char *dm_ism_states_str[DM_ISM_NUM_STATES] = {
[DM_ISM_STATE_FULL_POWER_RUNNING] = "FULL_POWER_RUNNING",
[DM_ISM_STATE_FULL_POWER_BUSY] = "FULL_POWER_BUSY",
[DM_ISM_STATE_HYSTERESIS_WAITING] = "HYSTERESIS_WAITING",
[DM_ISM_STATE_HYSTERESIS_BUSY] = "HYSTERESIS_BUSY",
[DM_ISM_STATE_OPTIMIZED_IDLE] = "OPTIMIZED_IDLE",
[DM_ISM_STATE_OPTIMIZED_IDLE_SSO] = "OPTIMIZED_IDLE_SSO",
[DM_ISM_STATE_TIMER_ABORTED] = "TIMER_ABORTED",
};
void amdgpu_dm_ism_commit_event(struct amdgpu_dm_ism *ism,
enum amdgpu_dm_ism_event event)
{
enum amdgpu_dm_ism_event next_event = event;
struct amdgpu_crtc *acrtc = ism_to_amdgpu_crtc(ism);
struct amdgpu_device *adev = drm_to_adev(acrtc->base.dev);
struct amdgpu_display_manager *dm = &adev->dm;
struct dm_crtc_state *acrtc_state = to_dm_crtc_state(acrtc->base.state);
lockdep_assert_held(&dm->dc_lock);
ASSERT(dm->dc);
if (!acrtc_state) {
trace_amdgpu_dm_ism_event(acrtc->crtc_id, "NO_STATE",
"NO_STATE", "N/A");
return;
}
do {
bool transition = dm_ism_trigger_event(ism, event);
next_event = DM_ISM_NUM_EVENTS;
if (transition) {
trace_amdgpu_dm_ism_event(
acrtc->crtc_id,
dm_ism_states_str[ism->previous_state],
dm_ism_states_str[ism->current_state],
dm_ism_events_str[event]);
next_event = dm_ism_dispatch_power_state(ism, acrtc_state);
} else {
trace_amdgpu_dm_ism_event(
acrtc->crtc_id,
dm_ism_states_str[ism->current_state],
dm_ism_states_str[ism->current_state],
dm_ism_events_str[event]);
}
event = next_event;
} while (next_event < DM_ISM_NUM_EVENTS);
}
static void dm_ism_delayed_work_func(struct work_struct *work)
{
struct amdgpu_dm_ism *ism =
container_of(work, struct amdgpu_dm_ism, delayed_work.work);
struct amdgpu_crtc *acrtc = ism_to_amdgpu_crtc(ism);
struct amdgpu_device *adev = drm_to_adev(acrtc->base.dev);
struct amdgpu_display_manager *dm = &adev->dm;
guard(mutex)(&dm->dc_lock);
amdgpu_dm_ism_commit_event(ism, DM_ISM_EVENT_TIMER_ELAPSED);
}
static void dm_ism_sso_delayed_work_func(struct work_struct *work)
{
struct amdgpu_dm_ism *ism =
container_of(work, struct amdgpu_dm_ism, sso_delayed_work.work);
struct amdgpu_crtc *acrtc = ism_to_amdgpu_crtc(ism);
struct amdgpu_device *adev = drm_to_adev(acrtc->base.dev);
struct amdgpu_display_manager *dm = &adev->dm;
guard(mutex)(&dm->dc_lock);
amdgpu_dm_ism_commit_event(ism, DM_ISM_EVENT_SSO_TIMER_ELAPSED);
}
void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm)
{
struct drm_crtc *crtc;
struct amdgpu_crtc *acrtc;
struct amdgpu_dm_ism *ism;
lockdep_assert_not_held(&dm->dc_lock);
drm_for_each_crtc(crtc, dm->ddev) {
acrtc = to_amdgpu_crtc(crtc);
ism = &acrtc->ism;
disable_delayed_work_sync(&ism->delayed_work);
disable_delayed_work_sync(&ism->sso_delayed_work);
}
}
void amdgpu_dm_ism_force_full_power(struct amdgpu_display_manager *dm)
{
struct drm_crtc *crtc;
struct amdgpu_crtc *acrtc;
lockdep_assert_held(&dm->dc_lock);
drm_for_each_crtc(crtc, dm->ddev) {
acrtc = to_amdgpu_crtc(crtc);
amdgpu_dm_ism_commit_event(&acrtc->ism,
DM_ISM_EVENT_EXIT_IDLE_REQUESTED);
}
}
void amdgpu_dm_ism_enable(struct amdgpu_display_manager *dm)
{
struct drm_crtc *crtc;
struct amdgpu_crtc *acrtc;
struct amdgpu_dm_ism *ism;
drm_for_each_crtc(crtc, dm->ddev) {
acrtc = to_amdgpu_crtc(crtc);
ism = &acrtc->ism;
enable_delayed_work(&ism->delayed_work);
enable_delayed_work(&ism->sso_delayed_work);
}
}
void amdgpu_dm_ism_init(struct amdgpu_dm_ism *ism,
struct amdgpu_dm_ism_config *config)
{
ism->config = *config;
ism->current_state = DM_ISM_STATE_FULL_POWER_RUNNING;
ism->previous_state = DM_ISM_STATE_FULL_POWER_RUNNING;
ism->next_record_idx = 0;
ism->last_idle_timestamp_ns = 0;
INIT_DELAYED_WORK(&ism->delayed_work, dm_ism_delayed_work_func);
INIT_DELAYED_WORK(&ism->sso_delayed_work, dm_ism_sso_delayed_work_func);
}
EXPORT_IF_KUNIT(amdgpu_dm_ism_init);
void amdgpu_dm_ism_fini(struct amdgpu_dm_ism *ism)
{
cancel_delayed_work_sync(&ism->sso_delayed_work);
cancel_delayed_work_sync(&ism->delayed_work);
}
EXPORT_IF_KUNIT(amdgpu_dm_ism_fini);