Skip to content

Thermal Management User Guide


REVISION HISTORY

Revision No.
Description
Date
1.0
  • Initial release
  • 10/08/2026

    1. Overview

    The SGS thermal management subsystem is the software layer that keeps the SoC inside its safe operating temperature envelope. It is built on top of the standard Linux thermal framework and reuses the on-chip Tsensor as the temperature source.

    The default policy is a two-state hysteresis loop (described in section 3): the CPU is allowed to run across the full DVFS range below 115°C, and is forced to the lowest OPP as soon as the temperature crosses 115°C. Full DVFS range is restored only after the temperature falls back below 100°C.

    The subsystem ties three Linux thermal concepts together:

    • A thermal zone (soc-thermal) bound to the SGS Tsensor.
    • A set of trip points (passive throttle point + critical shutdown point).
    • Cooling devices (CPU DVFS and the IPU accelerator) driven by a custom SGS governor named sgs_highest whenever a trip is crossed.

    2. Keyword

    • Thermal zone

      A logical object that aggregates one temperature sensor, a set of trip points and the cooling devices bound to those trip points. Defined in <Kernel>/Documentation/devicetree/bindings/thermal/thermal-zones.yaml.

    • Trip point

      A temperature threshold inside a thermal zone. Each trip has a temperature, a hysteresis and a type (passive, critical, hot, ...).

    • Cooling device

      A device that can dissipate heat when triggered. On mhera two cooling devices are bound to the SoC thermal zone: CPU DVFS (throttled by lowering the CPU frequency and voltage) and the IPU accelerator (throttled by lowering the IPU clock). The IPU node is labelled dla in the DTS, so cooling-device references use <&dla ...>.

    • Governor

      The policy module that decides which cooling state a cooling device should be put into when a trip is crossed. The kernel ships several governors (step_wise, power_allocator, user_space); SGS adds one more, sgs_highest, which is selected as the default on mhera.

    • sgs_highest governor

      A two-point governor contributed by SGS: it pushes the cooling device to its upper bound (instance->upper) the moment the trip temperature is reached, and releases it back to state 0 only after the temperature drops below trip_temp - hysteresis. See section 3.1 for details.

    3. FUNCTION DESCRIPTION

    3.1. Two-State Throttling Policy

    The diagram below illustrates the default mhera thermal policy. The threshold values come from arch/arm64/boot/dts/sgs/mhera.dtsi (target trip = 115000 m°C, hysteresis = 15000 m°C):

    Full DVFS Range                    Lowest Frequency
    (No Throttling)                      (Throttled)
    +--------------+                   +--------------+
    |              |  Temp >= 115 C    |              |
    |   CPU runs   | ----------------> |   CPU runs   |
    |  at any freq |                   |  at min freq |
    |              |  Temp <= 100 C    |              |
    |              | <---------------- |              |
    +--------------+                   +--------------+
         (State 0)                         (State MAX)
    

    The wide 15 °C hysteresis was chosen on purpose to avoid oscillation around the trip point — a narrower 10 °C value caused the CPU to bounce in and out of the throttled state as the temperature hovered near 115 °C. The three phases of the loop are:

    • Below 115 °C the cooling device stays at state 0 — every OPP in the cpufreq table is available.
    • At 115 °C the sgs_highest governor jumps the cooling device to its upper bound (instance->upper). For CPU DVFS, upper corresponds to the lowest frequency in the OPP table, so the CPU is pinned to its lowest OPP.
    • The CPU stays at the lowest OPP until the temperature drops to 115 - 15 = 100 °C. Only then does the governor release the cooling device back to state 0.

    3.2. Critical Trip

    A separate critical trip at 125 °C triggers the kernel's thermal shutdown path (thermal_zone_device_check() → orderly poweroff). This trip has hysteresis = <0> because it is intended as a hard stop, not a recovery point.

    3.3. In-Kernel Temperature API

    The Tsensor driver exports a helper that any in-tree kernel module can call to read the current SoC temperature:

    int sgs_get_temp(void);
    

    It internally looks up the soc-thermal zone, reads its temperature and returns it in degrees Celsius (integer). See section 7.1.

    4. HARDWARE CONNECTION INTRODUCTION

    N/A. Thermal management reuses the on-chip Tsensor; no external pins or components are involved.

    5. Uboot USAGE INTRODUCTION

    Not supported. Thermal management runs entirely in the kernel.

    6. Kernel USAGE INTRODUCTION

    6.1. Configure Kernel Config

    The relevant options live under two menus in menuconfig:

    Device Drivers  --->
        [*] Thermal drivers  --->
            [*] Enable writable trip points         (THERMAL_WRITABLE_TRIPS)
            [*] Generic cpu cooling support         (CPU_THERMAL)
            ()  Default Thermal governor            ---> sgs_highest
                                                        (THERMAL_DEFAULT_GOV_SGS_HIGHEST)
            [*]   sgs_highest                       (THERMAL_GOV_SGS_HIGHEST)
            [*]   step_wise                         (THERMAL_GOV_STEP_WISE)
            [*]   User_space thermal governor       (THERMAL_GOV_USER_SPACE)
        [*] Sgs SoC platform drivers  --->
            <*>   SGS T-sensor driver               (SGS_TSENSOR)
    

    In every released mhera defconfig the following lines are set explicitly (see arch/arm64/configs/mhera_ssm004a_*_defconfig):

    CONFIG_THERMAL_DEFAULT_GOV_SGS_HIGHEST=y
    CONFIG_THERMAL_GOV_STEP_WISE=y
    CONFIG_THERMAL_GOV_SGS_HIGHEST=y
    CONFIG_CPU_THERMAL=y
    CONFIG_THERMAL_WRITABLE_TRIPS=y
    CONFIG_SGS_TSENSOR=y
    

    CONFIG_THERMAL_DEFAULT_GOV_SGS_HIGHEST causes DEFAULT_THERMAL_GOVERNOR to expand to "sgs_highest" in drivers/thermal/thermal_core.h. Without it the kernel falls back to the upstream default (step_wise), which does not implement the policy described in section 3.1.

    6.2. Configure DTS

    Thermal management needs two pieces of device tree: the sgs_thermal sensor node (already described in the Tsensor guide) and a thermal-zones block.

    6.2.1. CPU and IPU nodes — advertise as cooling devices

    Add #cooling-cells = <2> to the CPU node so the cpufreq driver can register it as a cooling device:

    cpu0: cpu@0 {
        compatible = "arm,cortex-a53";
        clocks = <&CLK_cpu_pll>, <&CLK_cpu_pll>;
        operating-points-v2 = <&cpu0_opp_table>;
        #cooling-cells = <2>;          /* required for CPU_THERMAL */
        reg = <0x000>;
    };
    

    The sgs cpufreq driver sets .flags = CPUFREQ_IS_COOLING_DEV on its cpufreq_driver structure, so the kernel will auto-register the CPU as a cooling device when this cell is present.

    The IPU accelerator (DTS node labelled dla, compatible = "sgs,dla") is registered as a second cooling device the same way — give the node a label and add #cooling-cells = <2>:

    dla: dla {
        compatible = "sgs,dla";
        interrupts = <GIC_SPI INT_IRQ_IPU2TOP0 IRQ_TYPE_LEVEL_HIGH>,
                     <GIC_SPI INT_IRQ_IPU2TOP1 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&CLK_ipupll_clk>;
        status = "ok";
        #cooling-cells = <2>;          /* required for CPU_THERMAL */
    };
    

    When a trip is crossed the sgs_highest governor pushes every bound cooling device to its upper bound, so the CPU is pinned to its lowest OPP and the IPU is throttled at the same time.

    6.2.2. thermal-zones block

    thermal_zones: thermal-zones {
        soc_thermal: soc-thermal {
            polling-delay-passive = <100>;
            polling-delay = <1000>;
            thermal-sensors = <&sgs_thermal 0>;
    
            trips {
                target: trip-point-0 {
                    temperature = <115000>;   /* mC */
                    hysteresis = <15000>;     /* mC, restore at 100 C */
                    type = "passive";
                };
                soc_crit: soc-crit {
                    temperature = <125000>;   /* mC */
                    hysteresis = <0>;
                    type = "critical";
                };
            };
    
            cooling-maps {
                map0 {
                    trip = <&target>;
                    cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
                                     <&dla 0 1>;
                };
            };
        };
    };
    

    The attributes used by the thermal driver:

    Attributes Description
    thermal-sensors Phandle of the temperature sensor, here the sgs_thermal node.
    polling-delay Polling interval (ms) when no passive trip is active.
    polling-delay-passive Polling interval (ms) once a passive trip is active.
    trips/temperature Trip threshold in millicelsius.
    trips/hysteresis Cooling-down delta before the governor releases the cooling device.
    trips/type passive for the throttle point, critical for the shutdown point.
    cooling-maps Bindings between trips and cooling devices. Each entry is <&dev min_state max_state>; THERMAL_NO_LIMIT lets the governor pick any state the device exposes. The CPU uses THERMAL_NO_LIMIT THERMAL_NO_LIMIT (full DVFS range), the IPU uses 0 1 (two states: 0 = full rate, 1 = throttled).

    The actual mhera.dtsi guards trip-point-0 and the cooling-maps block behind #ifdef CONFIG_CPU_THERMAL, so they vanish from the compiled DTB if CONFIG_CPU_THERMAL is disabled — only the critical trip remains. The critical trip is always present and is the last line of defence.

    For the full thermal-zone binding syntax, refer to: <Kernel>/Documentation/devicetree/bindings/thermal/thermal-zones.yaml

    6.3. Configure Padmux

    N/A.

    6.4. Module Usage Introduction

    6.4.1. SYSFS — read the temperature

    The Linux thermal framework exposes one sysfs node per thermal zone. The SoC zone is named soc-thermal:

    # Read the current temperature (millicelsius)
    cat /sys/class/thermal/thermal_zone0/temp
    
    # Read the type string
    cat /sys/class/thermal/thermal_zone0/type
    # -> soc-thermal
    
    # List the cooling devices and their current state
    cat /sys/class/thermal/cooling_device0/cur_state
    cat /sys/class/thermal/cooling_device0/max_state
    

    Each trip point also has its own sysfs attribute, e.g. /sys/class/thermal/thermal_zone0/trip_point_0_temp, /sys/class/thermal/thermal_zone0/trip_point_0_type. They are writable at runtime because CONFIG_THERMAL_WRITABLE_TRIPS=y is enabled.

    6.4.2. SYSFS — force a temperature for debugging

    The Tsensor driver registers a test_temp write-only attribute on the thermal-zone device. Writing a non-zero value (in millicelsius) makes the driver report that value instead of the real Tsensor reading, so the whole thermal policy can be exercised on a board that never actually reaches 115 °C:

    # Force the SoC to report 120 C — the governor should pin the CPU to min freq
    echo 120000 > /sys/class/thermal/thermal_zone0/test_temp
    
    # Restore real Tsensor reading
    echo 0 > /sys/class/thermal/thermal_zone0/test_temp
    

    test_temp is intended for EVB bring-up and automated tests; it has no effect on production deployments.

    6.4.3. Switch governor at runtime

    The governor can be changed on a per-zone basis through sysfs. This is useful when comparing sgs_highest against step_wise on the same board:

    # Available governors
    cat /sys/class/thermal/thermal_zone0/available_policies
    
    # Current governor
    cat /sys/class/thermal/thermal_zone0/policy
    
    # Switch to step_wise
    echo step_wise > /sys/class/thermal/thermal_zone0/policy
    
    # Switch back to sgs_highest
    echo sgs_highest > /sys/class/thermal/thermal_zone0/policy
    

    6.5. Sample Code

    6.5.1. Read temperature from user space

    awk '{printf "SoC temp: %.1f C\n", $1/1000}' \
        /sys/class/thermal/thermal_zone0/temp
    

    6.5.2. Read temperature from a kernel module

    #include <linux/thermal.h>
    
    int soc_temp_degC(void)
    {
        struct thermal_zone_device *tzd;
        int temp, ret;
    
        tzd = thermal_zone_get_zone_by_name("soc-thermal");
        if (IS_ERR(tzd))
            return -ENODEV;
    
        ret = thermal_zone_get_temp(tzd, &temp);
        if (ret)
            return ret;
    
        return temp / 1000;   /* millicelsius -> degrees Celsius */
    }
    

    Or simply call the helper exported by the Tsensor driver:

    extern int sgs_get_temp(void);   /* returns degrees Celsius */
    
    int now = sgs_get_temp();
    

    7. API Reference

    The in-kernel API is declared in drivers/sgs_common/tsensor/drv/src/drv_tsensor_lnx.c and is available to any module that depends on the SGS Tsensor driver.

    7.1. sgs_get_temp

    • Function

      Read the current SoC temperature from the soc-thermal zone.

    • Syntax

      int sgs_get_temp(void)
      
    • Parameters

      Parameter Description
      N/A N/A
    • Return value

      Return value Description
      N >= 0 Current SoC temperature in degrees Celsius.
      0 Returned when the soc-thermal zone cannot be found or thermal_zone_get_temp fails.

    8. FAQ

    Q1: Why is sgs_highest needed at all? The kernel already has step_wise.

    step_wise walks the cooling states one step at a time on every polling tick. To force the CPU to its lowest OPP immediately, the cooling-device binding would need to hardcode min_state/max_state, but mhera uses a single unified OPP table whose effective length is trimmed at runtime by cpufreq_cfg depending on the chip variant. Hardcoded bounds in the DTS cannot track that, so a governor that always jumps straight to instance->upper is required.

    Q2: Why was the hysteresis widened from 10 °C to 15 °C?

    A 10 °C hysteresis (restore at 105 °C) caused the CPU to bounce between the lowest OPP and the full OPP table when the temperature hovered near the threshold, producing noisy thermal events and visible frequency jitter. 15 °C (restored at 100 °C) was found to be stable on the EVB.

    Q3: What happens if CONFIG_CPU_THERMAL is disabled?

    The target passive trip and the cooling-maps block are compiled out of the DTB. Only the critical trip at 125 °C remains, so the SoC will still shut down on over-temperature but no DVFS throttling will occur. The default mhera defconfigs all enable CONFIG_CPU_THERMAL.

    Q4: The board never reaches 115 °C. How do I verify the policy on the EVB?

    Use the test_temp debug node (section 6.4.2) to inject a fake temperature, then watch /sys/class/thermal/cooling_device0/cur_state and /sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq to confirm the cooling state and CPU frequency change as expected.