Skip to content

Expander GPIO: Initial state not set correctly when configuring as output and low active #5107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
agners opened this issue Jul 28, 2022 · 11 comments

Comments

@agners
Copy link
Contributor

agners commented Jul 28, 2022

Describe the bug

It seems that external GPIOs (controlled through firmware) do not apply the initial state correctly.

I am using the following device tree snippet

	pwr_led: led-pwr {
		label = "PWR";
		linux,default-trigger = "none";
		default-state = "off";
		gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
	};

The expectation is that this makes the LED to be off by default.

The leds-gpio driver uses the gpiod API to initialize the GPIO correctly:
https://elixir.bootlin.com/linux/v5.15/source/drivers/leds/leds-gpio.c#L109

The gpiod_direction_output API takes care of the polarity flag, and requests a high output from the GPIO driver:
https://elixir.bootlin.com/linux/v5.15/source/drivers/gpio/gpiolib.c#L2324

Currently, the value (1) is directly passed to the firmware in gpio-raspberrypi-exp.c. The state of the GPIO however is not changed.

It seems that the firmware is aware of the polarity configuration on its own, and does not wrongly applies the new value?

This change fixes the problem for that particular use case, but I am not sure if that is correct:

@@ -118,6 +118,9 @@ static int rpi_exp_gpio_dir_out(struct gpio_chip *gc, unsigned int off, int val)
        if (ret < 0)
                return ret;
        set_out.polarity = ret;         /* Retain existing setting */
+       if (set_out.polarity)
+               set_out.state = !val;
 
        ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_SET_GPIO_CONFIG,
                                    &set_out, sizeof(set_out));

What is interesting to me is that reading back the GPIO seems to return the level "unaltered":

[    1.643491] rpi_exp_gpio_get, off 2, state 1

Steps to reproduce the behaviour

Apply the following device tree change:

	pwr_led: led-pwr {
		label = "PWR";
		linux,default-trigger = "none";
		default-state = "off";
		gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
	};

Observe that the red LED remains on.

Device (s)

Raspberry Pi CM4

System

This is on Home Assistant OS. We are using the latest 5.15 downstream kernel from this repository. As this is a pure kernel issue, this should be the very same on a regular Raspberry Pi.

# uname -a
Linux homeassistant 5.15.32-v8 #1 SMP PREEMPT Thu Jul 28 14:06:36 UTC 2022 aarch64 HAOS
# dmesg | grep firmware
[    0.108313] raspberrypi-firmware soc:firmware: Attached to firmware from 2022-03-24T13:19:26, variant start
[    0.112338] raspberrypi-firmware soc:firmware: Firmware hash is e5a963efa66a1974127860b42e913d2374139ff5

Logs

No response

Additional context

No response

@pelwell
Copy link
Contributor

pelwell commented Jul 28, 2022

If you declare the GPIO as ACTIVE_LOW then "off" gives a high logic level. Change one or the other to get an initial low logic level.

@agners
Copy link
Contributor Author

agners commented Jul 28, 2022

If you declare the GPIO as ACTIVE_LOW then "off" gives a high logic level.

Yes, that is the theory. Unfortunately, it doesn't work that way. When ACTIVE_LOW and "off" is given, the logic level is low (which is wrong). This leads to the LED to be on (since the GPIO is connected on the cathode side of the LED).

image

@agners
Copy link
Contributor Author

agners commented Jul 28, 2022

FWIW, the polarity is ACTIVE_LOW by default for the power LED. So using the following should turn off both LEDs.

dtparam=act_led_trigger=none
dtparam=pwr_led_trigger=none

