blob: 0ebe11f1fe58445860f359af319e8d0e703a0812 [file] [log] [blame]
#!/bin/bash
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Quick hack to monitor thermals on Exynos based platforms. Since we only have
# passive cooling, the only thing we can do is limit CPU temp.
#
# TODO: validate readings from hwmon sensors by comparing to each other.
# We should ignore readings with more than 10C differences from peers.
PROG=$(basename "$0")
PLATFORM=$(mosys platform name)
debug=0
if [[ "$1x" == '-dx' ]]; then
debug=1
fi
# if PLATFORM is empty, try again
for i in $(seq 5); do
if [[ -n "${PLATFORM}" ]]; then
break
fi
sleep 1
logger -t "${PROG}" "Unable to get platform name, retry ${i} of 5"
PLATFORM=$(mosys platform name)
done
# Log the platform
logger -t "${PROG}" "Platform ${PLATFORM}"
# Use the same thermal settings for Skate and Spring
if [[ "${PLATFORM}" == "Skate" ]]; then
PLATFORM="Spring"
fi
# cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies
# 1700000 1600000 1500000 ...
if [[ "${PLATFORM}" == "Pit" ]]; then
PIT_MAX_FREQ=$(cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq)
if [[ ${PIT_MAX_FREQ} -gt 1800000 ]]; then
EXYNOS5_CPU_FREQ=(1900000 1600000 1400000 1200000 1100000 900000 800000
650000 300000)
else
EXYNOS5_CPU_FREQ=(1800000 1600000 1400000 1200000 1100000 900000 800000
650000 300000)
fi
# Ares has 5 tmu sensors for cpu and gpu
cpu_tpath="/sys/class/thermal/thermal_zone[0-4]"
elif [[ "${PLATFORM}" == "Pi" ]]; then
EXYNOS5_CPU_FREQ=(2000000 1800000 1400000 1200000 1100000 900000 800000
650000 300000)
# Ares2 has also 5 tmu sensors for cpu and gpu
cpu_tpath="/sys/class/thermal/thermal_zone[0-4]"
else
EXYNOS5_CPU_FREQ=(1700000 1600000 1500000 1400000 1300000 1200000 1100000
1000000 900000 800000 700000 600000 500000 400000 300000 200000)
cpu_tpath="/sys/class/thermal/thermal_zone0"
fi
# CPU temperature threshold we start limiting CPU Freq
# TODO(crosbug.com/p/17658) HACK: remove once characterized
if [[ "${PLATFORM}" == "Spring" ]]; then
: $(( t0 = $(cat "${cpu_tpath}"/trip_point_0_temp) / 1000 - 1 ))
: $(( t1 = $(cat "${cpu_tpath}"/trip_point_1_temp) / 1000 - 1 ))
CPU_TEMP_MAP=(${t0} ${t0} ${t0} ${t0} ${t0} ${t0} ${t0} ${t0} ${t0} ${t0}
${t1})
HWMON_TEMP_MAP=(${t0} ${t0} ${t0} ${t0} ${t0} ${t0} ${t0} ${t0} ${t0} ${t1})
elif [[ "${PLATFORM}" == "Pit" ]]; then
# Just relying on thermistors under 80 dgree C
CPU_TEMP_MAP=(80 81 82 83 84 85 86 90 100)
# 50 -> 1.6Ghz(A15), 52 -> 1.2 Ghz(A15)
HWMON_TEMP_MAP=(49 50 51 52 53 54 70 80 90)
elif [[ "${PLATFORM}" == "Pi" ]]; then
# Just relying on thermistors under 80 dgree C
CPU_TEMP_MAP=(80 81 82 83 84 85 86 90 100)
# 50 -> 1.8Ghz(A15), 52 -> 1.2 Ghz(A15)
HWMON_TEMP_MAP=(49 50 51 52 53 54 70 80 90)
else
# 63 -> 1.4Ghz, 69 -> 1.1 Ghz, 75 -> 800Mhz
CPU_TEMP_MAP=(60 61 62 63 65 67 68 69 71 73 75)
# 52 -> 1.4Ghz, 60->1.1Ghz, 65->800Mhz
HWMON_TEMP_MAP=(49 50 51 52 55 58 60 62 64 65)
fi
# We don't quote cpu_tpath because it might have wildcards that
# we want to expand into the array.
DAISY_CPU_TEMP=(${cpu_tpath}/temp)
#######################################
# Find all hwmon thermal sensors.
#
# It's OK if there are none.
#
# Globals:
# HWMON_TEMP_SENSOR
# Arguments:
# None
# Returns:
# None
#######################################
find_hwmon_sensors() {
local hwmon_dir
local sensor
HWMON_TEMP_SENSOR=()
for hwmon_dir in /sys/class/hwmon/hwmon*/device; do
for sensor in "${hwmon_dir}"/temp*_input; do
if [[ -r "${sensor}" ]]; then
HWMON_TEMP_SENSOR+=( "${sensor}" )
fi
done
done
}
read_temp() {
local sensor="$1"
local t
if [[ -r "${sensor}" ]]; then
# treat $1 as numeric and convert to C
local raw
raw=$(cat "${sensor}" 2> /dev/null)
if [[ -z "${raw}" ]]; then
return 1
fi
: $(( t = raw / 1000 ))
# valid CPU range is 25 to 125C. Give hwmon sensors more range.
if [[ ${t} -lt 15 || ${t} -gt 140 ]]; then
# do nothing - ignore the reading
logger -t "${PROG}" "ERROR: temp ${t} out of range"
return 1
fi
# WARNING: if valid temps are ever outside [0, 255] this return will not
# work like you think...
return ${t}
fi
logger -t "${PROG}" "ERROR: could not read temp from ${sensor}"
# sleep so script isn't respawned so quickly and spew
sleep 10
exit 1
}
lookup_freq_idx() {
local t=$1
shift
local temp_map=("$@")
local i=0
local n=${#temp_map[@]}
while [[ ${i} -lt ${n} ]]; do
if [[ ${t} -le ${temp_map[i]} ]]; then
return ${i}
fi
: $(( i += 1 ))
done
# we ran off the end of the map. Use slowest speed in that map.
logger -t "${PROG}" "ERROR: temp ${t} not in temp_map"
: $(( i = n - 1 ))
return ${i}
}
# Thermal loop steps
set_max_cpu_freq() {
local max_freq=$1
local cpu
for cpu in /sys/devices/system/cpu/cpu?/cpufreq; do
echo "${max_freq}" > "${cpu}/scaling_max_freq"
done
}
# Only update cpu Freq if we need to change.
last_cpu_freq=0
find_hwmon_sensors
# Get frequency throttling set by the firmware to limit power draw
power_cap=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq)
logger -t "${PROG}" "CPU max freq set to $((${power_cap} / 1000)) Mhz at boot"
# Print power info on first pass, then every other 4 passes.
power_info_pass=4
while true; do
max_cpu_freq=${EXYNOS5_CPU_FREQ[0]}
# read the list of temp sensors
cpu_temps=()
for sensor in "${DAISY_CPU_TEMP[@]}"; do
read_temp "${sensor}"
cpu_temp=$?
lookup_freq_idx ${cpu_temp} "${CPU_TEMP_MAP[@]}"
f=$?
cpu_freq=${EXYNOS5_CPU_FREQ[f]}
if [[ ${cpu_freq} -gt 0 && ${cpu_freq} -lt ${max_cpu_freq} ]]; then
max_cpu_freq=${cpu_freq}
fi
# record temps for (DEBUG and) validation later
cpu_temps+=(${cpu_temp})
done
temps=()
if [[ ${#HWMON_TEMP_SENSOR[@]} -gt 0 ]]; then
for sensor in "${HWMON_TEMP_SENSOR[@]}"; do
read_temp "${sensor}"
temp=$?
# record temps for (DEBUG and) validation later
temps+=(${temp})
done
# TODO validate hwmon sensor readings.
# we should reject anything that is more than 5C off from all others.
max_temp=${temps[0]}
for k in "${temps[@]}"; do
if [[ ${max_temp} -lt ${k} ]]; then
max_temp=${k}
fi
done
lookup_freq_idx ${max_temp} "${HWMON_TEMP_MAP[@]}"
f=$?
therm_cpu_freq=${EXYNOS5_CPU_FREQ[f]}
# we have a valid reading and it's lower than others
if [[ ${therm_cpu_freq} -gt 0 &&
${therm_cpu_freq} -lt ${max_cpu_freq} ]]; then
max_cpu_freq=${therm_cpu_freq}
fi
fi
# Handle the power cap if the battery is too low.
if [[ "${PLATFORM}" == "Spring" && ${power_cap} -lt ${EXYNOS5_CPU_FREQ[0]} ]]
then
power_supply="/sys/class/power_supply/sbs-*-000b"
energy_now=$(cat ${power_supply}/energy_now)
energy_full=$(cat ${power_supply}/energy_full)
battery_percent=$(( energy_now * 100 / energy_full ))
# if we have charged, restore the full CPU frequency range
if [[ ${battery_percent} -gt 5 ]]; then
power_cap=${EXYNOS5_CPU_FREQ[0]}
logger -t "${PROG}" "Freq cap reset to $((${power_cap} / 1000)) Mhz"
fi
# force to stay below the cap set to limit total power draw
if [[ ${max_cpu_freq} -gt ${power_cap} ]]; then
max_cpu_freq=${power_cap}
fi
fi
if [[ debug -gt 0 ]]; then
echo $(date +"%H:%M:%S") , \
$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq), \
${max_cpu_freq}, ${cpu_temp}, ${temps[@]}
fi
if [[ ${last_cpu_freq} -ne ${max_cpu_freq} ]]; then
last_cpu_freq=${max_cpu_freq}
logger -t "${PROG}" "Max CPU Freq set to ${max_cpu_freq} \
(Celsius: ${cpu_temps[*]} / ${temps[*]})"
set_max_cpu_freq ${max_cpu_freq}
fi
if [[ "${PLATFORM}" == "Spring" ]]; then
# Report charger type.
if [[ "${power_info_pass}" == "4" ]]; then
power_info_pass=0
# Charger type is 4-byte hex, but metric_client accepts only
# decimal. Sparse histograms use 32-bit bucket indices, but
# the 64-bit values produced by awk are truncated correctly.
charger_type=$(($(ectool powerinfo | awk \
'/USB Device Type:/ { print $4; }')))
if [[ -n "${charger_type}" ]]; then
metrics_client -s Platform.SpringChargerType ${charger_type}
fi
fi
: $(( power_info_pass += 1 ))
fi
sleep 15
done