reset sound after suspend to memory (deep sleep)
#1
After a suspend from memory, sound does not work.

On my machine (Manjaro ARM XFCE 20.06 Kernel 5.7), the sound driver snd_soc_es8316 needs to reset the sound chip. The problem is the kernel is preventing unloading and reloading of the sound driver snd_soc_es8316.


My first attempt was initializing the sound chip from the shell mimicking what the es8316 driver is doing in its probe function:

Code:
# modprobe  i2c-dev
# i2cset -y -f 1 0x11 0x00 0x3f
# i2cset -y -f 1 0x11 0x00 0x80
# i2cset -y -f 1 0x11 0x0c 0xff
# i2cset -y -f 1 0x11 0x03 0x32

I got some static noise making me believe I talked to the right device Big Grin It has not helped.


The es8316 driver can't be un- and reloaded but un- and rebound. Execute this line after returning from suspend:

Code:
$ sudo tee /sys/bus/i2c/drivers/es8316/{un,}bind <<< 1-0011


It's quite easy to crash the kernel. I suspect the sound driver should not be accessed in the split-second between its unbinding and binding. So far, I have only seen kernel crashes when executing the unbind/bind command while playing music. I have not seen any kernel crashes otherwise. You never know what happens on a modern Linux in the background, so YMMV.

I have some vague ideas for a proper solution. Will not happen any time soon. Hope others will step in.
  Reply
#2
I've been able to reset audio (on Ubuntu 20.04, mainline 5.7 kernel) after a deep sleep with the following:


Code:
pulseaudio -k && sudo alsa force-reload

I believe it accomplishes similar things.  On the other hand, it doesn't work if something is trying to play music, but neither does it crash the kernel.
  Reply
#3
Hello Syonyk

(06-16-2020, 09:05 AM)Syonyk Wrote: I've been able to reset audio (on Ubuntu 20.04, mainline 5.7 kernel) after a deep sleep with the following:


Code:
pulseaudio -k && sudo alsa force-reload

I believe it accomplishes similar things.  On the other hand, it doesn't work if something is trying to play music, but neither does it crash the kernel.

Ubuntu's alsa script is unloading and then reloading all sound related drivers. That does not work for me on stock Manjaro 20.06.

Great that the alsa script works for you. I assume ubuntu uses systemd , so you should not run pulseaudio -k, but something along these lines

Code:
$ systemctl --user stop pulseaudio.socket
$ sudo alsa force-reload
$ systemctl --user start pulseaudio


You can check with lsof if the sound device is in use (maybe there is a better way?)