But that is not the case: It does turn off the activity LED, but the power LED stays on. I first thought that default-state = "keep" is the reason, using default-state = "off" does not help (which essentially is the problematic configuration I'd like to report here).

Side note: Most guides suggest to also set pwr_led_activelow=off, e.g. to turn off both LEDs, the suggestion is:

# Disable the ACT LED.
dtparam=act_led_trigger=none
dtparam=act_led_activelow=off

# Disable the PWR LED.
dtparam=pwr_led_trigger=none
dtparam=pwr_led_activelow=off

For the activity LED, this has no effect as it is high active by default.

However, for the power LED this changes the GPIO setting to be GPIO_ACTIVE_HIGH (since off = 0 = GPIO_ACTIVE_HIGH).

/* Bit 0 express polarity */
#define GPIO_ACTIVE_HIGH 0
#define GPIO_ACTIVE_LOW 1

Currently, this makes the power LED to be off. But it is wrong, the PWR LED is not a high active GPIO. But setting power LED low active should cause the LED to be on!

@pelwell
Copy link
Contributor

pelwell commented Jul 28, 2022

The expectation is that this makes the LED to be off by default.

If you declare the GPIO as ACTIVE_LOW then "off" gives a high logic level.
Yes, that is the theory.

Those two statements are contradictory. Explain.

@agners
Copy link
Contributor Author

agners commented Jul 28, 2022

The expectation is that this makes the LED to be off by default.

		default-state = "off";
		gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;

I expect it to be off, but when I do this, the LED is on (means logic level high).

I think we are in agreement what we expect:

If you declare the GPIO as ACTIVE_LOW then "off" gives a high logic level.

"gives a high logic level." -> LED off.

I don't see the contradiction?

We agree that

  • GPIO logic level low equals LED on
  • GPIO logic level high equals LED off

Correct?

You said:

If you declare the GPIO as ACTIVE_LOW then "off" gives a high logic level.

I assume with "then off" you meant default-state = "off", correct?

So, GPIO_ACTIVE_LOW + default-state = "off", should lead to a GPIO level high, and a LED which is off.

That is not the case.

@6by9
Copy link
Contributor

6by9 commented Jul 28, 2022

The firmware does have a notion of polarity for itself.

https://github.com/raspberrypi/firmware/blob/master/extra/dt-blob.dts#L1768

            pin@p130 { function = "output"; termination = "no_pulling"; polarity = "active_low"; startup_state = "active"; }; // PWR_LED (RED)

So &expgpio 2 is defined as being inverted within the firmware.

Memory says that the set/get state calls were meant to pass through directly though, and the polarity was only to retain the state (as was required by the older gpio API).

Testing for myself:
Using https://github.com/6by9/rpi3-gpiovirtbuf as my quick and dirty hacking tool, rpi3-gpiovirtbuf s 130 0 does indeed turn a CM4 power LED on, and rpi3-gpiovirtbuf s 130 1 turns it off.

Checking with a multimeter, s 130 1 sets pin 2 of the 74LVC1G07 driver to 3.3V, and s 130 0 sets pin 2 to 0 volts, so there is no inversion there.

The datasheet of the 74LVC1G07 fig 3 shows an inverter before the open-drain output, therefore 1 / 3V3 / (inactive with ACTIVE_LOW) will be inverted to drive the FETs gate at 0V, and hence the LED is off.
0 / 0V / (active with ACTIVE_LOW) will be inverted to "high" to drive the FET, and the LED is on.

As you say, the kernel for CM4 is configured with

		led-pwr {
			label = "PWR";
			gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
			default-state = "keep";
			linux,default-trigger = "default-on";
		};

and using /sys/class/leds/led1/brightness, 0 turns the LED off, and 1 turns it on, so it is doing the appropriate inversion based on that ACTIVE_LOW flag.

So that largely confirms to me that whilst the firmware does have a notion of polarity, it is bypassed when driven by Linux, and that is behaving as it should.

The only difference I can see with your DT snippet is that you're setting it to default-state = "off"; instead of keep. So is it only that initial state that you're worrying about?

@6by9
Copy link
Contributor

6by9 commented Jul 28, 2022

Ah, it looks to be the firmware on that initial set. It passes through all the parameters via a different path, and that does apply a polarity correction which we don't want.
I'll look at it in more detail tomorrow.

@agners
Copy link
Contributor Author

agners commented Jul 28, 2022

Ah, it looks to be the firmware on that initial set. It passes through all the parameters via a different path, and that does apply a polarity correction which we don't want.

That very much looks like what I am observing indeed.

It is confusing as subsequent GPIO sets/clears seem to work.

I'll look at it in more detail tomorrow.

Very much appreciated, thank you!

@6by9
Copy link
Contributor

6by9 commented Jul 29, 2022

Test firmware for 2711 based boards (Pi4/CM4/Pi400) that should solve this at https://drive.google.com/file/d/1CI2wzuYDSWa4ewSm8Spw4ztI2xp5HL0r/view?usp=sharing

The same fix will apply to the other boards as well, but that will come with the full release.

@agners
Copy link
Contributor Author

agners commented Jul 29, 2022

@6by9 thanks, just copied the firmware on my board and I can confirm the power LED is now behaving as expected!

I am a bit hesitant to deploy this firmware in production. I'll wait until it makes it into stable and gets a bit wider testing. I use a kernel level work around meanwhile.

From my side, this issue can be closed. Thanks!

@pelwell pelwell closed this as completed Aug 8, 2022
@6by9
Copy link
Contributor

6by9 commented Aug 8, 2022

Firmware change has been merged internally, so should be in the next rpi-update release.

popcornmix added a commit to raspberrypi/firmware that referenced this issue Aug 8, 2022
popcornmix added a commit to raspberrypi/rpi-firmware that referenced this issue Aug 8, 2022
mkreisl added a commit to xbianonpi/xbian-package-firmware that referenced this issue Jul 1, 2023
- firmware: board_info: Handle misprogrammed 3B rev 1.2s

- firmware: mmal: Add mapping for IL OMX_IndexParamBrcmEnableIJGTableScaling param

- firmware: Handle overlay parameters embedded in overlay_map.dtb
  See: raspberrypi/linux#4860

- firmware: firmware: Add HDMI_PORTS trait

- firmware: arm_dt: Fix rpi-poe overlay parameters
  See: #1689

- firmware: jpeghw: Skip APP0 AVI1 headers, regardless of length
  See: https://forums.raspberrypi.com/viewtopic.php?p=1975448

- firmware: camera_subsystem: Report ignored interfaces due to libcamera
- See: #1679

- firmware: Export os_prefix, overlay_prefix, rsts and boot-mode on all models

- firmware: vcfw/hdmi_i2c: Initialise all instances from hdmi_i2c_init

- firmware: dtoverlay: Add support for string escape sequences
  See: https://forums.raspberrypi.com/viewtopic.php?t=330792

- firmware: isp: R and B order must be swapped when reading VC_IMAGE_RGBA32 into the ISP
  See: http://git/vc4/vc4/-/merge_requests/1430

- firmware: dtoverlay: Fix path rebasing and exports

- firmware: dtoverlay: Fix clang warnings

- firmware: arm_loader_dvfs: Make arm only see its own boosts, fixed and min clocks

- firmware: arm_loader: Support longer file paths
  See: #1720

- firmware: arm_loader_dvfs: Support CLOCK_HDMI as boostable clock
  See: raspberrypi/linux#5016

- firmware: dtblob: Use a cached alias to reduce boot time

- firmware: hdmi: Reduce the number of EDID retries if hotplug is not detected

- firmware: variants: Add mjpg_encode to the standard firmware image

- firmware: vcgencmd display_power and camera_auto_detect fixes

- firmware: arm_loader_dvfs: Only add clocks to boostable list when they have been boosted
  See: #1726

- firmware: arm_dt: Try upstream DTB files if downstream absent

- firmware: arm_loader: Delay the USB controller switchover

- firmware: Fix for vc_image YUYV family to YUV422 planar conversion function

- firmware: arm_loader_dvfs: Only add clocks to boostable list when they have been boosted
  See: #1726

- firmware: arm_dt: Try upstream DTB files if downstream absent

- firmware: arm_loader: Delay the USB controller switchover

- firmware: Fix for vc_image YUYV family to YUV422 planar conversion function

- firmware: arm_dt: camera_auto_detect cam0 flag needs to look at Unicam instance, not port

- firmware: platform: over-voltage Zero 2 W by two pips
  See: #1723

- firmware: hello_pi: Fix some build issues
  See: #1728

- firmware: video_decode: Stop decode on a colourspace change
  See: raspberrypi/linux#5059

- firmware: video_encode: Fix subsample image alignment assert

- firmware: tc358762_DSI: Don't start the PV and DSI before the HVS

- firmware: arm-dt: Export log buffer addresses to /proc/chosen/log

- firmware: arm_loader: Fix GET_CLOCKS to not overwrite client buffer
  See: #1688

- firmware: arm_loader: Declare program_sdhost_use_dma

- firmware: arm_loader: initramfs over NVME fix
  See: #1731

- firmware: Disable BT flow control pins for Pi3 rev1.3

- firmware: power: Fix failover to secondary PMIC interface functions
  See: https://forums.raspberrypi.com/viewtopic.php?t=338429

- firmware: arm_loader: Correct GPIO expander initial state via SET_GPIO_CONFIG
  See: raspberrypi/linux#5107

- firmware: platform: Set min_frequency for HDMI SM clock on Pi0-3

- firmware: arm_loader: Never set warranty bit
  See: #1741

- firmware: vcfw: camera_subsystem: Fix loop counter for powering up devices
  See: https://forums.raspberrypi.com/viewtopic.php?t=338917

- firmware: ldconfig: Add [cm4s] conditional

- firmware: Fix USB boot
  See: #1744

- firmware: video decode/MJPEG fixes
  See: http://git/vc4/vc4/-/merge_requests/1548

- firmware: power: Restore VEC and PIXEL clocks after HDMI domain power cycle
  See: raspberrypi/linux#4962

- firmware: isp: Workaround for very unpleasant artifacts in the sharpening block

- firmware: arm_loader: Raise maximum gzipped kernel size

- firmware: arm-loader: Indicate tryboot status via /proc/device-tree/chosen/bootloader/tryboot

- firmware: arm_loader: Increase TFTP block size to 1468 bytes
  See: raspberrypi/rpi-eeprom#375

- firmware: Add kernel= logging

- firmware: camera_auto_detect changes
  See: #1750

- firmware: board_info: Fix Pi 400 PHY addresses
  See: #1754

- firmware: il: isp: Correct order buffers were returned in

- firmware: isp: Run ISP without hi-res output buffer

- firmware: arm_dt: Export the bootloader EEPROM RSA public key via device-tree

- firmware: Add tryboot A_B mode

- firmware: ldconfig: Add all, none, tryboot section support to autoboot.txt for start.elf

- firmware: arm-dt: bootloader: Pass the original partition number when booting a ramdisk

- firmware: arm_loader: HAT EEPROM support for GPIO bank 1
  See: #1756

- firmware: arm_loader: Add vcmailbox support for 256bit OTP customer device key
  See: raspberrypi/usbboot#163

- firmware: il: video_encode: MJPEG is not conditional on being RASPBERRYPI_FULL

- firmware: arm_loader: Improvements to Compute Module audio
  See: https://forums.raspberrypi.com/viewtopic.php?p=2052680

- firmware: arm_loader: Fix GPIO bank 1 support
  See: #1756

- firmware: arm_loader: PWM1 is not available on GPIO 45

- firmware: power: Always read the uncached voltage for AIN and USB_PD
  See: https://forums.raspberrypi.com/viewtopic.php?p=2059832#p2059832

- firmware: Use new SDHCI controller instead of legacy arasan
  See: #1763

- firmware: Add D flag to video= cmdline option when hotplug is forced

- firmware: Actually rebuild firmware described in previous commit

- firmware: hdmi_2711: Make some clock setup unconditional so booting without hdmi setup is possible

- firmware: arm_dispmanx: Correct support for NV21, and add support for YV16
  See: #1767

- firmware: arm_dispmanx: Fix FKMS to adopt pre-multiplied alpha
  See: #1773

- firmware: il isp: Correct histogram masks for updated group 2 regions

- firmware: video_decode: Convert the active lines, not the padded buffer

- firmware: bootloader: Raise CMA cap to 512MB on a 64-bit Pi4

- firmware: bootloader: Prefer 64-bit kernels on Pi 4s
  See: https://forums.raspberrypi.com/viewtopic.php?p=2088935#p2088935

- firmware: platform: clocks: Replace m2mc with hdmi for state machine clock on 2711

- firmware: bootloader: Fix automatic 64bit selection on Pi3s
  See: https://forums.raspberrypi.com/viewtopic.php?p=2089764#p2089764

- firmware: Handle 64-bitness of named kernels
  See: #1792

- firmware: gencmd: Add a fallback to mailbox interface if vchiq is not available

- firmware: arm_loader: Set local-bd-address if 6 zeroes found

- firmware: arm_loader: Really check for a zero local-bd-address
  See: raspberrypi/linux#5437

- firmware: arm_dt: Don't overwrite existing i2c aliases
  See: raspberrypi/linux#5428

- firmware: arm_loader: Reduce CMA warning severity
  See: #1807
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants