Repository: sibradzic/amdgpu-clocks
Pull request #66: Updated RDNA 4 support
Author: login: Lawstorant
Created: 2025-03-15T20:55:17Z | Merged: 2025-03-24T10:18:34Z

Support new RDNA4 interfaces introduced in:
https://lore.kernel.org/amd-gfx/20250311213833.870840-1-tomasz.pakula.oficjalny@gmail.com/T/#u

Waiting for patch to hit upstream.

<details>
<summary>Operation log</summary>

```
tomek@komputr ~/repos/amdgpu-clocks (rdna4-updates) $ cat /sys/class/drm/card1/device/pp_od_clk_voltage
OD_SCLK_OFFSET:
0Mhz
OD_MCLK:
0: 97Mhz
1: 1259MHz
OD_VDDGFX_OFFSET:
0mV
OD_RANGE:
SCLK_OFFSET:    -500Mhz       1000Mhz
MCLK:      97Mhz       1500Mhz
VDDGFX_OFFSET:    -200mv          0mv
tomek@komputr ~/repos/amdgpu-clocks (rdna4-updates) $ sudo ./amdgpu-clocks                        
WARNING: /sys/class/drm/card0/device/pp_od_clk_voltage does not exist, skipping!
Writen initial backup states to /tmp/amdgpu-custom-state.card1.initial
Detecting the state values at /sys/class/drm/card1/device/pp_od_clk_voltage:
  SCLK offset: 0Mhz
  MCLK state 0: 97Mhz
  MCLK state 1: 1259MHz
  VDD GFX Offset: 0mV
  Maximum clocks & voltages:
    SCLK offset range:    -500Mhz       1000Mhz
    MCLK clock range:      97Mhz       1500Mhz
    VDDGFX offset range:    -200mv          0mv
  Curent power cap: 304W
Verifying user state values at /etc/default/amdgpu-custom-state.card1:
  SCLK offset: -100Mhz
  VDD GFX Offset: -75mV
  Force power cap to 150W
Committing custom states to /sys/class/drm/card1/device/pp_od_clk_voltage:
  Done
tomek@komputr ~/repos/amdgpu-clocks (rdna4-updates) $ cat /sys/class/drm/card1/device/pp_od_clk_voltage
OD_SCLK_OFFSET:
-100Mhz
OD_MCLK:
0: 97Mhz
1: 1259MHz
OD_VDDGFX_OFFSET:
-75mV
OD_RANGE:
SCLK_OFFSET:    -500Mhz       1000Mhz
MCLK:      97Mhz       1500Mhz
VDDGFX_OFFSET:    -200mv          0mv
```

</details>


Diff:
diff --git a/README.md b/README.md
index e3c68da..3f4da5d 100644
--- a/README.md
+++ b/README.md
@@ -92,11 +92,11 @@ FORCE_POWER_CAP: 99000000
 ```
 
 ### RDNA 4
-With **RDNA 4**, `pp_od_clk_voltage` exposes two `SCLK` offsets but only `1`
-(max `SCLK` offset) can be adjusted. Example of custom power state file:
+With **RDNA4**, `pp_od_clk_voltage` exposes `SCLK_OFFSET` instead of multiple
+`SCLK` states. Example of custom power state file:
 ```shell
 OD_SCLK_OFFSET:
-1: 200Mhz
+200Mhz
 OD_MCLK:
 0: 97MHz
 1: 2519MHz