Code:
$ lsof /dev/snd/*
COMMAND    PID    USER  FD  TYPE DEVICE SIZE/OFF  NODE NAME
pulseaudi 73457 manjaro  mem    CHR  116,2          478461 /dev/snd/pcmC0D0p
pulseaudi 73457 manjaro    20u  CHR  116,4      0t0 478465 /dev/snd/controlC0
pulseaudi 73457 manjaro    21r  CHR 116,33      0t0    47 /dev/snd/timer
pulseaudi 73457 manjaro    22u  CHR  116,2      0t0 478461 /dev/snd/pcmC0D0p
pulseaudi 73457 manjaro    37u  CHR  116,4      0t0 478465 /dev/snd/controlC0


Your command pulseaudio -k is killing pulseaudio, but systemd monitors it and restarts it. So maybe your alsa command is sometimes too slow. Try my systemd commands. I expect its not necessary to stop playing audio. The systemd commands will take care of it and stop playing audio.
  Reply
#4
Thanks, that's useful - I'll poke with some of that.

My hope is to put something in the "restore from sleep" scripts that takes care of it automatically, or figure out what isn't handling sleep right in the kernel drivers.
  Reply
#5
Information 
Today I got a bit fed up by the sound not working after deep sleep, so until the driver itself is capable of handling sleep I hacked together a workaround that seems to work well enough. I haven't packaged it yet or anything, but I wanted to share with community what I got so far.

Right now I have two pieces. First is a script:
Code:
$ cat /usr/local/sbin/sndreset
#!/bin/bash
chmod 000 /dev/snd/pcmC0D0*
message_timeout=0
while lsof /dev/snd/pcmC0D0* | grep -q /dev/snd/pcmC0D0
do
if (( message_timeout == 0 ))
then
message_timeout=11
for line in "$(ps -eo pid,user:32,args | grep "dbus-daemon.*--session" | grep -v grep | xargs)"
do
pid="$(echo "$line" | cut -d' ' -f 1)"
user="$(echo "$line" | cut -d' ' -f 2)"
bus_addr="$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$pid/environ | grep -z DBUS_SESSION_BUS_ADDRESS /proc/$pid/environ | sed 's/DBUS_SESSION_BUS_ADDRESS=//')"
DBUS_SESSION_BUS_ADDRESS="$bus_addr" sudo -u $user -E /usr/bin/notify-send -u normal -t 10000 -a 'Audio recovery' 'Unable to restore audio' 'A process is attempting audio playback, it needs to be stopped before audio can be restored.'
done
fi
let message_timeout--
sleep 1
done
tee /sys/bus/i2c/drivers/es8316/{un,}bind <<< 1-0011 > /dev/null
sleep 1
/usr/sbin/alsactl -E HOME=/run/alsa -E XDG_RUNTIME_DIR=/run/alsa/runtime restore

Second is a systemd service unit:
Code:
$ cat /etc/systemd/system/pinebookpro-sleep.service
[Unit]
Descript=Recover audio after sleep
After=suspend.target hibernate.target hybrid-sleep.target suspend-then-hibernate.target

[Service]
ExecStart=/usr/local/sbin/sndreset

[Install]
WantedBy=suspend.target hibernate.target hybrid-sleep.target suspend-then-hibernate.target

Make sure the script is executable and the service unit is enabled.

How does it work:
Service unit plugs itself to run the script after suspend is done. I think I should be able to remove `hibernate.target` from it, but I decided to keep it around just in case. The meat is in the script itself, and here is what it does:
1. it removes all permissions from the /dev/snd/pcm* files, thus making sure no new process will be able to open them.
2. it enters a loop where for as long as the above files are still open it will send a message to all active sessions hinting at what to do to restore sound playback. Said message will be active for 10 seconds, and it is sent every 11 seconds. Loop iterates every second.
3. once whatever holds the above files open is stopped the loop will end. If one is using PulseAudio it should close those files too once no application is trying to play any sound. Since the files are set to permissions 000 no new process, including PulseAudio, should be able to open them again.
4. the device is re-initialized
5. finally, after a bit of a delay, restore asound.state to raise the volume in ALSA mixer - without this the sound volume may be stuck too low.

Note that this script doesn't need PulseAudio to be stopped in any way, it only needs that nothing tries to play any sound.

Finally about asound.state - I used one from Manjaro as a basis, but I: a) edited it to set both instances of 'Headphone Playback Volume' and both instances of 'Headphone Mixer Volume' to max value (3 and 11) - four places to change total; and b) set immutable flag on the file once it's copied to /var/lib/alsa/asound.state - otherwise I had it reset to values less than max volume. Something I still haven't figured out yet - how to call `alsactl restore` automatically at the right time on boot, because if it's called too early - the volume will not be restored properly, and sound will be stuck being very quiet. So far the best I was able to do is call it using systemd timer at `OnBootSec=1min`.
This message was created with 100% recycled electrons
  Reply
#6
OK, so it's been couple days now and two reboots, after I figured out the issue I reported earlier with OS coming up without sound card (left-over configuration from one of the earlier experiments) things seem to work well so far. I have sound after boot, and I have sound after deep sleep. If the laptop went to sleep while playing some sounds it will not reset until the playback stops, but as soon as it stops it reset within few seconds. I may even be able to get around that block eventually, but for now it doesn't bother me enough to spend more time on it.
This message was created with 100% recycled electrons
  Reply
#7
(03-08-2021, 11:48 PM)moonwalkers Wrote: until the driver itself is capable of handling sleep I hacked together a workaround that seems to work well enough.
Syonyk was on the right track in https://forum.pine64.org/showthread.php?tid=10496 but we never heard back from him, again.
  Reply
#8
K, with some more modifications I was able to get the script working pretty well with sleep/resume cycles, with only caveat being applications that use sound may either quit/crash or lose sound until restart, or just pause playback - the exact behavior depends on a particular app. Here is the updated script:

Code:
$ cat /usr/local/sbin/sndreset
#!/bin/bash
chmod 000 /dev/snd/pcmC0D0*
for line in "$(ps -eo pid,user:32,args | grep "dbus-daemon.*--session" | grep -v grep | xargs)"
do
pid="$(echo "$line" | cut -d' ' -f 1)"
user="$(echo "$line" | cut -d' ' -f 2)"
bus_addr="$(sed -n 's/.*[\x0]DBUS_SESSION_BUS_ADDRESS=\([^\x0]*\)\x0.*/\1\n/p' /proc/$pid/environ)"
DBUS_SESSION_BUS_ADDRESS="$bus_addr" sudo -u $user -E /bin/systemctl --user stop pulseaudio.s*
done
while lsof /dev/snd/pcmC0D0* 2>/dev/null | grep -q /dev/snd/pcmC0D0
do
sleep 0.1
done
echo "1-0011" > /sys/bus/i2c/drivers/es8316/unbind
echo "1-0011" > /sys/bus/i2c/drivers/es8316/bind
for line in "$(ps -eo pid,user:32,args | grep "dbus-daemon.*--session" | grep -v grep | xargs)"
do
pid="$(echo "$line" | cut -d' ' -f 1)"
user="$(echo "$line" | cut -d' ' -f 2)"
bus_addr="$(sed -n 's/.*[\x0]DBUS_SESSION_BUS_ADDRESS=\([^\x0]*\)\x0.*/\1\n/p' /proc/$pid/environ)"
DBUS_SESSION_BUS_ADDRESS="$bus_addr" sudo -u $user -E /bin/systemctl --user start pulseaudio.socket
DBUS_SESSION_BUS_ADDRESS="$bus_addr" sudo -u $user -E /bin/systemctl --user start pulseaudio.service
done
until lsof /dev/snd/controlC0 2>/dev/null | grep -q pulse
do
sleep 0.1
done
/usr/sbin/alsactl -E HOME=/run/alsa -E XDG_RUNTIME_DIR=/run/alsa/runtime restore
This message was created with 100% recycled electrons
  Reply
