Skip to content
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

Rework GPIO and introduce PINCTRL API to support gpio, pinctrl DTS nodes #15611

Open
mnkp opened this issue Apr 23, 2019 · 9 comments

Comments

Projects
None yet
5 participants
@mnkp
Copy link
Collaborator

commented Apr 23, 2019

Introduction

As a general requirement we want Zephyr DTS to adhere to the specification as well as to use bindings compatible with those of the Linux kernel. To support gpio, pinctrl DTS node bindings the current GPIO driver needs to be reworked. We also need to introduce PINCTRL driver.

A GPIO driver is used to configure and control the pins directly by the application. A PINCTRL driver is used to configure the pins and route the signals to the internal hardware modules on the SoC.

Requirement to support gpio, pinctrl DTS node bindings used by the Linux kernel doesn't imply that we have to provide the same GPIO, PINCTRL API like Linux does. In fact, the APIs can be quite different. We only need to process configuration data provided by the gpio, pinctrl DTS nodes in an easy and convenient manner.

Problem description

Unlike virtually any other driver e.g. I2C, RTC, CAN, UART which functionality is confined to a single hardware module the pin configuration and control functions can be spread among multiple SoC components. The implementation can vary significantly between vendors or even SoC families.

As an example both Intel Quark and NXP/Freescale K64 have separate GPIO and pinctrl modules however functionality assigned to each module by the respective SoC family is quite different. I.e. in one case debouncing is controlled by GPIO in the other by pinctrl module.

Pin functions controlled by

Intel Quark GPIO module: value, direction, interrupts, debouncing, source (pio/ext)
Intel Quark pinctrl module: muxing, pullup, slew rate

K64 GPIO module: value, direction
K64 pinctrl module: muxing, pullup/pulldown, slew rate, open drain, drive strength, interrupts, debouncing, source (pio/ext)

Proposed change

The driver API should hide implementation details and make it possible to write portable code. I.e. it would be impractical to expect user writing application code to know if they need to use PINCTRL or GPIO driver to configure an output as an open drain. As such both GPIO and PINCTRL API should provide means to fully configure pin properties such as open drain, drive strength, input debounce, pull up or schmitt-trigger mode.

Update GPIO and introduce PINCTRL API to let Zephyr drivers and application code configure pins using data provided by gpio, pinctrl DTS nodes. The API should use naming convention / configuration options which directly relate to the gpio, (gpio.h) and pinctrl bindings defined by Linux DTS. Zephyr implementation should preserve the meaning of Linux defined configuration options. E.g. relation between DTS pinctrl node property drive-open-drain and corresponding feature of the Zephyr API should be straightforward to identify and have the same effect.

Detailed RFC

GPIO/PINCTRL driver should use the following flags as a parameter to *_configure function:

  • GPIO_ACTIVE_LOW, GPIO_ACTIVE_HIGH: indicate pin active state (logical value '1' in low state or logical value '1' in high state). gpio_pin_write/gpio_pin_read functions should write/read the logical pin value.
  • GPIO_OPEN_DRAIN, GPIO_OPEN_SOURCE: configure single ended pin driving mode
  • GPIO_PULL_UP, GPIO_PULL_DOWN: enable pin pull-up, pull-down
  • GPIO_INPUT: configure pin as input
  • GPIO_OUTPUT: configure pin as output. By default output is configured in push/pull mode. This behavior can be modified via GPIO_OPEN_DRAIN, GPIO_OPEN_SOURCE flags.
  • GPIO_OUTPUT_INIT_LOW/GPIO_OUTPUT_INIT_HIGH: initialize the output in a low/high state
  • GPIO_INPUT_DEBOUNCE: enable pin debounce circuitry

Linux DTS does not specify interrupt configuration within GPIO/PINCTRL node but rather a dedicated IRQ chip node. Zephyr GPIO API shall provide means to directly configure interrupts. Following base flags are proposed:

  • GPIO_INT_ENABLE
  • GPIO_INT_LEVEL
  • GPIO_INT_EDGE
  • GPIO_INT_LOW
  • GPIO_INT_HIGH

To make interrupt configuration easier for the end user the following convenience flags are proposed:

  • GPIO_INT_EDGE_RISING
  • GPIO_INT_EDGE_FALLING
  • GPIO_INT_EDGE_BOTH
  • GPIO_INT_LEVEL_LOW
  • GPIO_INT_LEVEL_HIGH

Where each of the convenience flags is defined as a combination of a base flag. E.g.

