Zephyr PS GPIO Driver Implementation

This page provides details on the implementation of the PS GPIO Zephyr driver.

GPIO Layers

The gpio driver does not use a subsystem. Instead, the Zephyr GPIO framework header (gpio.h) validates the parameters passed and calls the driver API.

Zephyr PS GPIO driver call flow: the application invokes gpio_pin_configure on the Zephyr GPIO framework, which calls the driver pin_configure callback.

Zephyr PS GPIO driver call flow.

PS/PMC GPIO Node

Node

PMC GPIO DTS
============

psgpio: gpio@f1020000 {
        compatible = "xlnx,ps-gpio";
        status = "disabled";
        reg = <0xf1020000 0x1000>;
        interrupts = <GIC_SPI 180 IRQ_TYPE_LEVEL
                        IRQ_DEFAULT_PRIORITY>;
        interrupt-names = "irq_0";

        #address-cells = <1>;
        #size-cells = <0>;

        psgpio_bank0: psgpio_bank@0 {
                compatible = "xlnx,ps-gpio-bank";
                reg = <0x0>;
                gpio-controller;
                #gpio-cells = <2>;
                ngpios = <26>;
                status = "okay";
        };

        psgpio_bank1: psgpio_bank@1 {
                compatible = "xlnx,ps-gpio-bank";
                reg = <0x1>;
                gpio-controller;
                #gpio-cells = <2>;
                ngpios = <26>;
                status = "okay";
        };

        psgpio_bank3: psgpio_bank@3 {
                compatible = "xlnx,ps-gpio-bank";
                reg = <0x3>;
                gpio-controller;
                #gpio-cells = <2>;
                ngpios = <32>;
                status = "okay";
        };

        psgpio_bank4: psgpio_bank@4 {
                compatible = "xlnx,ps-gpio-bank";
                reg = <0x4>;
                gpio-controller;
                #gpio-cells = <2>;
                ngpios = <32>;
                status = "okay";
        };
};


PS GPIO DTS
===========

psgpio: gpio@f19d0000 {
        compatible = "xlnx,ps-gpio";
        status = "disabled";
        reg = <0xf19d0000 0x1000>;
        interrupts = <GIC_SPI 20 IRQ_TYPE_LEVEL
                        IRQ_DEFAULT_PRIORITY>;
        interrupt-names = "irq_0";

        #address-cells = <1>;
        #size-cells = <0>;

        psgpio_bank0: psgpio_bank@0 {
                compatible = "xlnx,ps-gpio-bank";
                reg = <0x0>;
                gpio-controller;
                #gpio-cells = <2>;
                ngpios = <26>;
                status = "okay";
        };

        psgpio_bank3: psgpio_bank@3 {
                compatible = "xlnx,ps-gpio-bank";
                reg = <0x3>;
                gpio-controller;
                #gpio-cells = <2>;
                ngpios = <32>;
                status = "okay";
        };
};

Probe Data From Node

In driver we need to assign the compatible string to a zephyr macro, DT_DRV_COMPAT. If the assigned string matched with any one of the DT nodes along with status property okay then driver need to register the driver API to the GPIO framework using a framework structure.

Probe PS/PMC GPIO NODE

#define GPIO_XLNX_PS_DEV_INITITALIZE(idx)\
GPIO_XLNX_PS_GEN_BANK_ARRAY(idx)\
GPIO_XLNX_PS_DEV_CONFIG_IRQ_FUNC(idx)\
GPIO_XLNX_PS_DEV_DATA(idx)\
GPIO_XLNX_PS_DEV_CONFIG(idx)\
GPIO_XLNX_PS_DEV_DEFINE(idx)

DT_INST_FOREACH_STATUS_OKAY(GPIO_XLNX_PS_DEV_INITITALIZE);

DT_DRV_COMPAT

  • This macro holds the compatible string of the driver “xlnx_ps_gpio

DT_INST_FOREACH_STATUS_OKAY

  • This macro calls “GPIO_XLNX_PS_DEV_INITITALIZE(idx)” on all node with compatible “DT_DRV_COMPAT” and status “okay”

GPIO_XLNX_PS_GEN_BANK_ARRAY

  • This creates an array of device structure which holds all the child nodes(psgpio_bank<X>) device structure.

GPIO_XLNX_PS_DEV_CONFIG_IRQ_FUNC

  • This declare a definition of irq registration function.

GPIO_XLNX_PS_DEV_DATA

  • This declares a device specific data structure gpio_xlnx_ps_dev_data.

