GPIO shutdown and poweroff

I built a handheld around a Raspberry Pi Zero W, and now I am working on migrating it to the Radxa Zero to take advantage of the additional power. There are a couple of RPI-specific aspects that I need to figure out how to implement in EmuELEC with this board, and one of them is the power switch with safe shutdown.

(Side note: The forum won’t let me include more than 2 links because I’m a new user, so I’ve collected all my hyperlink references in this Pastebin and will use Wikipedia-style bracketed reference numbers in my post. Apologies for the inconvenience.)

I am using an external battery/charge/boost controller that has a GPIO-controlled enable (EN) pin. I am using this safe shutdown circuit [1] and a simple SPDT switch. When the switch is “on,” the battery is connected to the EN pin, which enables the power controller and supplies power to the Pi and other peripherals. When the switch is turned “off,” the Pi is triggered via GPIO to initiate a shutdown. After shutdown is complete, a second GPIO on the Pi is driven high to indicate it’s safe to power off. The power controller EN pin is then driven low and the power is shut off.

The Pi implementation relies on the gpio-shutdown [2] and gpio-poweroff [3] device tree overlays. I understand these overlays are specific to the Raspberry Pi, so I’m trying to figure out whether something similar is available on the Amlogic devices/Radxa Zero.

Looking at the code for gpio-shutdown [4], it looks like the kernel just monitors the specified GPIO and fires a KEY_POWER when the state changes. gpio-poweroff is actually a kernel module [5], and the gpio-poweroff overlay [6] looks like it just provides configurable parameters for the kernel module through a boot overlay.

Looking at the EmuELEC Github repo, I noticed that Radxa_Zero_boot.ini [7] contains a line:
if test "${gpiopower}" != ""; then setenv gpiopower "gpiopower=${gpiopower}"; fi

From my Google research, it looks like “gpiopower” is indeed something similar to gpio-shutdown that is specific to the Amlogic vendor kernel, but does not exist in the mainline kernel.

Am I correct to assume from this that EmuELEC uses an Amlogic kernel that has support for this gpiopower parameter? If so, should I expect this to work in the same manner as gpio-shutdown when I supply the correct pin in boot.ini?

If not (and just in case this is helpful to anyone else), here’s some more info I found in my research:

I found a Github issue thread on the Home Assistant project [8] where a user was unable to use gpiopower because that project uses mainline kernel, so the developer implemented the feature by patching the dtsi [9].

I found a similar block of code [10] in the EmuELEC Github repo, which looks like it works by polling a specific GPIO pin and then firing KEY_POWER if it changes state, but that block looks like it’s specific to the Beelink GS_King X device.

However, I did find a patch [11] that adds support for the Radxa Zero, which seems to be adding meson-g12a-radxa-zero.dts from the Radxa-maintained kernel [12]. That .dts doesn’t seem to have anything related to GPIO power, but the meson-g12b-radxa-zero2.dts from the same Radxa repo does have a block that looks like it’s implementing a GPIO power-off sequence [13].

Maybe I can just copy that into the Radxa Zero patch?

I also can’t tell from the Github repo whether the kernel has CONFIG_POWER_RESET_GPIO enabled, built as a module, or not set. As I understand it, this is the part that changes state of a second GPIO pin after the system shutdown has completed. This is what the shutdown circuit needs in order to tell the battery/power controller to shut off the power.

Overall, I am hoping a dev or someone who is more familiar with this project can tell me whether it’s already possible to accomplish what I’m trying to do, or if not, whether any of the pieces are already in place and what would still need to be done to get it to work. Thank you in advance!

The kernel is pulled from another repo when an EmuELEC/CoreELEC/LibreELEC image is built, in cases of EmuELEC/CoreELEC it’s from CoreELEC 's linux-amlogic repo. So you should take a look at the corresponding kernel repo if you want to implement such low-level stuffs: GitHub - CoreELEC/linux-amlogic. The kernel config resides in EmuELEC/CoreELEC repos though: EmuELEC/linux.aarch64.conf at dev · EmuELEC/EmuELEC · GitHub

So if you want to implement your kernel module, you either fork linux-amlogic and do your modification there and change the url/version in under projects/Amlogic-ce/packages/linux. Or you generate patches and put them under projects/Amlogic-ce/packages/linux/patches. Kernel config under projects/Amlogic-ce/devices/Amlogic-ng/linux also should be updated

For modification of linux-amlogic it would be better to ask for help from developers of CoreELEC, but they are really busy these days implementing S905X4 support. I suggest you wait until they finish the work on hand

One thing to note is that the boot.ini you mentioned is only a startup script and has nothing to do with kernel behaviour. That GPIO key is read by u-boot to define corresponding behaviour, before kernel is even loaded.

Since you’ve tinkered around a Pi I assume you know dt describes the device and then the kernel applys corresponding drivers. So gpio nodes in dts must be modified. The main stuffs you want to edit if you want to play with GPIO is dts, you should choose a corresponding one from linux-amlogic/arch/arm64/boot/dts/amlogic at amlogic-4.9-19 · CoreELEC/linux-amlogic · GitHub as a start point, and be careful not to mess up with already defined ones

