sleep: default RK3326 to s2idle, preserve ALSA and governor state

Suspend on the R36S family hangs because the shipped sleep.conf orders
SuspendState as "mem standby freeze" and px30 mainline has no working
S3, so the kernel commits to a state it cannot leave. Ship
SuspendState=freeze on RK3326; the power button is the rk817 PMIC
pwrkey, which every board DTS already marks wakeup-source, and logind
already routes HandlePowerKey=suspend through systemd-sleep into this
hook.

Borrow the state discipline from the dArkOS sleep hooks: blank the
backlight before the kernel path runs, snapshot the full mixer state
with alsactl (the rk817 can lose its routing across s2idle and the
existing restore only re-set the master volume), and save/restore the
cpu/gpu/dmc governors. dwc2 reload on resume was already covered by
modules.bad.

Untested on hardware: needs a device check that s2idle enters (screen
and audio off), the pwrkey wakes it, and audio routing plus USB gadget
survive the round trip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Douglas Teles
2026-07-07 18:28:53 -03:00
parent 8aacf5c823
commit 205c5bccb3
2 changed files with 58 additions and 0 deletions
@@ -13,6 +13,13 @@ PKG_TOOLCHAIN="manual"
makeinstall_target() {
mkdir -p ${INSTALL}/usr/config/sleep.conf.d
cp sleep.conf ${INSTALL}/usr/config/sleep.conf.d/sleep.conf
if [ "${DEVICE}" = "RK3326" ]; then
# px30 mainline has no working S3: with the stock order the kernel
# tries "mem" first and the console hangs instead of sleeping. Land
# on s2idle directly; the PMIC pwrkey is a wakeup-source in every
# board DTS so the power button wakes it back up.
sed -i 's/^SuspendState=.*/SuspendState=freeze/' ${INSTALL}/usr/config/sleep.conf.d/sleep.conf
fi
cp modules.bad ${INSTALL}/usr/config
mkdir -p ${INSTALL}/usr/lib/systemd/system-sleep/
@@ -74,6 +74,52 @@ modules() {
esac
}
alsastate() {
# The rk817 codec can lose its routing across s2idle on some boards;
# snapshot the whole mixer state instead of trusting post-resume
# defaults (the volume restore below only covers the master control).
case ${1} in
store)
alsactl store -f /run/archr-sleep-asound.state >${EVENTLOG} 2>&1
;;
restore)
if [ -f /run/archr-sleep-asound.state ]; then
alsactl restore -f /run/archr-sleep-asound.state >${EVENTLOG} 2>&1
fi
;;
esac
}
governors() {
# Preserve cpu/gpu/dmc governors across the sleep in case a resume
# path resets them (the BSP kernels did; cheap insurance on mainline).
local STATE=/run/archr-sleep-governors
case ${1} in
save)
: > ${STATE}
for g in /sys/devices/system/cpu/cpufreq/policy*/scaling_governor \
/sys/class/devfreq/*/governor; do
[ -f "${g}" ] && echo "${g} $(cat ${g})" >> ${STATE}
done
;;
restore)
[ -f ${STATE} ] || return 0
while read -r gpath gvalue; do
[ -f "${gpath}" ] && echo "${gvalue}" > "${gpath}" 2>/dev/null
done < ${STATE}
rm -f ${STATE}
;;
esac
}
backlight_off() {
# Blank before the kernel path runs so a slow suspend never leaves the
# panel lit; the post branch restores the saved user setting below.
for bl in /sys/class/backlight/*/brightness; do
[ -f "${bl}" ] && echo 0 > "${bl}" 2>/dev/null
done
}
quirks() {
for QUIRK in /usr/lib/autostart/quirks/platforms/"${HW_DEVICE}"/sleep.d/${1}/* \
/usr/lib/autostart/quirks/devices/"${QUIRK_DEVICE}"/sleep.d/${1}/*; do
@@ -93,12 +139,17 @@ case $1 in
bluetooth stop
powerstate stop
modules stop
backlight_off
alsastate store
governors save
quirks pre
touch /run/.last_sleep_time
;;
post)
ledcontrol
modules start
governors restore
alsastate restore
powerstate start
headphones start
inputsense start