#9
Sorry... life got in the way.  I really should get that set of patches bundled up for submission, I just haven't been liking computers much lately and have been avoiding them as much as possible outside work.

My set of patches isn't very good, I'm certain it's not entirely correct. but it does work (mostly).



Code:
diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c
index b303ebbd5..d9e33214a 100644
--- a/sound/soc/codecs/es8316.c
+++ b/sound/soc/codecs/es8316.c
@@ -32,6 +32,8 @@ static const unsigned int supported_mclk_lrck_ratios[] = {
256, 384, 400, 512, 768, 1024
};

+#define ES8316_CONFIG_REGISTERS 0x50
+
struct es8316_priv {
struct mutex lock;
struct clk *mclk;
@@ -43,6 +45,7 @@ struct es8316_priv {
unsigned int allowed_rates[NR_SUPPORTED_MCLK_LRCK_RATIOS];
struct snd_pcm_hw_constraint_list sysclk_constraints;
bool jd_inverted;
+ uint8_t config_save[ES8316_CONFIG_REGISTERS];
};

/*
@@ -708,11 +711,39 @@ static int es8316_set_jack(struct snd_soc_component *component,
return 0;
}

+static void dump_es8316_config(struct snd_soc_component *component) {
+ int i;
+ uint32_t val;
+
+ for (i = 0; i <= 0x4F; i++) {
+ printk(KERN_INFO "es8316[%02x]: %x\n", i, snd_soc_component_read32(component, i));
+ }
+}
+
+static void save_es8316_config(struct snd_soc_component *component, struct es8316_priv *es8316) {
+ int i;
+
+ for (i = 0; i < ES8316_CONFIG_REGISTERS; i++) {
+ es8316->config_save[i] = (uint8_t)snd_soc_component_read32(component, i);
+ }
+}
+
+static void restore_es8316_config(struct snd_soc_component *component, struct es8316_priv *es8316) {
+ int i;
+
+ for (i = 0; i < ES8316_CONFIG_REGISTERS; i++) {
+ snd_soc_component_write(component, i, es8316->config_save[i]);
+ }
+}
+
+
static int es8316_probe(struct snd_soc_component *component)
{
struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component);
int ret;

+ printk(KERN_INFO "es8316_probe\n");
+
es8316->component = component;

es8316->mclk = devm_clk_get_optional(component->dev, "mclk");
@@ -748,9 +779,33 @@ static int es8316_probe(struct snd_soc_component *component)
*/
snd_soc_component_write(component, ES8316_CLKMGR_ADCOSR, 0x32);

+ dump_es8316_config(component);
+
+ return 0;
+}
+
+static int es8316_suspend(struct snd_soc_component *component)
+{
+ struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component);
+
+ printk(KERN_INFO "In es8316_suspend\n");
+ dump_es8316_config(component);
+ save_es8316_config(component, es8316);
return 0;
}