/** Trigger GPIO pin interrupt on level high. */
#define GPIO_INT_LEVEL_HIGH     (GPIO_INT_ENABLE  \
				 | GPIO_INT_LEVEL \
				 | GPIO_INT_HIGH)

New API functions

Following two functions are added to let the user directly control the pin level ignoring GPIO_ACTIVE_LOW flag. Writing value 1 to a pin will always set it to high output state, writing value 0 will always set it to low output state.

int gpio_pin_write_raw(struct device *port, u32_t pin, u32_t value);
int gpio_pin_read_raw(struct device *port, u32_t pin, u32_t *value);

Pin Controller API: TBD

Dependencies

When a new PINCTRL API is introduced the current PINMUX API can be deprecated.

Concerns and Unresolved Questions

The GPIO and PINCTRL drivers will significantly overlap their functionality since many driver features (e.g. pin drive strength, pull-up, pull-down, ability to configure output as on open source) need to be provided by both drivers. A better choice may be merging the two APIs into one.

@galak galak referenced this issue Apr 23, 2019

Open

Driver API review/cleanup/rework #5697

18 of 33 tasks complete
@pabigot

This comment has been minimized.

Copy link
Collaborator

commented Apr 25, 2019

Pull-up and pull-down input configuration flags appear to be missing.

GPIO_DIR_OUT_LOW/HIGH and GPIO_OUTPUT_INIT_LOW/HIGH appear to be redundant.

Some devices (Nordic GPIO, Semtex SX1509B) support drive strength configuration with very different capabilities and defaults. Slew rate was also mentioned. Unless every potential hardware capability can be anticipated with a generic API, there should be some mechanism to pass driver-implementation-specific flags along with the standard ones so vendor extensions can be controlled by applications that are aware of the underlying hardware.

It's unclear whether the flags used in the device-tree gpios composite's flags field are to be the same as the corresponding argument to a GPIO driver configuration function, or if some of the flags may only relevant for either device-tree or dynamic configuration.

The rework should clearly define at what point the flags specified in the device tree will be applied: when the GPIO driver is initialized (e.g. to come up in a low-power mode) or when whatever uses the corresponding GPIO is initialized (which would likely be a very different configuration). It may be useful to provide multiple sets of configuration flags for a specific GPIO, perhaps by leveraging the PINMUX concept.

Functions in the new GPIO and PINCTRL driver API should clearly document exactly what is meant by each flag, and exactly what behavior is expected from drivers that are given flags that represent a configuration that is not supported by the hardware (ignore, error, make up something, ...). The existing solution failed to do this, and the results have been unsatisfactory.

@b0661

This comment has been minimized.

Copy link
Collaborator

commented Apr 25, 2019

GPIO_ACTIVE_LOW, GPIO_ACTIVE_HIGH: indicate pin active state (logical value '1' in low state or logical value '1' in high state)

If this is the output configuration it should be renamed to GPIO_OUTPUT_ACTIVE_LOW/HIGH to avoid any misinterpretation.

GPIO_OPEN_DRAIN, GPIO_OPEN_SOURCE: configure single ended pin driving mode

How is push/pull operation configured?

GPIO_INPUT_DEBOUNCE: enable pin debounce circuitry (the debounce parameters, if any, should be set by a SoC specific DTS)

Why not have a set of generic debounce times that may fit a lot of SoCs (e.g. DEBOUNCE_NONE, DEBOUNCE_SHORT, DEBOUNCE_MEDIUM, DEBOUNCE_LONG). What short/ medium/ long means is dependent on the SoC and may even be specified in the DTS.

Linux DTS does not specify interrupt configuration within GPIO/PINCTRL node but rather a dedicated IRQ chip node. Zephyr GPIO API shall provide means to directly configure interrupts. Following flags are proposed:

GPIO_INT_EDGE_RISING
GPIO_INT_EDGE_FALLING
GPIO_INT_EDGE_BOTH
GPIO_INT_LEVEL_LOW
GPIO_INT_LEVEL_HIGH

GPIO_INT_EDGE_BOTH is just a waste of configuration space, it should be the combination of the GPIO_INT_EDGE_RISING and GPIO_INT_EDGE_FALLING flags.

@mbolivar mbolivar added this to In Progress in API review/cleanup/rework Apr 30, 2019

@mbolivar mbolivar moved this from In Progress to To Do in API review/cleanup/rework Apr 30, 2019