Thanks @7Ji, this is helpful! Regarding boot.ini though, my understanding is that u-boot passes the contents of the bootargs environment variable directly to the kernel as parameters. As I read the “stock” Radxa_Zero_boot.ini, it checks to see if there’s a gpiopower key in config.ini, and if so, that gets appended to the string of bootargs that gets passed to the kernel. Then it becomes a question of whether the kernel knows what to do with that.

This is the part I’m currently trying to work out. It seems to me that the Amlogic kernel should already have this baked in, with the possible need to include a block in a dts. If it doesn’t work out of the box, I’m going to try borrowing that block from the official Raxda Zero 2 dts and see if that gets it working.

Double checked and it is really passed to kernel, so it should work already, have you run EmuELEC/CoreELEC and actually tested it?

So I’ve spent some time trying to see if this works “out of the box.” The Radxa Zero GPIO pin info is here. I first tried pin #3 which is GPIO number 490. So in config.ini, I set gpiopower=490. After rebooting, dmesg included: [ 0.000000@0]d gpio-keypad: gpiopower_setup gpiopower : 490

Nevertheless, shorting that pin to either GND or VCC did not produce any result.

So next I tried cat /sys/kernel/debug/pinctrl/pinctrl@ff800014/pinconf-pins which gave me this output:

Pin config settings per pin
Format: pin (name): configs
pin 0 (GPIOAO_0): input bias pull up, output drive strength (2), input enabled
pin 1 (GPIOAO_1): input bias pull up, output drive strength (2), input enabled
pin 2 (GPIOAO_2): input bias pull down, output drive strength (2), pin output (1 level)
pin 3 (GPIOAO_3): input bias pull up, output drive strength (2), input enabled
pin 4 (GPIOAO_4): input bias pull down, output drive strength (2), input enabled
pin 5 (GPIOAO_5): input bias pull up, output drive strength (2), input enabled
pin 6 (GPIOAO_6): input bias pull down, output drive strength (2), input enabled
pin 7 (GPIOAO_7): input bias pull up, output drive strength (2), input enabled
pin 8 (GPIOAO_8): input bias pull up, output drive strength (2), input enabled
pin 9 (GPIOAO_9): input bias pull down, output drive strength (2), input enabled
pin 10 (GPIOAO_10): input bias pull up, output drive strength (2), input enabled
pin 11 (GPIOAO_11): input bias pull down, output drive strength (2), pin output (0 level)
pin 12 (GPIOE_0): input bias disabled, output drive strength (3), input enabled
pin 13 (GPIOE_1): input bias disabled, output drive strength (3), input enabled
pin 14 (GPIOE_2): input bias pull down, output drive strength (2), input enabled
pin 15 (GPIO_TEST_N): input bias pull up, output drive strength (2), pin output (1 level)

From this it looked like GPIOAO_3 was configured as an input pin that is pulled high by default, so I checked the Radxa Zero GPIO table, saw that GPIOAO_3 is pin #7 (GPIO number 415) and so tried again with gpiopower=415 in config.ini. On reboot I confirmed that dmesg said: [ 0.000000@0]d gpio-keypad: gpiopower_setup gpiopower : 415

Nevertheless, connecting this pin to GND or VCC produced no result. I suspect I’m missing something in the dtbs, so my next step is to figure out how to modify those for my system.

Made some progress, sort of. I made a patch for /arch/arm64/boot/dts/amlogic/g12a_s905y2_radxa_zero.dts that adds the following:

        gpio-keys-polled {
                compatible = "gpio-keys-polled";
                poll-interval = <100>;

                power-button {
                        label = "power";
                        linux,code = <KEY_POWER>;
                        gpios = <&gpio_ao GPIOAO_3 (GPIO_ACTIVE_LOW)>;

        gpio-poweroff {
                compatible = "gpio-poweroff";
                gpios = <&gpio_ao GPIOAO_2 GPIO_ACTIVE_LOW>;
                timeout-ms = <20000>;

I also changed the kernel config so that CONFIG_POWER_RESET_GPIO, CONFIG_KEYBOARD_GPIO, and CONFIG_KEYBOARD_GPIO_POLLED are built in.

With the new kernel and dtb, I can now successfully trigger a system shutdown by shorting GPIOAO_3 to ground. However, this pin is “hardcoded” into the kernel and is not affected by the gpiopower line in config.ini. I feel like there’s something in gpio_keypad.c that’s supposed to perform a similar function with the pin defined in config.ini, but it’s just not working.

So while the first part (GPIO-triggered shutdown) is now working for me, the second part (triggering another GPIO when the shutdown is complete) isn’t. With the new kernel, I get this in dmesg:

[    0.674882@1]- poweroff-gpio gpio-poweroff: gpio_poweroff_probe: pm_power_off function already registered
[    0.674935@1]- poweroff-gpio: probe of gpio-poweroff failed with error -16

Some googling tells me this means that something else in the kernel is registering the pm_power_off function so gpio-poweroff can’t create the handle. I’ve spent some time poking around trying to find it but so far no luck. I’m hoping that maybe the desired functionality is already built in somehow, but I haven’t been able to find any documentation suggesting how this could be made to work. Hoping someone has an idea.

1 Like