+static int es8316_resume(struct snd_soc_component *component)
+{
+ struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component);
+ printk(KERN_INFO "In es8316_resume\n");
+
+ restore_es8316_config(component, es8316);
+ dump_es8316_config(component);
+
+ return 0;
+}
+
+
static void es8316_remove(struct snd_soc_component *component)
{
struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component);
@@ -761,6 +816,8 @@ static void es8316_remove(struct snd_soc_component *component)
static const struct snd_soc_component_driver soc_component_dev_es8316 = {
.probe = es8316_probe,
.remove = es8316_remove,
+ .suspend = es8316_suspend,
+ .resume = es8316_resume,
.set_jack = es8316_set_jack,
.controls = es8316_snd_controls,
.num_controls = ARRAY_SIZE(es8316_snd_controls),
@@ -797,11 +854,15 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client,
struct es8316_priv *es8316;
int ret;

+ printk(KERN_INFO "es8316_i2c_probe\n");
+
es8316 = devm_kzalloc(&i2c_client->dev, sizeof(struct es8316_priv),
      GFP_KERNEL);
if (es8316 == NULL)
return -ENOMEM;

+ printk(KERN_INFO "i2c_probe es8316 = %llx\n", (uint64_t)es8316);
+
i2c_set_clientdata(i2c_client, es8316);

es8316->regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap);
@@ -845,11 +906,57 @@ static const struct acpi_device_id es8316_acpi_match[] = {
};
MODULE_DEVICE_TABLE(acpi, es8316_acpi_match);