@mbolivar mbolivar moved this from To Do to In Progress in API review/cleanup/rework Apr 30, 2019

@mbolivar mbolivar removed this from In Progress in API review/cleanup/rework Apr 30, 2019

@mnkp

This comment has been minimized.

Copy link
Collaborator Author

commented May 7, 2019

Pull-up and pull-down input configuration flags appear to be missing.

Added to the issue description.

GPIO_DIR_OUT_LOW/HIGH and GPIO_OUTPUT_INIT_LOW/HIGH appear to be redundant.

I'm not sure how they made it there. Removed.

Some devices (Nordic GPIO, Semtex SX1509B) support drive strength configuration with very different capabilities and defaults. Slew rate was also mentioned. Unless every potential hardware capability can be anticipated with a generic API, there should be some mechanism to pass driver-implementation-specific flags along with the standard ones so vendor extensions can be controlled by applications that are aware of the underlying hardware.

I think we need to discuss drive strength, slew rate and debouncing configuration a bit more. In Linux DTS these are configured with a parameter.

  • drive-strength takes as argument the target strength in mA.
  • input-debounce takes the debounce time in usec as argument or 0 to disable debouncing
  • slew-rate takes as argument an integer value (unit seems to be unspecified)

If we want to stay true to the promise that Zephyr DTS description is compatible with Linux DTS then we would need to configure these parameters via a dedicated functions taking respected parameter as an argument. The GPIO_INPUT_DEBOUNCE flag would need to be removed. Setting drive strength in mA doesn't seem to be very practical, on the other hand that's probably the only way to do it in a generic way. We could provide some SoC specific convenience macros that would encode available drive strengths in mA as an easy to understand name.

It's unclear whether the flags used in the device-tree gpios composite's flags field are to be the same as the corresponding argument to a GPIO driver configuration function, or if some of the flags may only relevant for either device-tree or dynamic configuration.

The DTS configuration has to follow the rules specified in GPIO bindings. These are documented in Linux DTS GPIO bindings.

The rework should clearly define at what point the flags specified in the device tree will be applied: when the GPIO driver is initialized (e.g. to come up in a low-power mode) or when whatever uses the corresponding GPIO is initialized (which would likely be a very different configuration). It may be useful to provide multiple sets of configuration flags for a specific GPIO, perhaps by leveraging the PINMUX concept.

According to the Linux DTS automatic configuration of GPIO pins is achieved by means of gpio-hog property.

The GPIO chip may contain GPIO hog definitions. GPIO hogging is a mechanism
providing automatic GPIO request and configuration as part of the
gpio-controller's driver probe function.

That's independent from GPIO API but surly needs to be clarified.

Functions in the new GPIO and PINCTRL driver API should clearly document exactly what is meant by each flag, and exactly what behavior is expected from drivers that are given flags that represent a configuration that is not supported by the hardware (ignore, error, make up something, ...). The existing solution failed to do this, and the results have been unsatisfactory.

Yes, fully agree. This should be taken care of in the PR.

@mnkp

This comment has been minimized.

Copy link
Collaborator Author

commented May 7, 2019

GPIO_ACTIVE_LOW, GPIO_ACTIVE_HIGH: indicate pin active state (logical value '1' in low state or logical value '1' in high state)

If this is the output configuration it should be renamed to GPIO_OUTPUT_ACTIVE_LOW/HIGH to avoid any misinterpretation.

The flag is honored by gpio_pin_write and gpio_pin_read functions. I've clarified the description.

GPIO_OPEN_DRAIN, GPIO_OPEN_SOURCE: configure single ended pin driving mode

How is push/pull operation configured?

By default (no flags) output is configured in push/pull mode. I've updated the description.

Why not have a set of generic debounce times that may fit a lot of SoCs (e.g. DEBOUNCE_NONE, DEBOUNCE_SHORT, DEBOUNCE_MEDIUM, DEBOUNCE_LONG). What short/ medium/ long means is dependent on the SoC and may even be specified in the DTS.

I discuss the configuration of debounce time in the previous comment.

Linux DTS does not specify interrupt configuration within GPIO/PINCTRL node but rather a dedicated IRQ chip node. Zephyr GPIO API shall provide means to directly configure interrupts. Following flags are proposed:

GPIO_INT_EDGE_RISING
GPIO_INT_EDGE_FALLING
GPIO_INT_EDGE_BOTH
GPIO_INT_LEVEL_LOW
GPIO_INT_LEVEL_HIGH