GPIO_XLNX_PS_DEV_CONFIG

  • This declare a device specific config structure “gpio_xlnx_ps_dev_cfg” and initialize them.

GPIO_XLNX_PS_DEV_DEFINE

  • This macro creates a device object during kernel system init.

  • gpio_xlnx_ps_init API is run by the kernel during system initialization.

gpio_xlnx_ps_init

  • Call irq configure.

Probe PS/PMC GPIO BANK NODE

#define GPIO_XLNX_PS_BANK_INIT(idx)\
static const struct gpio_xlnx_ps_bank_dev_cfg gpio_xlnx_ps_bank##idx##_cfg = {\
        .common = {\
                .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(idx),\
        },\
        .base_addr = DT_REG_ADDR(DT_PARENT(DT_INST(idx, DT_DRV_COMPAT))),\
        .bank_index = DT_INST_REG_ADDR(idx),\
};\
static struct gpio_xlnx_ps_bank_dev_data gpio_xlnx_ps_bank##idx##_data;\
DEVICE_DT_INST_DEFINE(idx, gpio_xlnx_ps_bank_init, NULL,\
        &gpio_xlnx_ps_bank##idx##_data, &gpio_xlnx_ps_bank##idx##_cfg,\
        PRE_KERNEL_1, CONFIG_GPIO_INIT_PRIORITY, &gpio_xlnx_ps_bank_apis);

DT_INST_FOREACH_STATUS_OKAY(GPIO_XLNX_PS_BANK_INIT);
  • gpio_xlnx_ps_bank_apis is a structure variable where we add all the api that need to be registered to framework.

gpio_xlnx_ps_bank_init

  • Disable interrupt and status register.

  • make all GPIO pins as input and make data as 0.

GPIO Framework

GPIO framework has a structure where drivers need to register their APIs by following the same protocol used in the structure that follows,

Freamework Structure

__subsystem struct gpio_driver_api {
        int (*pin_configure)(const struct device *port, gpio_pin_t pin,
                             gpio_flags_t flags);
#ifdef CONFIG_GPIO_GET_CONFIG
        int (*pin_get_config)(const struct device *port, gpio_pin_t pin,
                              gpio_flags_t *flags);
#endif
        int (*port_get_raw)(const struct device *port,
                            gpio_port_value_t *value);
        int (*port_set_masked_raw)(const struct device *port,
                                   gpio_port_pins_t mask,
                                   gpio_port_value_t value);
        int (*port_set_bits_raw)(const struct device *port,
                                 gpio_port_pins_t pins);
        int (*port_clear_bits_raw)(const struct device *port,
                                   gpio_port_pins_t pins);
        int (*port_toggle_bits)(const struct device *port,
                                gpio_port_pins_t pins);
        int (*pin_interrupt_configure)(const struct device *port,
                                       gpio_pin_t pin,
                                       enum gpio_int_mode, enum gpio_int_trig);
        int (*manage_callback)(const struct device *port,
                               struct gpio_callback *cb,
                               bool set);
        uint32_t (*get_pending_int)(const struct device *dev);
#ifdef CONFIG_GPIO_GET_DIRECTION
        int (*port_get_direction)(const struct device *port, gpio_port_pins_t map,
                                  gpio_port_pins_t *inputs, gpio_port_pins_t *outputs);
#endif /* CONFIG_GPIO_GET_DIRECTION */
};

Functional Implementation

pin_configure

  • Configure gpio pin as input or output based on user input.

  • In case of output mode configuration, based on user input output value is set.

port_get_raw

  • Read DATA\_<X> register.

port_set_masked_raw

  • Clear masked bits and write value into DATA\_<X> register.

port_set_bits_raw

  • Write 1 into DATA\_<X> register.

port_clear_bits_raw

  • Write 0 into DATA\_<X> register.

port_toggle_bits

  • Toggle bits in DATA\_<X> register.

pin_interrupt_configure

  • Disable interrupt or enable interrupt for a particular pin.

  • Configure interrupt type as level based or edge based trigger.

  • In case of edge based trigger we can further configure as rising edge or falling edge and both.

  • In case of level based we can further configure as rising 1 or falling 0 but not both.

  • Enable interrupt clear the pending INT_STAT register and then enable the interrupt.

manage_callback

  • Register the call back to get called when interrupt is triggered for a particular pin.

get_pending_int

  • Read INT_STAT\_<X> register value and clear the same register.