Enabling standby mode using the power key

Hello,

Months ago while working in our Kernel, I managed to enable the power key functionality to do a soft power off of the game shell (How to get the power button to soft power off). To enable it, the change was simple: I just removed the power key code (in drivers/input/misc/axp20x-pek.c, and drivers/mfd/axp20x.c) that was in our patch (52rc4.patch) and used the the mainline kernel without these changes. Maybe our kernel devs needed this before, but it is not needed anymore in recent kernel, I don’t know.

This is a very convenient way to switch off the gameshell but what I really wanted was a standby mode to conserve power.

Recently I started to investigate what I could do to get standby working. The first thing I did was to set mem to /sys/power/state with echo mem > /sys/power/state. This will trigger a standby routine and doing it the gameshell enters in a sort of sleep mode. It was very promising, the problem was that I was not able to get it back from the sleep state with the power key and I needed to hold the power key for more than 7~10 seconds for a hardware power off. Another way to get it back would be using the RTC to trigger a wakeup routine. The problem was that the RTC was not enabled as wake up source in our kernel (5.2rc5), so I couldn’t test it. On recent kernels (>5.5) I found that the RTC wake functionality was finally implemented, and I decided to try it. The easiest way to do it is using the command rtcwake, so I first sent:

#rtcwake -m mem -s 60

To enter in standby (mem state) , and wakeup after 60 seconds. It worked :slight_smile:

Now the problem is to make it wakeup using the power key. I found the developer that made a lot of contributions to the mainline linux kernel for the Allwinner SOC’s, and our PMIC. He is working actively on improving battery life on Pinephone, and the Pinebook that uses Allwinner SOC. He suggested me to try using one of his kernel branches to enable a irq that maybe would wakeup our SOC with the power key.

The branch is patch/irqchip-v2 from https://github.com/smaeul/linux. I applied my gameshell patch for the kernel 5.7rc5 and compiled his kernel. It worked!!! Finally the power key can wake up! The downside was that it switches off immediately after waking up. The problem was that the power key still sending a KEY_POWER to the userspace. But to solve it was easy: Just change what the power key sends to userspace on drivers/input/misc/axp20x-pek.c . Doing so I cannot have the power key for power off. Not happy yet.

Looking in our PMIC datasheet I found that it can identify four different events coming from power key: falling edge, rising edge, short press, and long press. So I modified the kernel code to instead of send a KEY_POWER event, it would send a KEY_SUSPEND for short press, and KEY_POWER for long press. At first I though that as soon the SOC wakes, it would trigger again a sleep event like before, but to my surprise it didn’t happen. When the system wakes, it doesn’t trigger another sleep event and all worked :smiley:. A short press trigger a suspend event and wakes up, a long press trigger a power off. Perfect.

I tried to understand what was happening but the datasheet was not very clear, I found that our PMIC can wake up from falling edge, but short press was not in the list. So maybe when it wakes up it reports to the userspace that an falling edge occurred, but don’t report short press. I know that it is impossible to have a falling edge without a short press, but it is how things seems to be working. I couldn’t find a better explanation, you can comment if you have a better idea.

Here are my changes to the kernel code:

diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
index aa59496e4..3653f4bf4 100644
--- a/drivers/mfd/axp20x.c
+++ b/drivers/mfd/axp20x.c
@@ -202,8 +202,10 @@ static const struct resource axp803_usb_power_supply_resources[] = {
 };

 static const struct resource axp22x_pek_resources[] = {
-       DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_PEK_RIS_EDGE, "PEK_DBR"),
-       DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_PEK_FAL_EDGE, "PEK_DBF"),
+//     DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_PEK_RIS_EDGE, "PEK_DBR"),
+//     DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_PEK_FAL_EDGE, "PEK_DBF"),
+       DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_PEK_LONG, "PEK_DBF"),
+       DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_PEK_SHORT, "PEK_DBR"),
 };

 static const struct resource axp288_power_button_resources[] = {

and

diff --git a/drivers/input/misc/axp20x-pek.c b/drivers/input/misc/axp20x-pek.c
index c8f87df93..7aa7591ad 100644
--- a/drivers/input/misc/axp20x-pek.c
+++ b/drivers/input/misc/axp20x-pek.c
@@ -212,10 +212,16 @@ static irqreturn_t axp20x_pek_irq(int irq, void *pwr)
         * The power-button is connected to ground so a falling edge (dbf)
         * means it is pressed.
         */
-       if (irq == axp20x_pek->irq_dbf)
+       //Using long press for KEY_POWER instead of falling edge
+       //Using short press for KEY_SUSPEND
+       if (irq == axp20x_pek->irq_dbf) {
                input_report_key(idev, KEY_POWER, true);
-       else if (irq == axp20x_pek->irq_dbr)
                input_report_key(idev, KEY_POWER, false);
+       }
+       else if (irq == axp20x_pek->irq_dbr) {
+                input_report_key(idev, KEY_SUSPEND, true);
+                input_report_key(idev, KEY_SUSPEND, false);
+        }

        input_sync(idev);

@@ -252,6 +258,7 @@ static int axp20x_pek_probe_input_device(struct axp20x_pek *axp20x_pek,
        idev->dev.parent = &pdev->dev;

        input_set_capability(idev, EV_KEY, KEY_POWER);
+       input_set_capability(idev, EV_KEY, KEY_SUSPEND);

        input_set_drvdata(idev, axp20x_pek);

So, now comes the part of understanding our sleep mode.
Looking the contents in /sys/power/mem_sleep we only see [s2idle] and no other modes. To understand the modes we can look at:

https://www.kernel.org/doc/Documentation/power/states.txt

State: Suspend-To-Idle
ACPI state: S0
Label: “s2idle” (“freeze”)

This state is a generic, pure software, light-weight, system sleep state.
It allows more energy to be saved relative to runtime idle by freezing user
space and putting all I/O devices into low-power states (possibly
lower-power than available at run time), such that the processors can
spend more time in their idle states.

This state can be used for platforms without Power-On Suspend/Suspend-to-RAM
support, or it can be used in addition to Suspend-to-RAM to provide reduced
resume latency. It is always supported.

Which means that it doesn’t switches off the processor, and is doesn’t suspend to RAM ([deep] state). It freezes all the processes. It is like having everything on but with load at 0%. The CPU works at lowest possible level.

The developer (smaeul) told me that he is working in a open source code to develop deep state for Allwinner SOC named crust-firmware (GitHub - crust-firmware/crust: SCP (power management) firmware for sunxi SoCs), but what I understood from him was that deep state is not implemented in our Allwinner R16 SOC:

…Deep suspend requires extra firmware that runs while the CPUs are off, and turns the CPUs back on when an interrupt comes in. That firmware doesn’t exist for R16 on mainline Linux. It looks like U-Boot (your PSCI implementation) always returns “Not Implemented” for ARM_PSCI_1_0_FN_PSCI_FEATURES , which means Linux should detect it as not supporting suspend…

crust-firmware relies on a always-on AR100 microcontroller built-in inside the SOC. This microcontroller is responsible of waking up the CPU, GPU… and provides power management services.

The bad part is that the AR100 seems to not be present inside our R16 SOC. Maybe our R16 doesn’t support deep state in hardware. If someone can write something about it would be very good to understand if we have it implemented in hardware or not, and if yes where we can get the firmware that enables it (even if it is not open source).

Meanwhile we can use the power key, and the s2idle mode, I think this is already a good start. So I did some experiments to see what we get.

I charged the gameshell to 100% and monitored the power consumption. The CPU load was less than 5% for the entire period, wifi was on. I took measurements each 5 minutes for different LCD brightness configurations (9 (max), 5, and 0 (swithed off)). For the freezing mode I took measurements with 1 hour intervals (I woke up the gameshell, measured, and started the standby again). Keep in mind that I’m using the original battery, and my battery is already ~1 year old.

To change brightness I used:

#echo n > /sys/class/backlight/backlight/brightness

where n is a number between 0, and 9. 0 means off, and 9 is the max brightness.

To take measurements I created a small script to write it in a file and used a cronjob to execute it every 5 minutes:

#!/bin/bash
cat /sys/class/power_supply/axp20x-battery/voltage_now | tr -d '\n' >> bat_current.txt
echo -en '\t' >> bat_current.txt
cat /sys/class/power_supply/axp20x-battery/capacity | tr -d '\n' >> bat_current.txt
echo -en '\t' >> bat_current.txt
cat /sys/class/power_supply/axp20x-battery/current_now | tr -d '\n' >> bat_current.txt
echo -en '\t' >> bat_current.txt
date +%H:%M >> bat_current.txt

This script generates a line in a text file with the battery voltage, capacity, current, and the actual time separated by a tab.

The results clearly states that when freezing we didn’t use a deep state mode or suspend to RAM, but it is the better scenario. You ensure that the CPU won’t do any job, switches off the screen, the network and you can return to your work in 3 seconds. A deeper state will probably need something like 8-10 seconds to return to life.

For curiosity I also plotted the power consumed over time.

Edit: An extra information can be taken from the graph: The battery capacity in mAh. For this we can integrate the blue trace over time. In this case, for approximation since it is stable, we can just multiply the averaged power (0.86 W) by the time that the battery lasted (375 min.). We get:

(375/60)x0.86/3.7 = 1.45 Ah = 1450 mAh

This is very good, and my battery, despite its time, still holding a very good amount of charge. Also it’s better than the specs that should be 1200 mAh. I think, based on this measurement, our battery is 1500 mAh instead.

The measurement is quite stable, but for the other LCD options (5, and 9 levels) the results were unstable. Probably because the CPU still can do tasks, and the power consumption varies over time. Different of what happens in freezing, where no tasks are executed.

In summary, the freezing mode using the power key is a pause/unpause button and you can return to your game very fast. Your game will be frozen like all the system. This is very convenient when you are for instance changing trains, waiting for the bus when moving in the city, or need to talk to someone for a few minutes… But not enough for many hours of pause. In this case switching off is better (with long key press :slight_smile: ).

For these experiments I also used my dtb files that allows higher frequency of operations (1.2GHz and beyond - #27 by Joao_Manoel). It enables lower voltage operations for the CPU. The original dtb allows lower CPU frequencies, but keep applying higher voltage to the CPU.

If we get more information about the deep state mode we can dream a bit more.

In this thread “How do I come to suspend?”, @hal mentioned:

I think 10 hours is amazing, and very acceptable (for me at least), maybe he can help us to improve what I did further with what he tried before. Also, how we could use the non-open source firmware to get a deeper state? Maybe we could modify a bit the crust-firmware and make it work for the R16.

I apologies for language mistakes here, I’m not good at writing in English.

6 Likes

glad to see the deep search and effort you made into this, that’s very promising (and was pretty pleasant to read

1 Like

Thanks :slight_smile:
The text have many language mistakes, I’ll edit to improve it often.

I didn’t expect to have it long, but wanted to document the steps properly so someone else can help us to improve it.

1 Like