+static int es8316_i2c_suspend(struct device *dev) {
+
+ printk(KERN_INFO "es8316_i2c_suspend\n");
+
+ return 0;
+}
+
+static int es8316_i2c_resume(struct device *dev) {
+ struct es8316_priv *es8316 = dev_get_drvdata(dev);
+ int ret;
+
+ printk(KERN_INFO "es8316_i2c_resume\n");
+
+ printk(KERN_INFO "i2c_resume es8316 = %llx\n", (uint64_t)es8316);
+
+ //es8316->regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap);
+ //if (IS_ERR(es8316->regmap))
+ // return PTR_ERR(es8316->regmap);
+
+ //es8316->irq = i2c_client->irq;
+ //mutex_init(&es8316->lock);
+
+ ret = devm_request_threaded_irq(dev, es8316->irq, NULL, es8316_irq,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+ "es8316", es8316);
+ if (ret == 0) {
+ /* Gets re-enabled by es8316_set_jack() */
+ disable_irq(es8316->irq);
+ } else {
+ dev_warn(dev, "Failed to get IRQ %d: %d\n", es8316->irq, ret);
+ es8316->irq = -ENXIO;
+ }
+
+ //return devm_snd_soc_register_component(&i2c_client->dev,
+ //       &soc_component_dev_es8316,
+ //       &es8316_dai, 1);
+ return 0;
+}
+
+
+const struct dev_pm_ops es8316_pm_ops = {
+        SET_SYSTEM_SLEEP_PM_OPS(es8316_i2c_suspend, es8316_i2c_resume)
+};
+
+
static struct i2c_driver es8316_i2c_driver = {
.driver = {
.name = "es8316",
.acpi_match_table = ACPI_PTR(es8316_acpi_match),
.of_match_table = of_match_ptr(es8316_of_match),
+ .pm = &es8316_pm_ops,
},
.probe = es8316_i2c_probe,
.id_table = es8316_i2c_id,
  Reply
#10
(03-10-2021, 11:31 PM)moonwalkers Wrote: K, with some more modifications I was able to get the script working pretty well with sleep/resume cycles, with only caveat being applications that use sound may either quit/crash or lose sound until restart, or just pause playback - the exact behavior depends on a particular app. Here is the updated script:

Perhaps you can help me or give some hints - I can't get your solution to work.
I am on Kernel 5.7.19-1-MANJARO-ARM, coming from an installation with manjaro-arm-installer (encrypted) last week, then did
Code:
pacman -S uboot-pinebookpro-bsp
dd if=/boot/idbloader.img of=/dev/mmcblk2 seek=64 conv=notrunc
dd if=/boot/uboot.img of=/dev/mmcblk2 seek=16384 conv=notrunc
dd if=/boot/trust.img of=/dev/mmcblk2 seek=24576 conv=notrunc
and installed the above mentioned kernel via packages linux-pinebookpro (-headers), edited /etc/systemd/sleep.conf with "SuspendState=mem.

If I use your script and systemd-unit unchanged, system hangs on waking up from sleep.

If I use the command
Code:
sudo tee /sys/bus/i2c/drivers/es8316/{un,}bind <<< 1-0011 > /dev/null
directly in terminal or the echo-command for unbind in the newer script, system hangs, too (seems to start a process, that does not end and can't be aborted by strg-c.
  Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Password reset or recovery tessa 2 248 11-11-2024, 04:27 PM
Last Post: tessa
  Sleep and external display - Are there any options? chris88233 7 3,007 09-04-2023, 09:03 PM
Last Post: wdt
  PineBook Pro seems to go to deep sleep, but doesn't wake up pogo 11 7,408 08-31-2023, 04:20 PM
Last Post: TRS-80
  Rkvdec memory leak? Abhinav 2 1,270 06-02-2023, 08:55 AM
Last Post: Abhinav
  Sound on Armbian Bram 1 1,377 04-01-2023, 03:16 PM
Last Post: TRS-80
  Suspend/Resume Broken with Bootloader on SPI Flash xp19375 2 1,630 03-24-2023, 04:25 PM
Last Post: srs5694
  With the help of a friend, I installed a beautiful deep os distribution, but I won't wangyukunshan 0 821 03-03-2023, 10:56 PM
Last Post: wangyukunshan
  Hibernation and Speaker Sound Issues, First Month of PBP kebab 7 4,014 11-02-2022, 02:33 PM
Last Post: kebab
  Resume from suspend not working after flashing Tow-Boot to SPI xp19375 3 2,510 10-31-2022, 10:14 AM
Last Post: wdt
  Loud distorted speaker sound when connecting to power supply myself600 2 1,619 09-21-2022, 12:11 PM
Last Post: myself600

Forum Jump:


Users browsing this thread: 3 Guest(s)