GPIO_INT_EDGE_BOTH is just a waste of configuration space, it should be the combination of the GPIO_INT_EDGE_RISING and GPIO_INT_EDGE_FALLING flags.

Indeed, that was not well explained. I propose to have to have two sets of flags. The base set used by the drivers, fully sufficient to configure an interrupt. Since their usage may not be intuitive to the end user / may involve too much typing the second set are convenience flags. This is a single identifier with a straightforward name which combines and may be used in place of a set of base flags. I've updated the description.

@anangl

This comment has been minimized.

Copy link
Collaborator

commented May 13, 2019

@mnkp
I am still concerned about the flags GPIO_ACTIVE_* and their influence on gpio_pin_write and gpio_pin_read functions. As I already mentioned in #12651 (comment), I think it would be better to keep the current behavior of gpio_pin_write and gpio_pin_read, and to handle these flags outside of GPIO drivers. My main concern is the inconsistency between reading/writing the state of pins and configuring interrupts for them (I assume from your previous comment in this topic that interrupt triggers should still operate only on physical input levels). I think this will be just confusing for people - one will configure an interrupt to be generated on the raising edge on a pin, and the state reported for this pin in the interrupt handler will be either 1 or 0, depending on the GPIO_ACTIVE_* flag used in this pin configuration. And to properly configure the interrupt to occur when the input changes its state to active, one would still need to use the same GPIO_ACTIVE_* flag that was used when the pin was configured (and a piece of code that will handle it, so probably it would be convenient to provide a wrapper for this in GPIO API).

You replied to my comment:

I provided a bit more elaborate answer in my comment in #11880. In short GPIO_ACTIVE_LOW is just a different name for GPIO_POL_INV flag which controls the value returned by gpio_pin_write and gpio_pin_read functions.

I am not convinced that GPIO_ACTIVE_LOW in its current form is just a replacement for GPIO_POL_INV. As I understand it, the inversion of polarity should affect a given pin in all aspects, including the configuration of interrupts, just as if there was a hardware inverter connected to the pin. I tried to look at how the polarity inversion is handled in hardware currently supported by Zephyr's GPIO drivers:

  • for Semtech SX1509B, polarity affects the relation between IO[x] and RegData[x], and the interrupt configuration is based on RegData[x], so it is influenced by the used polarity
  • for TI CC13x0 and CC26x0, the documentation says only that the input/output can be configured as inverted, and interrupts can be generated for positive and/or negative edges on the IO, so I cannot tell what will be the actual behavior, and I don't have a piece of such hardware to check it (later on, for PWMs it is mentioned that output inversion affects the edge detection interrupts, but I'm not sure if any assumptions regarding GPIOs can be based on this)
  • for ESP32 again, I cannot tell from the documentation whether GPIO interrupts are generated for pin levels before or after the inversion is applied
  • for PCAL9535A, only the input pins can be inverted and the simplified schematic of the I/Os shows that the polarity inversion only affects the input port register data, it is not taken into account in the interrupt generation path
  • other drivers either do not support the GPIO_POL_INV flag or support it in software

And now I'm really puzzled regarding what would be the best way to handle GPIOs polarity inversion. 🙂
Could anyone provide other examples of hardware supporting this feature, or provide any other hints?

Anyway, I still think that the influence of the GPIO_ACTIVE_LOW flag on the behavior of gpio_pin_write and gpio_pin_read functions should be reconsidered.

@mbolivar

This comment has been minimized.

Copy link
Member

commented May 14, 2019

Hi @mnkp, I wanted to clarify my remarks about fast access functions during today's meeting.

I think that the _raw variants are good and should be included. The issue I am considering is a separate one that would be an enhancement to your existing work.

My main point is that it would also be useful for other device drivers that need fast access to GPIOs built into the SoC to have a generic API for accessing the output registers for these devices.

In other words, something like this:

struct device *gpio_port = device_get_binding(...);
u32_t *output_register;
u32_t my_bit = 3U;

gpio_get_output_register(gpio_port, &output_register);
*output_register |= (1U << my_bit);

To be equivalent to:

struct device *gpio_port = device_get_binding(...);
gpio_pin_write_raw(gpio_port, 3U, 1U);

except without the overhead, so that the bit manipulation on output_register could be done in a tight loop.

The inspiration is from the FastLED library's pin abstraction (https://github.com/FastLED/FastLED/blob/master/fastpin.h; also see PORTING.md in the same repository), which works on a variety of Arm SoCs, ESPs, and AVRs at the very least.