@@ -139,22 +139,36 @@ and specify custom power states in `/etc/default/amdgpu-custom-states.card0`:
 ```shell
 $ sudo amdgpu-clocks
 
-Detecting the state values at /sys/class/drm/card0/device/pp_od_clk_voltage:
-    SCLK state 0: 700Mhz
-    SCLK state 1: 2539Mhz
-    MCLK state 0: 97Mhz
-    MCLK state 1: 1000MHz
-    VDD GFX Offset: 0mV
-    Maximum clocks & voltages:
-    SCLK clock 3150Mhz
-    MCLK clock 1200Mhz
-    Curent power cap: 130W
-Verifying user state values at /etc/default/amdgpu-custom-state.card0:
-    VDD GFX Offset: -75mV
-    Force performance level to manual
-    Force power cap to 99W
-Committing custom states to /sys/class/drm/card0/device/pp_od_clk_voltage:
-    Done
+Detecting the state values at /sys/class/drm/card1/device/pp_od_clk_voltage:
+  SCLK offset: 0Mhz
+  MCLK state 0: 97Mhz
+  MCLK state 1: 1259MHz
+  VDD GFX Offset: 0mV
+  Value ranges:
+    SCLK offset range:    -500Mhz       1000Mhz
+    MCLK clock range:      97Mhz       1500Mhz
+    VDDGFX offset range:    -200mv          0mv
+  Curent power cap: 304W
+
+Detecting the state values at /sys/class/drm/card1/device/gpu_od/fan_ctrl/fan_zero_rpm_enable:
+  Zero RPM enable: 1
+  Value ranges:
+    Zero RPM enable range: 0 1
+
+Detecting the state values at /sys/class/drm/card1/device/gpu_od/fan_ctrl/fan_zero_rpm_stop_temperature:
+  Zero RPM temperature: 50
+  Value ranges:
+    Zero RPM stop temperature range: 50 110
+
+Verifying user state values at /etc/default/amdgpu-custom-state.card1:
+  SCLK offset: 100MHz
+  VDD GFX Offset: -75mV
+  Force power cap to 250W
+  Zero RPM enable: 0
+  Zero RPM temperature: 65
+
+Committing custom states to /sys/class/drm/card1/device/pp_od_clk_voltage:
+  Done
 ```
 
 The script can also be invoked with specific custom state file prefix (can be
diff --git a/amdgpu-clocks b/amdgpu-clocks
index 288f1dc..fba427e 100755
--- a/amdgpu-clocks
+++ b/amdgpu-clocks
@@ -30,11 +30,22 @@ function fill_sclks() {
 }
 
 function fill_sclk_offset() {
-  echo "  SCLK offset ${1}: ${2}"
+  local offset_value
 
-  # For now, modyfing the [0] index is unsupported on RDNA4
-  if [[ $1 == "0" ]]; then return; fi
-  SCLK[${1}]="s ${1} ${2%*M[Hh]z}"
+  if [[ $1 =~ M[Hh]z ]]; then
+    [[ -z $SCLK_OFFSET_METHOD ]] && SCLK_OFFSET_METHOD="proper"
+    offset_value="$1"
+  else
+    # Legacy method for Linux < 6.14
+    # Modyfing [0] index is unsupported
+    [[ -z $SCLK_OFFSET_METHOD ]] && SCLK_OFFSET_METHOD="legacy"
+    [[ $1 != "1" ]] && return
+    offset_value="$2"
+  fi
+
+  echo "  SCLK offset: $offset_value"
+  SCLK_OFFSET="s ${offset_value%*M[Hh]z}"
+  [[ $SCLK_OFFSET_METHOD == "legacy" ]] && SCLK_OFFSET="s 1 ${offset_value%*M[Hh]z}"
 }
 
 function fill_mclks() {
@@ -100,23 +111,23 @@ function parse_states() {
       "OD_RANGE:")
         echo "  Value ranges:";;
       "SCLK: "*)
-        echo "    SCLK clock ${LINE##* }"
+        echo "    SCLK clock range: ${LINE#* }"
         MAX_SCLK=${LINE##* }
         MAX_SCLK=${MAX_SCLK%*M[Hh]z}
         ;;
       "SCLK_OFFSET:"*)
-        echo "    SCLK offset ${LINE##* }"
-        MAX_SCLK=${LINE##* }
-        MAX_SCLK=${MAX_SCLK%*M[Hh]z}
+        echo "    SCLK offset range: ${LINE#* }"
+        MAX_SCLK_OFFSET=${LINE##* }
+        MAX_SCLK_OFFSET=${MAX_SCLK%*M[Hh]z}
         ;;
       "MCLK: "*)
-        echo "    MCLK clock ${LINE##* }"
+        echo "    MCLK clock range: ${LINE#* }"
         MAX_MCLK=${LINE##* }
         MAX_MCLK=${MAX_MCLK%*M[Hh]z}
         ;;
       "VDDGFX_OFFSET: "*)
         echo "    VDDGFX offset range: ${LINE#*: }"
-        OFFSET_RANGE=${LINE#*: }
+        VOLTAGE_OFFSET_RANGE=${LINE#*: }
         ;;
       "VDDC: "*)
         echo "    VDDC voltage ${LINE##* }"
@@ -126,6 +137,9 @@ function parse_states() {
       [0-9]": "*)
         $state_fill_func ${LINE%%:*} ${LINE#* }
         ;;
+      [-0-9]*"M"[Hh]"z")
+        $state_fill_func ${LINE%%:*} ${LINE#* }
+        ;;
       [-0-9]*"mV")
         $offset_fill_func ${LINE%%:*} ${LINE#* }
         ;;
@@ -190,8 +204,12 @@ function set_custom_states() {
     sleep 0.25
   fi
   for CSTATE in "${SCLK[@]}"; do
+    [[ -z $CSTATE ]] && continue
     echo "${CSTATE}" > "${SYS_PP_OD_CLK}"
   done
+  if [ "${SCLK_OFFSET}" ]; then
+    echo "${SCLK_OFFSET}" > "${SYS_PP_OD_CLK}"
+  fi
   for MSTATE in "${MCLK[@]}"; do
     OLD_MSTATE=${DETECTED_MCLK[$((${MSTATE:3:1}))]}
     if [ "${MSTATE}" != "${OLD_MSTATE}" ]; then
@@ -273,9 +291,9 @@ function restore_states() {
 }
 
 function clear_vars() {
-    unset SCLK MCLK
-    unset VDDC_CURVE VDDGFX_OFFSET
-    unset sys_device sys_hwmons
+  unset SCLK MCLK SCLK_OFFSET SCLK_OFFSET_METHOD
+  unset VDDC_CURVE VDDGFX_OFFSET
+  unset sys_device sys_hwmons
 }
 
 check_ppfeaturemask


Reviews:
user: login: sibradzic
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T04:20:47Z
user: login: Lawstorant
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T08:49:51Z
user: login: sibradzic
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T10:49:57Z
user: login: Lawstorant
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T11:24:34Z
user: login: sibradzic
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T18:29:24Z
user: login: sibradzic
body: Sorry, but the "Script code refactor " commit does not belong to this PR. Can you please move it to separate PR, after "RDNA4" support is merged?
state: CHANGES_REQUESTED
author_association: OWNER
submitted_at: 2025-03-22T06:40:47Z
user: login: sibradzic
state: APPROVED
author_association: OWNER
submitted_at: 2025-03-22T18:08:52Z

Review comments:
diff_hunk: @@ -87,32 +84,21 @@ function parse_states() {
       "OD_RANGE:")
         echo "  Maximum clocks & voltages:";;
       "SCLK: "*)
-        echo "    SCLK clock ${LINE##* }"
-        MAX_SCLK=${LINE##* }
-        MAX_SCLK=${MAX_SCLK%*M[Hh]z}
-        ;;
+        echo "    SCLK clock range: ${LINE#* }";;
path: amdgpu-clocks
user: login: sibradzic
body: This may work on RDNA4, but will not for other cards. Older cards (sometimes it also depends on kernel version) require that `%*M[Hh]z` part, so let's try to do it the ugly old way. Same for MCLK, VDDGFX_OFFSET & VDDC. SLCK_OFFSET should be fine.
created_at: 2025-03-16T04:20:47Z
author_association: OWNER
diff_hunk: @@ -87,32 +84,21 @@ function parse_states() {
       "OD_RANGE:")
         echo "  Maximum clocks & voltages:";;
       "SCLK: "*)
-        echo "    SCLK clock ${LINE##* }"
-        MAX_SCLK=${LINE##* }
-        MAX_SCLK=${MAX_SCLK%*M[Hh]z}
-        ;;
+        echo "    SCLK clock range: ${LINE#* }";;
path: amdgpu-clocks
user: login: Lawstorant
body: Removed lines were noops. These variables were assigned but never used anywhere, that's why I removed them.
created_at: 2025-03-16T08:49:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,32 +84,21 @@ function parse_states() {
       "OD_RANGE:")
         echo "  Maximum clocks & voltages:";;
       "SCLK: "*)
-        echo "    SCLK clock ${LINE##* }"
-        MAX_SCLK=${LINE##* }
-        MAX_SCLK=${MAX_SCLK%*M[Hh]z}
-        ;;
+        echo "    SCLK clock range: ${LINE#* }";;
path: amdgpu-clocks
user: login: sibradzic
body: They are used, but only for reporting limits. They could be used to check allowed ranges in the future. I wouldn't want to remove them, but if you insist, let's open that discussion in a separate PR :bow: 
created_at: 2025-03-16T10:49:57Z
author_association: OWNER
diff_hunk: @@ -87,32 +84,21 @@ function parse_states() {
       "OD_RANGE:")
         echo "  Maximum clocks & voltages:";;
       "SCLK: "*)
-        echo "    SCLK clock ${LINE##* }"
-        MAX_SCLK=${LINE##* }
-        MAX_SCLK=${MAX_SCLK%*M[Hh]z}
-        ;;
+        echo "    SCLK clock range: ${LINE#* }";;
path: amdgpu-clocks
user: login: Lawstorant
body: I'll revert it for now if you want to keep the max stored, but if you do a reference search, you'll see that these symbols are only ever assigned, not used anywhere else.
created_at: 2025-03-16T11:24:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,32 +84,21 @@ function parse_states() {
       "OD_RANGE:")
         echo "  Maximum clocks & voltages:";;
       "SCLK: "*)
-        echo "    SCLK clock ${LINE##* }"
-        MAX_SCLK=${LINE##* }
-        MAX_SCLK=${MAX_SCLK%*M[Hh]z}
-        ;;
+        echo "    SCLK clock range: ${LINE#* }";;
path: amdgpu-clocks
user: login: sibradzic
body: Yes, I am aware, but will keep them for now.
created_at: 2025-03-16T18:29:24Z
author_association: OWNER

PR comments:
user: login: Lawstorant
created_at: 2025-03-19T23:35:08Z
body: @sibradzic I made it so `amdgpu-clocks` can dynamically handle both old, and new way of detecting/setting the `SCLK_OFFSET`. This way, even if someone will be unlocky enough to be stuck with just `6.13`, it will work.

<details>
<summary>Logs (test echo not included in the PR)</summary>

Legacy offset detected:

```
Detecting the state values at /sys/class/drm/card1/device/pp_od_clk_voltage:
  SCLK offset: 1000Mhz
  MCLK state 0: 97Mhz
  MCLK state 1: 1259MHz
  VDD GFX Offset: -75mV
  Value ranges:
    SCLK offset range:    -500Mhz       1000Mhz
    MCLK clock range:      97Mhz       1500Mhz
    VDDGFX offset range:    -200mv          0mv
  Curent power cap: 250W

Verifying user state values at /etc/default/amdgpu-custom-state.pci:0000:0f:00.0:
  SCLK offset: 25MHz
  VDD GFX Offset: -75mV
  Force power cap to 250W
  Zero RPM enable: 1
  Zero RPM temperature: 55

Committing custom states to /sys/class/drm/card1/device/pp_od_clk_voltage:
s 1 25
  Done
```

New offset detected:

```
Detecting the state values at /sys/class/drm/card1/device/pp_od_clk_voltage:
  SCLK offset: 0Mhz
  MCLK state 0: 97Mhz
  MCLK state 1: 1259MHz
  VDD GFX Offset: 0mV
  Value ranges:
    SCLK offset range:    -500Mhz       1000Mhz
    MCLK clock range:      97Mhz       1500Mhz
    VDDGFX offset range:    -200mv          0mv
  Curent power cap: 304W

Detecting the state values at /sys/class/drm/card1/device/gpu_od/fan_ctrl/fan_zero_rpm_enable:
  Zero RPM enable: 1
  Value ranges:
    Zero RPM enable range: 0 1

Detecting the state values at /sys/class/drm/card1/device/gpu_od/fan_ctrl/fan_zero_rpm_stop_temperature:
  Zero RPM temperature: 50
  Value ranges:
    Zero RPM stop temperature range: 50 110

Verifying user state values at /etc/default/amdgpu-custom-state.pci:0000:0f:00.0:
  SCLK offset: 25MHz
  VDD GFX Offset: -75mV
  Force power cap to 250W
  Zero RPM enable: 1
  Zero RPM temperature: 55

Committing custom states to /sys/class/drm/card1/device/pp_od_clk_voltage:
s 25
  Done
```

</details>
author_association: CONTRIBUTOR
user: login: Lawstorant
created_at: 2025-03-21T12:33:47Z
body: @sibradzic SCLK handling fixes have been merged ahead of 6.14 release this sunday: https://github.com/torvalds/linux/commit/d9d4cb224e4140f51847642aa5a4a5c3eb998af0
author_association: CONTRIBUTOR
user: login: sibradzic
created_at: 2025-03-22T06:32:44Z
body: Will merge as soon as kernel is released. Can you please drop the comment in readme about 6.14 requirement?
author_association: OWNER
user: login: Lawstorant
created_at: 2025-03-22T14:59:22Z
body: > Will merge as soon as kernel is released. Can you please drop the comment in readme about 6.14 requirement?

There's no requirement. With this PR, it can handle both old and new offset settings. Both in the custom state file and in the `pp_od_clk_voltage`.
author_association: CONTRIBUTOR