AMD General

> -----Original Message-----
> From: Kenneth Feng <[email protected]>
> Sent: Friday, October 16, 2026 3:24 PM
> To: [email protected]
> Cc: Wang, Kevin <[email protected]>; Feng, Kenneth
> <[email protected]>
> Subject: [PATCH] drm/amd/pm:restate all customized OD features on every
> table upload for smu 13.0.0/smu 14.0.2
>
> PMFW interprets OverDriveTable.FeatureCtrlMask as the complete set of
> overdrive features that are currently overridden, not as a delta describing 
> the
> operation at hand. On import it resets every feature whose bit is clear back 
> to
> its default value - for the VF curve that means zeroing
> VoltageOffsetPerZoneBoundary[] outright.
>
> The driver assumed the opposite. smu_v14_0_2_od_edit_dpm_table() clears
> the mask after a successful commit, so a subsequent above-default
> power1_cap write goes through smu_v14_0_2_set_ppt_limit() and uploads a
> table carrying only PP_OD_FEATURE_PPT_BIT. PMFW then drops the user's
> voltage offset and reverts to the stock V/F curve, while the driver's cached
> user table still reports the offset through pp_od_clk_voltage.
>
> Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5935
> Signed-off-by: Kenneth Feng <[email protected]>
> ---
>  drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h |  2
> +  .../drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c  | 35 +++++++----------
>   .../drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c  | 32 +++++++---------
>   .../drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c  | 38 +++++++++++-----
> ---
>  4 files changed, 52 insertions(+), 55 deletions(-)
>
> diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h
> b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h
> index f516d0d3bb92..9d47d2ab1b49 100644
> --- a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h
> +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h
> @@ -433,6 +433,8 @@ struct smu_table_context {
>       void                            *boot_overdrive_table;
>       void                            *user_overdrive_table;
>
> +     uint32_t                        user_od_feature_mask;
> +
>       struct smu_driver_table driver_tables[SMU_DRIVER_TABLE_COUNT];
>  };
>
> diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c
> b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c
> index 6beafabf1306..468aaeb79ffb 100644
> --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c
> +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c
> @@ -1569,7 +1569,6 @@ static int smu_v13_0_0_od_edit_dpm_table(struct
> smu_context *smu,
>       struct amdgpu_device *adev = smu->adev;
>       uint32_t offset_of_voltageoffset;
>       int32_t minimum, maximum;
> -     uint32_t feature_ctrlmask;
>       int i, ret = 0;
>
>       switch (type) {
> @@ -1891,21 +1890,16 @@ static int
> smu_v13_0_0_od_edit_dpm_table(struct smu_context *smu,
>                       if (ret)
>                               return ret;
>               } else {
> -                     feature_ctrlmask = od_table-
> >OverDriveTable.FeatureCtrlMask;
>                       memcpy(od_table,
>                      table_context->boot_overdrive_table,
>                      sizeof(OverDriveTableExternal_t));
> -                     od_table->OverDriveTable.FeatureCtrlMask =
> feature_ctrlmask;
> +                     od_table->OverDriveTable.FeatureCtrlMask = 0;
> +                     table_context->user_od_feature_mask = 0;
>               }
>               fallthrough;
>       case PP_OD_COMMIT_DPM_TABLE:
> -             /*
> -              * The member below instructs PMFW the settings focused in
> -              * this single operation.
> -              * `uint32_t FeatureCtrlMask;`
> -              * It does not contain actual informations about user's custom
> -              * settings. Thus we do not cache it.
> -              */
> +             table_context->user_od_feature_mask =
> +                     od_table->OverDriveTable.FeatureCtrlMask;
>               offset_of_voltageoffset = offsetof(OverDriveTable_t,
> VoltageOffsetPerZoneBoundary);
>               if (memcmp((u8 *)od_table + offset_of_voltageoffset,
>                          table_context->user_overdrive_table +
> offset_of_voltageoffset, @@ -1918,7 +1912,6 @@ static int
> smu_v13_0_0_od_edit_dpm_table(struct smu_context *smu,
>                               return ret;
>                       }
>
> -                     od_table->OverDriveTable.FeatureCtrlMask = 0;
>                       memcpy(table_context->user_overdrive_table +
> offset_of_voltageoffset,
>                              (u8 *)od_table + offset_of_voltageoffset,
>                              sizeof(OverDriveTableExternal_t) -
> offset_of_voltageoffset); @@ -2201,6 +2194,7 @@ static int
> smu_v13_0_0_set_default_od_settings(struct smu_context *smu)
>                      boot_od_table,
>                      sizeof(OverDriveTableExternal_t));
>               smu->user_dpm_profile.user_od = false;
> +             smu->smu_table.user_od_feature_mask = 0;
>       } else if (smu->user_dpm_profile.user_od) {
>               memcpy(&user_od_table_bak,
>                      user_od_table,
> @@ -2250,12 +2244,8 @@ static int
> smu_v13_0_0_restore_user_od_settings(struct smu_context *smu)
>       OverDriveTableExternal_t *od_table = table_context-
> >overdrive_table;
>       OverDriveTableExternal_t *user_od_table = table_context-
> >user_overdrive_table;
>       int res;
> -
> -     user_od_table->OverDriveTable.FeatureCtrlMask =
> BIT(PP_OD_FEATURE_GFXCLK_BIT) |
> -
>       BIT(PP_OD_FEATURE_UCLK_BIT) |
> -
>       BIT(PP_OD_FEATURE_GFX_VF_CURVE_BIT) |
> -
>       BIT(PP_OD_FEATURE_FAN_CURVE_BIT) |
> -
>       BIT(PP_OD_FEATURE_ZERO_FAN_BIT);
> +     user_od_table->OverDriveTable.FeatureCtrlMask =
> +             table_context->user_od_feature_mask;
>       res = smu_v13_0_0_upload_overdrive_table(smu, user_od_table);
>       user_od_table->OverDriveTable.FeatureCtrlMask = 0;
>       if (res == 0)
> @@ -3069,8 +3059,10 @@ static int smu_v13_0_0_set_ppt_limit(struct
> smu_context *smu,
>
>               if (current_od_table.OverDriveTable.Ppt) {
>                       od_table->OverDriveTable.Ppt = 0;
> -                     od_table->OverDriveTable.FeatureCtrlMask |= 1U <<
> PP_OD_FEATURE_PPT_BIT;
> -
> +                     table_context->user_od_feature_mask &=
> +                             ~(1U << PP_OD_FEATURE_PPT_BIT);
> +                     od_table->OverDriveTable.FeatureCtrlMask =
> +                             table_context->user_od_feature_mask;
>                       ret = smu_v13_0_0_upload_overdrive_table(smu,
> od_table);
>                       if (ret) {
>                               dev_err(smu->adev->dev, "Failed to upload
> overdrive table!\n"); @@ -3084,8 +3076,9 @@ static int
> smu_v13_0_0_set_ppt_limit(struct smu_context *smu,
>                       return ret;
>
>               od_table->OverDriveTable.Ppt = (limit * 100) / msg_limit -
> 100;
> -             od_table->OverDriveTable.FeatureCtrlMask |= 1U <<
> PP_OD_FEATURE_PPT_BIT;
> -
> +             table_context->user_od_feature_mask |= 1U <<
> PP_OD_FEATURE_PPT_BIT;
> +             od_table->OverDriveTable.FeatureCtrlMask =
> +                     table_context->user_od_feature_mask;
>               ret = smu_v13_0_0_upload_overdrive_table(smu, od_table);
>               if (ret) {
>                 dev_err(smu->adev->dev, "Failed to upload overdrive
> table!\n"); diff --git
> a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c
> b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c
> index 414fb436d88f..b96152d3920f 100644
> --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c
> +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c
> @@ -1578,7 +1578,6 @@ static int smu_v13_0_7_od_edit_dpm_table(struct
> smu_context *smu,
>       struct amdgpu_device *adev = smu->adev;
>       uint32_t offset_of_voltageoffset;
>       int32_t minimum, maximum;
> -     uint32_t feature_ctrlmask;
>       int i, ret = 0;
>
>       switch (type) {
> @@ -1900,22 +1899,17 @@ static int
> smu_v13_0_7_od_edit_dpm_table(struct smu_context *smu,
>                       if (ret)
>                               return ret;
>               } else {
> -                     feature_ctrlmask = od_table-
> >OverDriveTable.FeatureCtrlMask;
>                       memcpy(od_table,
>                                       table_context->boot_overdrive_table,
>                                       sizeof(OverDriveTableExternal_t));
> -                     od_table->OverDriveTable.FeatureCtrlMask =
> feature_ctrlmask;
> +                     od_table->OverDriveTable.FeatureCtrlMask = 0;
> +                     table_context->user_od_feature_mask = 0;
>               }
>               fallthrough;
>
>       case PP_OD_COMMIT_DPM_TABLE:
> -             /*
> -              * The member below instructs PMFW the settings focused in
> -              * this single operation.
> -              * `uint32_t FeatureCtrlMask;`
> -              * It does not contain actual informations about user's custom
> -              * settings. Thus we do not cache it.
> -              */
> +             table_context->user_od_feature_mask =
> +                     od_table->OverDriveTable.FeatureCtrlMask;
>               offset_of_voltageoffset = offsetof(OverDriveTable_t,
> VoltageOffsetPerZoneBoundary);
>               if (memcmp((u8 *)od_table + offset_of_voltageoffset,
>                          table_context->user_overdrive_table +
> offset_of_voltageoffset, @@ -1928,7 +1922,6 @@ static int
> smu_v13_0_7_od_edit_dpm_table(struct smu_context *smu,
>                               return ret;
>                       }
>
> -                     od_table->OverDriveTable.FeatureCtrlMask = 0;
>                       memcpy(table_context->user_overdrive_table +
> offset_of_voltageoffset,
>                              (u8 *)od_table + offset_of_voltageoffset,
>                              sizeof(OverDriveTableExternal_t) -
> offset_of_voltageoffset); @@ -2203,6 +2196,7 @@ static int
> smu_v13_0_7_set_default_od_settings(struct smu_context *smu)
>                      boot_od_table,
>                      sizeof(OverDriveTableExternal_t));
>               smu->user_dpm_profile.user_od = false;
> +             smu->smu_table.user_od_feature_mask = 0;
>       } else if (smu->user_dpm_profile.user_od) {
>               memcpy(&user_od_table_bak,
>                      user_od_table,
> @@ -2253,11 +2247,8 @@ static int
> smu_v13_0_7_restore_user_od_settings(struct smu_context *smu)
>       OverDriveTableExternal_t *user_od_table = table_context-
> >user_overdrive_table;
>       int res;
>
> -     user_od_table->OverDriveTable.FeatureCtrlMask =
> BIT(PP_OD_FEATURE_GFXCLK_BIT) |
> -
>       BIT(PP_OD_FEATURE_UCLK_BIT) |
> -
>       BIT(PP_OD_FEATURE_GFX_VF_CURVE_BIT) |
> -
>       BIT(PP_OD_FEATURE_FAN_CURVE_BIT) |
> -
>       BIT(PP_OD_FEATURE_ZERO_FAN_BIT);
> +     user_od_table->OverDriveTable.FeatureCtrlMask =
> +             table_context->user_od_feature_mask;
>       res = smu_v13_0_7_upload_overdrive_table(smu, user_od_table);
>       user_od_table->OverDriveTable.FeatureCtrlMask = 0;
>       if (res == 0)
> @@ -2719,7 +2710,10 @@ static int smu_v13_0_7_set_ppt_limit(struct
> smu_context *smu,
>
>               if (current_od_table.OverDriveTable.Ppt) {
>                       od_table->OverDriveTable.Ppt = 0;
> -                     od_table->OverDriveTable.FeatureCtrlMask |= 1U <<
> PP_OD_FEATURE_PPT_BIT;
> +                     table_context->user_od_feature_mask &=
> +                             ~(1U << PP_OD_FEATURE_PPT_BIT);
> +                     od_table->OverDriveTable.FeatureCtrlMask =
> +                             table_context->user_od_feature_mask;
>
>                       ret = smu_v13_0_7_upload_overdrive_table(smu,
> od_table);
>                       if (ret) {
> @@ -2734,7 +2728,9 @@ static int smu_v13_0_7_set_ppt_limit(struct
> smu_context *smu,
>                       return ret;
>
>               od_table->OverDriveTable.Ppt = (limit * 100) / msg_limit -
> 100;
> -             od_table->OverDriveTable.FeatureCtrlMask |= 1U <<
> PP_OD_FEATURE_PPT_BIT;
> +             table_context->user_od_feature_mask |= 1U <<
> PP_OD_FEATURE_PPT_BIT;
> +             od_table->OverDriveTable.FeatureCtrlMask =
> +                     table_context->user_od_feature_mask;
>
>               ret = smu_v13_0_7_upload_overdrive_table(smu, od_table);
>               if (ret) {
> diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c
> b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c
> index 33f93f6d90af..c4dfa66c723b 100644
> --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c
> +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c
> @@ -2385,6 +2385,7 @@ static int
> smu_v14_0_2_set_default_od_settings(struct smu_context *smu)
>                      boot_od_table,
>                      sizeof(OverDriveTableExternal_t));
>               smu->user_dpm_profile.user_od = false;
> +             smu->smu_table.user_od_feature_mask = 0;
>       } else if (smu->user_dpm_profile.user_od) {
>               memcpy(&user_od_table_bak,
>                      user_od_table,
> @@ -2433,11 +2434,8 @@ static int
> smu_v14_0_2_restore_user_od_settings(struct smu_context *smu)
>       OverDriveTableExternal_t *user_od_table = table_context-
> >user_overdrive_table;
>       int res;
>
> -     user_od_table->OverDriveTable.FeatureCtrlMask =
> BIT(PP_OD_FEATURE_GFXCLK_BIT) |
> -
>       BIT(PP_OD_FEATURE_UCLK_BIT) |
> -
>       BIT(PP_OD_FEATURE_GFX_VF_CURVE_BIT) |
> -
>       BIT(PP_OD_FEATURE_FAN_CURVE_BIT) |
> -
>       BIT(PP_OD_FEATURE_ZERO_FAN_BIT);
> +     user_od_table->OverDriveTable.FeatureCtrlMask =
> +             table_context->user_od_feature_mask;
>       res = smu_v14_0_2_upload_overdrive_table(smu, user_od_table);

I think there is a risk here, the power1_cap and pp_od_clk_voltage shared the 
same od staging table.

Best Regars,
Kevin
>       user_od_table->OverDriveTable.FeatureCtrlMask = 0;
>       if (res == 0)
> @@ -2522,7 +2520,6 @@ static int smu_v14_0_2_od_edit_dpm_table(struct
> smu_context *smu,
>       struct amdgpu_device *adev = smu->adev;
>       uint32_t offset_of_voltageoffset;
>       int32_t minimum, maximum;
> -     uint32_t feature_ctrlmask;
>       int i, ret = 0;
>
>       switch (type) {
> @@ -2811,21 +2808,26 @@ static int
> smu_v14_0_2_od_edit_dpm_table(struct smu_context *smu,
>                       if (ret)
>                               return ret;
>               } else {
> -                     feature_ctrlmask = od_table-
> >OverDriveTable.FeatureCtrlMask;
>                       memcpy(od_table,
>                      table_context->boot_overdrive_table,
>                      sizeof(OverDriveTableExternal_t));
> -                     od_table->OverDriveTable.FeatureCtrlMask =
> feature_ctrlmask;
> +                     od_table->OverDriveTable.FeatureCtrlMask = 0;
> +                     table_context->user_od_feature_mask = 0;
>               }
>               fallthrough;
>       case PP_OD_COMMIT_DPM_TABLE:
>               /*
> -              * The member below instructs PMFW the settings focused in
> -              * this single operation.
> -              * `uint32_t FeatureCtrlMask;`
> -              * It does not contain actual informations about user's custom
> -              * settings. Thus we do not cache it.
> +              * PMFW interprets FeatureCtrlMask as the complete set of
> OD
> +              * features that are currently overridden, not as a delta
> +              * describing this single operation. Any feature whose bit is
> +              * clear gets reset to its default on import. od_table's mask
> +              * has accumulated every feature the user touched since the
> +              * last full restore, so record it and keep restating it on
> +              * subsequent uploads.
>                */
> +             table_context->user_od_feature_mask =
> +                     od_table->OverDriveTable.FeatureCtrlMask;
> +
>               offset_of_voltageoffset = offsetof(OverDriveTable_t,
> VoltageOffsetPerZoneBoundary);
>               if (memcmp((u8 *)od_table + offset_of_voltageoffset,
>                          table_context->user_overdrive_table +
> offset_of_voltageoffset, @@ -2838,7 +2840,6 @@ static int
> smu_v14_0_2_od_edit_dpm_table(struct smu_context *smu,
>                               return ret;
>                       }
>
> -                     od_table->OverDriveTable.FeatureCtrlMask = 0;
>                       memcpy(table_context->user_overdrive_table +
> offset_of_voltageoffset,
>                              (u8 *)od_table + offset_of_voltageoffset,
>                              sizeof(OverDriveTableExternal_t) -
> offset_of_voltageoffset); @@ -2882,7 +2883,10 @@ static int
> smu_v14_0_2_set_ppt_limit(struct smu_context *smu,
>
>               if (current_od_table.OverDriveTable.Ppt) {
>                       od_table->OverDriveTable.Ppt = 0;
> -                     od_table->OverDriveTable.FeatureCtrlMask |= 1U <<
> PP_OD_FEATURE_PPT_BIT;
> +                     table_context->user_od_feature_mask &=
> +                             ~(1U << PP_OD_FEATURE_PPT_BIT);
> +                     od_table->OverDriveTable.FeatureCtrlMask =
> +                             table_context->user_od_feature_mask;
Please check upload table return value first, before copy user od feature mask 
to FeatureCtrlMask.

With that fixed, the patch is

Reviewed-by: Kevin Wang <[email protected]>

Best Regards,
Kevin
>
>                       ret = smu_v14_0_2_upload_overdrive_table(smu,
> od_table);
>                       if (ret) {
> @@ -2897,7 +2901,9 @@ static int smu_v14_0_2_set_ppt_limit(struct
> smu_context *smu,
>                       return ret;
>
>               od_table->OverDriveTable.Ppt = (limit * 100) / msg_limit -
> 100;
> -             od_table->OverDriveTable.FeatureCtrlMask |= 1U <<
> PP_OD_FEATURE_PPT_BIT;
> +             table_context->user_od_feature_mask |= 1U <<
> PP_OD_FEATURE_PPT_BIT;
> +             od_table->OverDriveTable.FeatureCtrlMask =
> +                     table_context->user_od_feature_mask;
>
>               ret = smu_v14_0_2_upload_overdrive_table(smu, od_table);
>               if (ret) {
> --
> 2.43.0

Reply via email to