Of course, there are problems to consider:

  • not all GPIO devices support this: SoCs may not have a GPIO port with this type of register, I2C-based GPIO expanders don't have this type of access, etc. But it is a common enough pattern that I think it would be useful to allow GPIO drivers to implement it, and gpio_get_output_register() can always return an error when it's not available. This would make it easier to write efficient drivers for timing sensitive bit-banging protocols when possible, without re-writing SoC-specific versions that duplicate GPIO driver code as we do today in e.g. https://github.com/zephyrproject-rtos/zephyr/blob/master/drivers/led_strip/ws2812b_sw.c.
  • concurrent access to output_register and the GPIO APIs may be an issue as well. I think this is the responsibility of the driver author or application to avoid. And anyway, in designs where only a single core is accessing the GPIO port (the common case), accesses to output_register are likely to be done with interrupts disabled to ensure timing determinism.

Your thoughts are welcome. Thanks.

@mnkp

This comment has been minimized.

Copy link
Collaborator Author

commented May 14, 2019

@mbolivar thanks for the proposal.

I was hoping that removing the access_op from GPIO API would improve things significantly. It does improve things but there is still a fair share of pointer dereferencing going on. To test it I've implemented gpio_pin_write_raw on SAM E70 (ARM Cortex-M7) SoC which is a very friendly chip to work with.

static int gpio_sam_pin_write_raw(struct device *dev, u32_t pin, u32_t value)
{
	const struct gpio_sam_config * const cfg = DEV_CFG(dev);
	Pio *const pio = cfg->regs;

	if (value) {
		/* Set the pin. */
		pio->PIO_SODR = 1 << pin;
	} else {
		/* Clear the pin. */
		pio->PIO_CODR = 1 << pin;
	}
	return 0;
}

with default Zephyr compiler optimization options the implementation takes 11 assembly instructions (for comparison the gpio_sam_pin_write function takes 32 assembly instructions. Since there is also some overhead for calling the function to have truly fast GPIO access we definitely need to considered the type of API you mentioned.

I believe though that letting an application directly access the output_register will not be possible. There are SoCs that provide only output_set, output_clear registers. There are SoCs that provide only output_write register. There are SoCs that provide both. The internal layout of the registers is also not always consistent. We would probably need a set of inline functions which would present a consistent API but be implemented individually by the SoC. Similar to your proposal but writing to the register would happen via an inline function. We would need to rely on CMake to include the right header (and in this case also implementation) file.

@mbolivar

This comment has been minimized.

Copy link
Member

commented May 15, 2019

I believe though that letting an application directly access the output_register will not be possible. There are SoCs that provide only output_set, output_clear registers. There are SoCs that provide only output_write register. There are SoCs that provide both. The internal layout of the registers is also not always consistent.

Yes, I understand this is not a universally supported register type. However, I believe that the existence of the fastled library shows that there is a small set of "types" of GPIO output registers, which we can support through a common API. E.g. if gpio_get_output_register errors out, the driver could fall back on gpio_get_output_set_register(), gpio_get_output_clear_register(), etc., with coverage level depending on the driver. We could consider a query routine where the GPIO driver returns a bitmask of supported types.

We would probably need a set of inline functions which would present a consistent API but be implemented individually by the SoC. Similar to your proposal but writing to the register would happen via an inline function. We would need to rely on CMake to include the right header (and in this case also implementation) file

I'm not sure I can see clearly how some of these details would work in practice, but I look forward to seeing more details if you have them.

Thanks for your response!

@anangl

This comment has been minimized.

Copy link
Collaborator

commented May 21, 2019

@mnkp Regarding the question I was trying to signal yesterday on slack. It's about configuring interrupts for handling a button, for instance. An application will get the flags defined for the button in DTS through a a generated macro, like SW0_GPIO_FLAGS. Ideally, it would just pass the value to the gpio_pin_configure function to get the pin properly configured, just like in the case of the LED that you presented in your PR. But the flags controlling the interrupts generation are application specific - some applications will need to react on the input becoming active (button pressed), others on the input becoming inactive (button released), and in some cases, perhaps not buttons, there may be a need to react on the signal level, not an edge. Thus, I think that the flags related to interrupts should not be specified in DTS (like in your PR), but instead the GPIO API should provide some means to get the proper interrupt configuration based on the GPIO_ACTIVE_* flag used in DTS. Hence, I thought that we would need to add some macros or inline functions that would handle the translation. Or do you have some other vision of achieving this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.