RTEMS GPIO API status

Last year I have added support for the Raspberry Pi GPIO I/O, but during the initial stages of that work it was identified that it would be useful to have a RTEMS-wide GPIO API. This lead me to generalize the RPI GPIO API so it could be used by other platforms. This post is a summary of the current features and capabilities of the API, and follows another post.

Initial note

Any reference to hardware registers are only meant to help the picturing process. The API does not deal with registers directly, instead it uses some abstractions which are explained next.

Nomenclature

GPIO bank – In the context of this API a GPIO bank refers to a GPIO register bank, where each bit controls a corresponding pin. Each BSP is expected to provide a value for the BSP_GPIO_PINS_PER_BANK constant, which should contain the amount of GPIO pins each bank can control (the register size). The API supports at most 32 pins per bank, and its the API that manages the banks, calculating the exact bank-pin pair from the received processor GPIO pin number. Because every bank has the same size, the API calculates how many banks there are by taking into account the total number of GPIO pins (through the BSP-defined BSP_GPIO_COUNT constant) and the number of pins per bank. Since the total count of GPIO pins might not be a multiple of the amount of pins per bank, it is possible that some platforms will have a shorter last bank (where some bits at the end of the register are reserved). The API accounts for this in the internal pin tracking data structure, so the API will never allow an access to a pin that does not exist.

Select bank – Each pin function is selected in hardware through registers, but since a pin may have more than the two basic functions (digital input and output), the selection registers usually require more than one bit per pin, meaning that each select register will control less pins that the other GPIO registers. For function selection directives the API uses a select bank, and each BSP can provide the amount of GPIOs a select bank can control through the BSP_GPIO_PINS_PER_SELECT_BANK constant.

Constants

Each BSP must provide at least the following constants:

BSP_GPIO_PIN_COUNT – Number of GPIO pins available to the API.
BSP_GPIO_PINS_PER_BANK – Number of GPIO pins per GPIO bank (usually the register size of the platform).

The next constant, although desirable, may be omitted:

BSP_GPIO_PINS_PER_SELECT_BANK – If the platform GPIO pin function selection registers control fewer pins than the usual GPIO bank (i.e.: the platform has more than 2 functions per pin, hence each pin will require more bits in the selection register, hence each selection bank will be shorter in the amount of pins), this constant can be provided. Also note that by providing this constant the API will call the rtems_gpio_bsp_multi_select function, so its implementation will have to be complete. This is more detailed in the following sections.

GPIO pin numbering

When referring to a specific GPIO pin the API expects from the callers the GPIO pin number in the processor. This eases the pin identification and avoids storing the bank-pin pair in memory which would lead to greater memory requirements, as every pin in a GPIO bank will have the same bank number (potentially each bank would end with 32 x 32-bits of redundant data, to store the same bank number). The API converts the processor numbering to a bank-pin pair (about two division operations), and that is what is sent to the BSP code.

A BSP can provide human-readable references to pins through constants or pre-defined configurations.

For instance a GPIO pin reference may look like:

#define GPIO_40 40

to refer to gpio pin 40 (processor numbering), but in the case of an on-board LED (or other pin that might have a pre-defined behavior), the BSP might provide a configuration such as:

const rtems_gpio_pin_conf act_led = {
{ /* Activity LED */
.pin_number = 16,
.function = DIGITAL_OUTPUT,
.pull_mode = NO_PULL_UP;
.interrupt = NULL,
.output_enabled = FALSE,
.logic_invert = FALSE,
.bsp_specific = NULL
};

Then an application can use the pin just by requesting the act_led configuration.

GPIO configurations

As mentioned previously, it is possible to define pin configuration tables to request any given pin configuration. It is also possible to define several versions for a given configuration, and update it as needed during run-time.

The data structures to be used are as follows:

typedef struct
{
/* Processor pin number. */
uint32_t pin_number;
rtems_gpio_function function;

/* Pull resistor setting. */
rtems_gpio_pull_mode pull_mode;

/* If digital out pin, set to TRUE to set the pin to logical high,
* or FALSE for logical low. If not a digital out then this
* is ignored. */
bool output_enabled;

/* If true inverts digital in/out applicational logic. */
bool logic_invert;

/* Pin interrupt configuration. Should be NULL if not used. */
rtems_gpio_interrupt_configuration *interrupt;

/* Structure with BSP specific data, to use during the pin request.
* If function == BSP_SPECIFIC this should have a pointer to
* a rtems_gpio_specific_data structure.
*
* If not this field may be NULL. This is passed to the BSP function
* so any BSP specific data can be passed to it through this pointer. */
void *bsp_specific;
} rtems_gpio_pin_conf;

If the pin should listen to some interrupt, then that configuration should go in the following structure:

typedef struct
{
rtems_gpio_interrupt active_interrupt;

rtems_gpio_handler_flag handler_flag;

/* Interrupt handler function. */
rtems_gpio_irq_state (*handler) (void *arg);

/* Interrupt handler function arguments. */
void *arg;

/* Software switch debounce settings. It should contain the amount of clock
* ticks that must pass between interrupts to ensure that the interrupt
* was not caused by a switch bounce.
* If set to 0 this feature is disabled . */
uint32_t debounce_clock_tick_interval;
} rtems_gpio_interrupt_configuration;

Each pin may only listen to one type of interrupt, although it is possible to listen to both edges or both levels at the same time. The handler_flag defines if the pin will have a single handler, or potentially more. At configuration time is only possible to define one handler, so any remaining handler should be installed with rtems_gpio_interrupt_handler_install.

Pin tracking

One role of the API is to keep track of the GPIO state and to synchronize the access to the GPIO banks. It uses an uni-dimensional array of the following structure:

typedef struct
{
rtems_gpio_function pin_function;

/* GPIO pull resistor configuration. */
rtems_gpio_pull_mode resistor_mode;

/* If true inverts digital in/out applicational logic. */
bool logic_invert;

/* True if the pin is on a group. */
bool on_group;

/* Interrupt data for a pin. This field is NULL if no interrupt is enabled
* on the pin. */
gpio_pin_interrupt_state *interrupt_state;
} gpio_pin;


This structure requires 8 bytes of memory per GPIO pin, which may increase if it has enabled interrupts. If it does it will be allocated in the interrupt_state pointer, and its definition is as follows:

typedef struct
{
/* GPIO pin's bank. */
uint32_t bank_number;

/* Currently active interrupt. */
rtems_gpio_interrupt active_interrupt;

/* Id of the task that will be calling the user-defined ISR handlers
* for this pin. */
rtems_id handler_task_id;

/* ISR shared flag. */
rtems_gpio_handler_flag handler_flag;

/* Linked list of interrupt handlers. */
rtems_chain_control handler_chain;

/* Switch-deboucing information. */
uint32_t debouncing_tick_count;
rtems_interval last_isr_tick;
} gpio_pin_interrupt_state;

The bank_number is here so that the threaded handler has a reference to the pin’s bank number, so it can acquire the lock while the interrupt is being handled.

Having an array allows for faster retrieval of the pin state, since they can be identified directly with the pin number. This reasoning assumes that no platform will have enough reserved pins (i.e.: pins that are not physically available), such that the memory wasted tracking these ghost pins is worse than fetching the pin state in a more custom data structure such as a tree.

Pin group

GPIO pins can be grouped together and used as a single entity. This can be useful to simplify GPIO operations (possibility to address multiple pins at once) or to bit-bang data buses and other GPIO-based interfaces.

To accomplish this each group has sets of pins, which can then be addressed as if they were one. The API allows to write and read data to a group, through the use of bitmasks, and it can do this because each group has separate sets for inputs and outputs. It also allows a group to have/use BSP specific functions, through a third set of pins.

A group can be defined using the following data structure:

typedef struct
{
const rtems_gpio_pin_conf *digital_inputs;
uint32_t input_count;

const rtems_gpio_pin_conf *digital_outputs;
uint32_t output_count;

const rtems_gpio_pin_conf *bsp_specifics;
uint32_t bsp_specific_pin_count;
} rtems_gpio_group_definition;

A group must have at least one set of pins filled with at least one pin, and while every pin in a given set must belong to the same GPIO bank, different sets can belong to different banks. The group operations actuate on the pins depending on the order they have in the group definition. For instance if the pins 1, 3, 7 and 13 are set as digital outputs in a given group and the value 0x5h (0101b) is written to the group, then pins 1 and 7 will be set with logical low, while 3 and 13 will be set with logical high. To ensure the fewer register accesses possible every operation from pin function selection to pin accesses is done through the API’s multi-pin functions. This means that reading the value of a group (which means reading the group’s digital inputs) will translate in a single register call (which is also a reason to have all pins in a set belong in the same bank). The group write function however translates into two register calls, as set and clear registers are usually separate (and that is how the API assumes it, by calling the BSP functions that do multi pin set and multi pin clear).

If the group includes pins with BSP-specific functions then the API only performs synchronization operations, and delegates the operation to the BSP through the following call:

rtems_status_code rtems_gpio_bsp_specific_group_operation(
uint32_t bank,
uint32_t *pins,
uint32_t pin_count,
void *arg
);

Any needed data to the operation, or any data to be retrieved through it should pass through the arg void pointer.

A group definition is sent to the API through the following call:

rtems_status_code rtems_gpio_define_pin_group(
const rtems_gpio_group_definition *group_definition,
rtems_gpio_group *group
);

The function parses the group definition to validate it and fills the following internal data structure:

struct rtems_gpio_group
{
rtems_chain_node node;

uint32_t *digital_inputs;
uint32_t digital_input_bank;
uint32_t input_count;

uint32_t *digital_outputs;
uint32_t digital_output_bank;
uint32_t output_count;

uint32_t *bsp_speficifc_pins;
uint32_t bsp_specific_bank;
uint32_t bsp_specific_pin_count;

rtems_id group_lock;
};

This structure is also the definition for the opaque type rtems_gpio_group, which the function rtems_gpio_create_pin_group instantiates and returns to the caller so it can refer to the group.

This structure keeps the pin sets for each function type, along with the corresponding bank numbers. The pin numbering stored here is relative to the corresponding bank.

Each group also has their own lock, so all group operations are synchronized.

Internally the groups are stored on a rtems chain, so each group is simply a node of that chain.

Error checking

The API returns rtems_status_code errors when needed, and the exact errors that each function might give are documented in the corresponding doxygen documentation. Additional information can be printed in some locations if the DEBUG constant is defined. The API also relies in some assertions in situations where the error should not happen (such as a timeout error while waiting for a semaphore with the NO_WAIT flag).

Parameter validation

Each function validates the data received as parameters, but most validations can be superfluous if a certain configuration was tested and is known to work. An application may use a certain GPIO configuration, and the API verifies if the configuration is valid every time it is used. However, if the configuration is to remain untouched, the API will waste time during execution to verify the same configuration over and over again. An approach to this could be to define a constant that would made this checkings optional, so they could be used during the development of the application, but as soon as the configurations stabilize this checks could be removed to avoid wasting resources during run-time.

Locking

Locking was added to the API by having a mutex per bank. Since each bank of pins corresponds to a register in hardware, it must be ensured that multiple threads/cores using the API do not stumble in a race condition while operating on pins within the same bank. At the same time the API tracks the GPIO pins status on an internal data structure, so it must also be ensured that the access to a pin status is atomic (having a mutex per bank ensures that only other banks pin states can be updated). Locks are always acquired before checking the pin state to avoid a race condition with another thread that might be requesting or changing the state.

Locks are obtained right before a critical section in every function that has them, and relinquishes them before any other API function call (there is no lock sharing) except if calling a BSP code function, even if that function will have to acquire it again. This eases the complexity of the code, and allows for GPIO context switches, as other pins in that bank may be processed in the meantime.

Interrupts

Interrupt handling in the API

At this time the API relies on threaded interrupt handlers. Each bank of pins (or interrupt vector) has an rtems interrupt handler, installed through rtems_interrupt_handler_install(), and each pin has a task (thread) that is created and started when interrupts are enabled on that pin through the API. After the initial setup each task enters on an infinite loop, which starts by putting the task to sleep.

Each bank has a rtems handler, while each pin has a threaded handler. Threaded handlers allow for minimal time spent on ISR context, but also means that the interrupt may not be handled right away. A possible solution may be to allow the API user to define if it wants threaded handling or not. Using threaded handlers also means that on a SMP setup multiple cores can process multiple interrupts at the same time. On a single core system, however, it would be better to have a task per bank, to minimize context switching.

Every time an interrupt is sensed on a GPIO bank, all interrupts on that bank are disabled and the hardware interrupt line on that vector is read. Every pin that shows as having a pending interrupt have their threaded handler called (since all tasks are sleeping it just sends a notification to wake up, avoiding the overhead of starting the task), and so the interrupt line can be cleared and interrupts enabled again on that bank.

Each pin’s task begins by applying the software switch debouncing function (if activated), and then all interrupt handlers associated with that pin are called in sequence. When everything is done the task’s infinite loop starts another iteration and puts the task to sleep until the next interrupt.

Interrupt management calls

The API relies on two functions to control interrupt generation on hardware, and provides other two for GPIO interrupt handler management.

For interrupt control it relies on the bsp_interrupt_vector_enable() and bsp_interrupt_vector_disable() function from the irq-generic API to enable and disable interrupt generation on a specified vector (GPIO bank). These are used to temporarily disable interrupts while they are being processed in the ISR handler, and are

For handler management (associate/disassociate interrupt handlers to a particular GPIO pin) the API provides its own functions, which operate on the pin’s rtems chain of handlers.

Possible improvements

Currently all interrupt driven actions are performed in threads, meaning that between the interrupt and the actual action some time have passed by. This behavior can be made optional by making the threaded handling an option, such that non threaded handlers would be called from ISR context. Having the two options allow for faster processing of an interrupt if the action it fires is small, or a delayed processing if the handler needs to block or can potentially take some time (both not compatible with ISR-time execution, where the system is only focused in the interrupt handling).

Another point is the amount of tasks/threads required in threaded handling, as pointed before.

API functions

API initializing

rtems_status_code rtems_gpio_initialize(void);

This function initializes the pin state structure and creates the bank locks. It uses an atomic flag to ensure that it is initialized only once.

GPIO group functions

rtems_gpio_group *rtems_gpio_create_pin_group(void);

rtems_status_code rtems_gpio_define_pin_group(
const rtems_gpio_group_definition *group_definition,
rtems_gpio_group *group
);

uint32_t rtems_gpio_read_group(rtems_gpio_group *group);

rtems_status_code rtems_gpio_write_group(
uint32_t data,
rtems_gpio_group *group
);

rtems_status_code rtems_gpio_group_bsp_specific_operation(
rtems_gpio_group *group,
void *arg
);




API functions for configuration tables:

rtems_status_code rtems_gpio_request_configuration(
const rtems_gpio_pin_conf *conf
);

rtems_status_code rtems_gpio_multi_select(
const rtems_gpio_pin_conf *pins,
uint8_t pin_count
);

rtems_status_code rtems_gpio_update_configuration(
const rtems_gpio_pin_conf *conf
);

rtems_gpio_request_configuration and rtems_gpio_update_configuration only parse the given configurations and perform the required API calls. Because the API knows the current state of every pin, it can compare the current state with the new one received through rtems_gpio_update_configuration and update it accordingly (if valid, of course).

rtems_gpio_multi_select allow several pins to be configured at the same time, or in the worst case, in a single API call. This worst case scenario may happen if the platform does not provide the needed information for multiple pin selection, in which case the API will cycle through the configurations, one by one.

Apart from multiple pin selection other configurations such as pull resistors or interrupt handling details is done a pin at a time (only the pin function selection is done in parallel). The advantage is that while a pin function selection takes usually two register accesses by a platform (read the selection register and change the required bits while maintaining the other pins data untouched), this function can use those two accesses to select a whole select bank. Without this function each pin selection would (or will if a platform happens to not support it) have access the registers two times per pin.

Also note that since each write to a bank implies a write to the corresponding register, multiple pin selection can only help if the pins being selected belong to the same selection bank, as access to multiple selection banks will always result in separate calls per selection bank.

GPIO digital I/O functions:

rtems_status_code rtems_gpio_multi_set(
uint32_t *pin_numbers,
uint32_t pin_count
);

rtems_status_code rtems_gpio_multi_clear(
uint32_t *pin_numbers,
uint32_t pin_count
);

uint32_t rtems_gpio_multi_read(
uint32_t *pin_numbers,
uint32_t pin_count
);

rtems_status_code rtems_gpio_set(uint32_t pin_number);

rtems_status_code rtems_gpio_clear(uint32_t pin_number);

uint8_t rtems_gpio_get_value(uint32_t pin_number);


API direct operations:

rtems_status_code rtems_gpio_request_pin(
uint32_t pin_number,
rtems_gpio_function function,
bool output_enable,
bool logic_invert,
void *bsp_specific
);

rtems_status_code rtems_gpio_resistor_mode(
uint32_t pin_number,
rtems_gpio_pull_mode mode
);

rtems_status_code rtems_gpio_interrupt_handler_install(
uint32_t pin_number,
rtems_gpio_irq_state (*handler) (void *arg),
void *arg
);

rtems_status_code rtems_gpio_enable_interrupt(
uint32_t pin_number,
rtems_gpio_interrupt interrupt,
rtems_gpio_handler_flag flag,
rtems_gpio_irq_state (*handler) (void *arg),
void *arg
);

rtems_status_code rtems_gpio_interrupt_handler_remove(
uint32_t pin_number,
rtems_gpio_irq_state (*handler) (void *arg),
void *arg
);



rtems_status_code rtems_gpio_disable_interrupt(uint32_t pin_number);

These functions allow for manual configuration of pins within the API, and stand as an alternative for the configuration based functions which parse a configuration and call these functions as needed.

API pin release functions

rtems_status_code rtems_gpio_release_pin(uint32_t pin_number);

rtems_status_code rtems_gpio_release_configuration(
const rtems_gpio_pin_conf *conf
);

rtems_status_code rtems_gpio_release_multiple_pins(
const rtems_gpio_pin_conf *pins,
uint32_t pin_count
);

rtems_status_code rtems_gpio_release_pin_group(
rtems_gpio_group group
);

These functions allow pin repurposing, by declaring to the API that a given pin or pins are now available for another function. In hardware terms the pin state will remain unchanged, with the only exception being if the pin has interrupts enabled, in which case interrupts are disabled, the interrupt_state pointer in the GPIO internal tracking structure is freed. Pull-up resistor state and the current pin function stay the same in hardware.

Software debouncing function:

rtems_status_code rtems_gpio_debounce_switch(
uint32_t pin_number,
int ticks
);


The switch debouncing function only applies to digital input pins with interrupts enabled. It works by recording the clock tick of the previously handled interrupt, and by requiring a certain number of clock ticks to pass before allowing the next interrupt to be handled.

Tristate pins

Tristate refers to the ability of a pin to be both a digital input or output pin, by switching the directions as needed. Since this may be BSP specific, a BSP may have this capability by using the BSP_SPECIFIC function.

BSP functions

Although the API is set to be generic it should not disregard platform-specific features. The API provides a series of function headers which a BSP will need to implement in order for the API to work with it, but at the same time it also allows platform specific data to reach BSP code and be processed by the BSP without bloating the API with features that most platforms do not support.

BSP/platform specific functionalities

To keep the API generic it only operates the two defining functions of a GPIO pin: digital input and output. However, most platforms rely on GPIO pins to perform functions ranging from ADCs to data buses such as I2C or SPI. To account for these additional functions (which may or may not exist on a given platform), the API provides hooks so applications/drivers can use them. For these the API role is limited to pin tracking, synchronization and interrupt management. The setup and control code for the BSP specific function is left to the BSP to implement.

The API also allows BSP specific data to be used with digital input and output pins, but the processing of that data is left to the BSP implementation.

This data is sent through void pointers, meaning that a BSP can define a data structure that can hold any information that it may require from an application, and then document it so GPIO users on that platform know what to send, and what to expect. Any BSP specific detail has to be documented by the BSP itself.

The list of functions that a BSP must implement is as follows:

Multiple pin operation functions:

rtems_status_code rtems_gpio_bsp_multi_set(
uint32_t bank,
uint32_t bitmask
);

rtems_status_code rtems_gpio_bsp_multi_clear(
uint32_t bank,
uint32_t bitmask
);

uint32_t rtems_gpio_bsp_multi_read(uint32_t bank, uint32_t bitmask);


These functions receive a bitmask with the pins that are to be operated in the given bank. Most implementations of these should be a simple write of the given bitmask to the given bank’s operation register. If not the bitmask may also be iterated.

Although the function receives a 32-bit mask, if the BSP defined a GPIO bank to have less than 32 pins then that will be the size of the bitmask.

Single pin operation function:

rtems_status_code rtems_gpio_bsp_set(uint32_t bank, uint32_t pin);

rtems_status_code rtems_gpio_bsp_clear(uint32_t bank, uint32_t pin);

uint8_t rtems_gpio_bsp_get_value(uint32_t bank, uint32_t pin);

Same as the previous functions, but only applying to a single pin.

GPIO pin function selection:

rtems_status_code rtems_gpio_bsp_select_input(
uint32_t bank,
uint32_t pin,
void *bsp_specific
);

rtems_status_code rtems_gpio_bsp_select_output(
uint32_t bank,
uint32_t pin,
void *bsp_specific
);

rtems_status_code rtems_bsp_select_specific_io(
uint32_t bank,
uint32_t pin,
uint32_t function,
void *pin_data
);


The select input and output functions should assign the given pin with the corresponding GPIO function in the platform’s hardware. The bsp_specific void pointer behavior is defined by the BSP being used. In the case of an platform specific GPIO function the BSP should define an integer identifier to refer to each function within the BSP, and that is the code used by an application to refer to it. This code is received as the “function” parameter in the rtems_bsp_select_specific_io function, and the pin_data void pointer is to be used to send additional data (same as the bsp_specific pointer in the other functions).

GPIO pull resistor setup:

rtems_status_code rtems_gpio_bsp_set_resistor_mode(
uint32_t bank,
uint32_t pin,
rtems_gpio_pull_mode mode
);

Pull resistors can only be configured on a single pin basis.

Interrupt operations:

The following functions should retrieve the current interrupt status on a given bank or interrupt vector (the API retrieves the vector number through the rtems_gpio_bsp_get_vector function), or clear any pending interrupts on a given vector.

uint32_t rtems_gpio_bsp_interrupt_line(rtems_vector_number vector);

void rtems_gpio_bsp_clear_interrupt_line(
rtems_vector_number vector,
uint32_t event_status
);

rtems_vector_number rtems_gpio_bsp_get_vector(uint32_t bank);

Functions to enable/disable interrupts on a given GPIO pin should also be implemented.

rtems_status_code rtems_bsp_enable_interrupt(
uint32_t bank,
uint32_t pin,
rtems_gpio_interrupt interrupt
);

rtems_status_code rtems_bsp_disable_interrupt(
uint32_t bank,
uint32_t pin,
rtems_gpio_interrupt interrupt
);

The following two functions may be implemented by just returning RTEMS_NOT_DEFINED, meaning that although they are not required for the API to work, it will have some limitations.

rtems_status_code rtems_gpio_bsp_multi_select(
rtems_gpio_multiple_pin_select *pins,
uint32_t pin_count,
uint32_t select_bank

);

If the BSP does not support multiple pin function selection the API will always do the selection one pin at a time. Supposing that an application tries to create a group with 10 output pins (for instance), having this function could do it in 2 register access. Otherwise it would result in 20 accesses.

rtems_status_code rtems_gpio_bsp_specific_group_operation(
uint32_t bank,
uint32_t *pins,
uint32_t pin_count,
void *arg
);

If a BSP does not provide support any additional GPIO function then the implementation of this function is not necessary, and any attempt to create a GPIO group with bsp specific functions will result on an error.

JTAG code loading and debugging

Before GSOC started and a few times until now I have tried to load and debug code on the Raspberry, without much success regarding RTEMS code. Alan Cudmore has suggested last year (http://alanstechnotes.blogspot.pt/2014/05/a-low-cost-jtag-debugger-for-raspberry.html) to use a FTDI FT2232H MiniMod along with OpenOCD, however although we get a JTAG connection, any code that is sent to the Pi fails to be transferred. I have checked the wiring, the JTAG setup program (the GPIO pins used for the JTAG connection have to be configured first) and OpenOCD configurations.

A great reference for this is the Dwelch67 repository (https://github.com/dwelch67/raspberrypi/tree/master/armjtag), which contains a JTAG setup program for the GPIO (armjtag.bin), OpenOcd configuration files for the FT4232H Mini Module and a test program (fastblink.elf). Alan adapted the configurations for the FT2232H (https://github.com/alanc98/rpi-minimod) which I also rechecked, and added the JTAG setup code in the RTEMS raspberry pi BSP code base.

At this point it is possible to create a JTAG connection, and have been able to control the Pi’s ACT_LED with memory word write (mww) instructions. I also managed to load Dwelch67’s test program fastblink.elf, which blinks the ACT_LED, and debug it using JTAG. To do this I recompiled the fastblink.elf program to include debug symbols (by changing the Makefile on Dwelch67’s repository) and sent it to the Pi (which is running the JTAG setup program) through GDB. I was able to set breakpoints and step through the code (although OpenOcd warns that some words were not transferred, and upon the first continue command GDB gives a “Memory write failure”), but whenever I tried to send a RTEMS binary (elf file) the transfer would fail. This may be related to some binary section memory address.

Also this JTAG configuration does not have a reset line, so the minimodule.cfg line for nSRST (ftdi_layout_signal nSRST -data 0x0020) can be left out to avoid an error message.

RTEMS GPIO API

This lengthy post proposes several possible changes to the current RPI GPIO API and some additions which can make it suitable to be used by any BSP, and is intended as a base for further discussion within the RTEMS community. The included code snippets are explanatory stubs, and all naming is likely subject to change.

This API should satisfy at least the following requirements:

  • Ability to manage digital input and output pins, providing an unified interface to any user application to perform this type of I/O independently of the BSP used (the specific pins used must, however, be managed by the application for each of the BSPs/platforms where the application is to be used);
  • Ability to add and manage additional types of I/O by a specific BSP, without affecting any other BSP or user application. The methods to deal with these new types are to be provided by the BSP to the API, during initialization;
  • The API must be able to manage interrupts, and allow gpio pins to be used as shared IRQ lines;
  • The API should provide function prototypes which are to be implemented by the BSPs to access their specific hardware;
  • The API should ensure atomicity where needed;
  • The API must allow for dynamic pin re-purposing;
  • The API should have at least a method for software switch debounce.

BSP API Initialization

Each BSP must provide its specific GPIO layout and I/O capabilities during the API initialization. At this point I am assuming that the only I/O that is universal to all platforms using a GPIO interface is digital I/O, meaning that any other I/O must be defined and managed by each requiring BSP as to avoid bloating the API. This may be done by filling a few structs such as the following.

GPIO layout/partitioning:

typedef struct
{
        /* Number of GPIO banks. */
        unsigned int gpio_bank_count;
        unsigned int pins_per_bank;
} gpio_layout;

Any BSP specific I/O types, in addition to plain digital I/O, which require the use of GPIO pins:

This may include interfaces such as analog I/O, data buses such as SPI or I2C, JTAG, and so on. Each I/O type would be defined through a struct, which at the same time could define an array of pins where this I/O could be performed. The purpose of this feature would be to allow the API itself to reflect the hardware capabilities, and warn the user if it tries to setup a pin with an I/O function it cannot provide. Since this implies an additional overhead (as the API would have to check the array every time a pin is requested to the API), maybe a BSP_OPT can be used to set this array as NULL to bypass this check (so the user would be responsible to ensure that each pin he uses can provide the intended I/O function).

typedef struct
{
        /* I/O function code, specified by the BSP. */
        unsigned int io_type;

        /* Matrix of bank/pins where this I/O can be performed. */
        unsigned int** pins;

        /* Pointer to the function which setups this I/O type. */
        gpio_error (*config_io) (gpio_pin conf);
} gpio_io_type;

The possible values for the io_type field would be defined by each BSP, together with the function (config_io) which the API will call whenever it receives a request to setup that I/O type on a pin.

The API creates and manages a linked list of gpio_io_type structs, so the specific types can be easily accessed.

API initialization

The API uses the following data structures:

typedef enum
{
        DIGITAL_IN,
        DIGITAL_OUT,
        BSP_SPECIFIC,
        NONE
} gpio_pin_function;

Every time a pin is requested to the API, it should state which function it should have. DIGITAL_IN and DIGITAL_OUT are the I/O functions that the API provides and manages on a generic and unified fashion to any application. If the function is set to BSP_SPECIFIC, then the API will know that it should call the BSP defined function to setup that pin. A function of NONE states that the pin is not being used, and may be requested to the API.

typedef struct
{
        /* The bsp defined function code. */
        unsigned int bsp_function;

        void* pin_data;
} gpio_specific_data;

If a pin is requested with the function BSP_SPECIFIC, this struct must be filled with the BSP defined function code, as well as any data it requires through the pin_data void pointer.

typedef struct
{
        /* Number of clock ticks that must pass between interrupts
        unsigned int clock_tick_interval;
} gpio_debounce_conf;

The gpio_debounce_conf should be used to set the debounce settings for a given pin. This is to be used by an helper function which performs software debouncing, avoiding a flood of interrupts on a switch release.

typedef struct
{
        /* Main ISR handling task for this pin. */
        rtems_id task_id;

        gpio_interrupt enabled_interrupt;

        gpio_handler_flag handler_flag;
  
        /* Linked list of interrupt handlers. */
        gpio_handler_list *handler_list;

        /* Software switch debounce settings. Should be NULL if not used. */
        gpio_debounce_conf *debounce;
} gpio_interrupt_conf;


If the pin will be listening to interrupts, the gpio_interrupt_conf must be used to do the setup. More details about the interrupt handling on the API is described in the section “Interrupt processing”.

The gpio_handler_list is a linked list of user/application defined IRS handlers and corresponding arguments, defined as follows:

typedef struct _gpio_handler_list
{
  struct _gpio_handler_list *next_isr;

  gpio_irq_state (*handler) (void *arg);

  void *arg;
} gpio_handler_list;

Next is shown the main data struct which defines the complete configuration of a gpio pin.

typedef struct
{
        unsigned int bank;
        unsigned int pin;

        gpio_pin_function function;

        /* True for active-low, false for active-high. */
        bool gpio_polarity;

        /* Pull resistor setting. */
        gpio_input_mode input_mode;

        /* Pin interrupt configuration. Should be NULL if not used. */
        gpio_interrupt_conf* interrupt;
        
        /*
         * TODO: other relevant generic fields?
         */

        /* Struct with bsp specific data.
         * If function == BSP_SPECIFIC this should have a pointer to
         * a gpio_specific_data struct.
         *
         * If not this field may be NULL. This is passed to the bsp function so any bsp specific data
         * can be passed to it through this pointer. */
        void* bsp_specific;
} gpio_pin;

For each bsp specific I/O function, the BSP should define a struct with the desired fields so that the API can pass it to the BSP defined function which will setup the pin. The bsp_specific pointer can also be used to pass bsp specific data for digital input or output processing functions within the BSP. If the specified function is set to BSP_SPECIFIC then a gpio_specific_data should be passed with this pointer.

The gpio_input_mode is defined as follows:

typedef enum
{
  PULL_UP,
  PULL_DOWN,
  NO_PULL_RESISTOR
} gpio_input_mode;

An user application must fill a gpio_pin struct for every pin it needs, and then give it to the following function which will configure the pin depending on the specified pin function.

gpio_error gpio_request(gpio_pin conf)
{
        switch ( conf.function ) {
               case DIGITAL_IN :
                    return gpio_setup_input(conf);
                case DIGITAL_OUT :      
                    return gpio_setup_output(conf);
                case BSP_SPECIFIC :
                    return bsp_setup_io(conf);
                case NONE:
                     return <some GPIO_error, as this is an unneeded call>
        }
}

Taking the gpio_setup_input as an example, it could be defined as:

gpio_error gpio_setup_input(gpio_pin conf)
{
        /* Parameter checking. */

        /* Check if pin is not being used. */

        return bsp_setup_input(conf)
}

The bsp_setup_function must be implemented by any bsp supporting the API, hence it will be different for every bsp/platform.

While the gpio_setup_output would be similar to the above, the bsp_setup_io function could be defined as:

gpio_error bsp_setup_io(gpio_pin conf)
{
        gpio_specific_data *sd = (gpio_specific_data) conf.bsp_specific;

        if ( sd == null ) {
           return <some gpio_error, as this function requires bsp specific data>
        }

        /* Check the linked list of gpio_io_type structs for the config_io function pointer which configures this type of pin. */

        /* Calls the found config_io function pointer, and sends along the conf struct. */
}

With a digital input or output pin configured and requested, an application may call the set, clear or get functions.

Taking the gpio_set function as an example:

gpio_error gpio_set(gpio_pin conf)
{
        /* Parameter checking. */

        return bsp_gpio_set(conf);
}

The bsp_gpio_set, which is to be implemented by the bsp, must contain the specific hardware code to set the given pin to the logical high.

When a pin is not needed anymore, it may be released through a function such as:

gpio_error gpio_release(gpio_pin conf)
{
        /* Parameter checking. */

        /* Remove any attached ISR. */

        assert(bsp_release(conf));

        /* Update API pin bookkeeping. */

        /* return */
}

User Application View Point

At this point we can evaluate how an application may look like while using the API.

Suppose we want a simple application that blinks some LED, and that it should work on BSP A and BSP B. Since they are two different platforms, the actual GPIO pin where the LED is connected in each platform will be different.

Before using a GPIO pin, it should be requested to the API. This is done by filling a gpio_pin struct with the pin and bank numbers, as well as the desired pin function and other relevant configurations.

gpio_pin led_pin = {
         #ifdef BSP_A
                .bank = 3,
                .pin = 1,
         #else
                .bank = 1,
                .pin = 27,
         #endif

         .function = DIGITAL_OUT,

         (...)
};

With the ifdef conditions in place the pin definition can be easily customized on a bsp/platform basis.

Having the pin definition, the application may now request it from the API:

assert(gpio_request(led_pin) == GPIO_SUCCESS);

And use it as needed:

while ( 1 ) {

      gpio_clear(led_pin);

      sleep(1);

      gpio_set(led_pin);

      sleep(1);
}

By continuing to use the led_pin struct to refer to the pin even after the request it is ensured that the user does not mistakenly requests pin x and tries to access pin y (as it may currently happens with the RPI API, where the actual pin number is used with the API functions). In the event that the led_pin structure is changed after the request, only the bank and pin number fields are considered outside the gpio_request call, and because the API has its own bookkeeping of which pins are being used and how, the user can be safe in the knowledge that any change in the led_pin struct done after the request is not taken into account, unless the pin is released and requested again with the new configuration.

API management data and functions

My view for the rtems-wide GPIO API is that is can be more than just an unified list of function calls, so that gpio applications can be used independently of the BSP. It should also:

  • monitor which pins are available, and which pins are being used (along with their current configuration);
  • handle the rtems side of interrupt handling, by creating/disabling ISR routines;
  • providing locking mechanisms where they are needed, such as in the functions creating/disabling interrupts;
  • provide helper functions, such as software switch debounce capabilities.

The current RPI GPIO API already provides much of the above, and it can be easily be made generic by carving out the RPI specific code.

Pin bookkeeping

The API maintains an array of gpio_pin structs, where the current execution status of GPIO is reflected. This is updated every time a pin is requested, and checked before using any pin.

Interrupt processing

The API has the following enums related to interrupt handling:

typedef enum
{
  FALLING_EDGE = 0,
  RISING_EDGE,
  LOW_LEVEL,
  HIGH_LEVEL,
  BOTH_EDGES,
  BOTH_LEVELS,
  NONE
} gpio_interrupt;

The gpio_interrupt enum defines the different types of interrupt enabled on a given pin. Not sure at this point if the BOTH_EDGES and BOTH_LEVELS are a common feature on most platforms.

typedef enum
{
  IRQ_HANDLED,
  IRQ_NONE
} gpio_irq_state;

The gpio_irq_state enum defines the return values of an ISR. It should state if the handler has handled the interrupt or not, and is useful when more than one ISR is defined on a single pin (gpio pin as a shared IRQ line for multiple devices).

typedef enum
{
  SHARED_HANDLER,
  UNIQUE_HANDLER
} gpio_handler_flag;

The gpio_handler_flag enum defines the two types of ISR: shared or unique. If a pin is requested to listen to a certain interrupt with the SHARED_HANDLER flag then multiple handlers (one per device connected to the pin) can be attached to that pin. Otherwise only one will be attached.

The ISR processing is currently all done through threaded IRQ’s, meaning that the ISR itself is not executed in ISR context, but in a separate task/thread. Each interrupt vector have an actual ISR (generic) which only task is to wake the correct ISR tasks, reducing the time spent in ISR context to a minimum.

void generic_isr(void* arg)
{
        /* Disables interrupt vector. */

        /* Calls memory barrier. */

        /* Calls function to probe the hardware to assess which interrupt(s) fired. */

        /* Wakes the ISR handler task on any pin that has a pending interrupt. */

        /* Clears all active interrupts on the hardware. */

        /* Calls memory barrier, before enabling the interrupt vector. */

        /* Enables interrupt vector. */
}

All hardware related functions are defined by the BSP’s. The API only defines the function prototypes so it can use them.

Each pin has a generic_handler_task, which is responsible for calling all the attached ISR handlers on that pin and check if any have acknowledged the interrupt and processed it. If no ISR has acknowledged the interrupt, treats it as a spurious interrupt.

Each ISR is responsible for probing their corresponding device (if multiple devices are connected to a single pin), and if their device has indeed generated an interrupt it then should be acknowledged and processed. The handled state is then returned to the generic_handler_task.

rtems_task generic_handler_task(rtems_task_argument arg)
{
        /* If the pin has a debouncing function active, call it. */

        while ( 1 ) {
              /* sleep until the generic_isr call's */

              /* retrieve the pins handler list. */

              /* Call all handlers in sequence, and count how many have handled the interrupt. */
        }

        /* If none has handled the interrupt, treat it as a spurious interrupt */
}


Enabling an interrupt

The API has an interrupt counter (initially zero), which is incremented every time an API pin is requested stating that it will be listening to some interrupt. A brief overview of the function handling this is shown below:

gpio_enable_interrupt(int dev_pin, gpio_interrupt interrupt, gpio_handler_flag flag, gpio_irq_state (*handler) (void *arg), void *arg)
{
        /* Parameter checking. */

        /* Acquire the API's interrupt lock. */

        /* If the pin has no interrupt, create and start generic_handler_task with the application-defined ISR routine. */

        /* If the interrupt_counter is zero, installs a generic ISR with rtems_interrupt_handler_install().
         * During this step the interrupt vector is disabled.
         */

        /* Enable the interrupt on hardware. */

        /* Update API bookkeeping. Increases the interrupt_counter. */

        /* Release the API's interrupt lock. */
}

Switch debouncing

The API keeps a record of the last clock tick where an interrupt was detected on a given pin. If the current clock tick is too close to that then ignores this interrupt.

int debounce_switch(int dev_pin)
{
        /* Get rtems_clock_get_ticks_since_boot() */


        if ( last_isr_tick < current_isr_tick ) {

                return -1;

        }


        last_isr_tick = current_isr_tick


        return 0;
}


Project intro

This blog post will introduce my GSOC project for this year, and detail some current ideas for its implementation. If everything goes better than expected there is some bonus work planned, but for now the project is as follows.

GPIO

For this project I will merge the last GSoC work and include/conclude some of the improvements made post-GSoC (can be seen at https://github.com/asuol/rtems/commit/eca62b41521da2e606f38320d31c83ec82e30ab6), namely:

Ability to have multiple arguments passed to an user-defined interrupt handler

All ISR handlers now receive a void pointer, which is to be used by the user to pass an user-defined struct with any data to be used by their user-defined ISR routine.

Have each interrupt being handled on a task

There are three handling levels through which an interrupt must pass until it is addressed:

  1. A generic and global (unique) ISR (generic_isr), installed with rtems_interrupt_handler_install the first time any interrupt is enabled in the API (through gpio_enable_interrupt), which is responsible for clearing the interrupt on the Pi and calling the pin’s ISR task (see following point);
  2. A generic task (generic_handler_task), which is created for every pin when the first interrupt is enabled on that pin (through gpio_enable_interrupt) and is responsible for calling the linked-list of user-defined ISRs (see next section);
  3. An used-defined ISR, which will finally address the interrupt (more details in the next section).

In this setup the interrupt is cleared in step 1, meaning that the time spent in ISR context is minimal. The tasks referred in step 2 and 3 are started right after they are created and put on wait (with rtems_event_receive), until they receive an event from the level below. After each processed interrupt they return to their waiting state, meaning that these tasks are always ready to start.

Each user-defined ISR is associated with a pin through a call to gpio_interrupt_handler_install during gpio_enable_interrupt, and the interrupt is only enabled on hardware in the end of gpio_enable_interrupt.

Ability to have multiple devices connected to a single GPIO pin

This allows for instance two devices to share a pin for interrupt handling. I started to work on this after the last GSOC, and the objective is to have multiple ISR routing (one per device) associated with a single GPIO pin, stored on a linked list associated with each individual pin. This allows a single GPIO pin to be shared among several devices for interrupt handling, with the requirement that the type of interrupt has to be the same for all connected devices (since the GPIO pin can only expect a single type of interrupt). This means that if more than one device is connected to a pin and an interrupt occurs it is impossible to know which device caused it.

For that reason, when an interrupt is detected the pin’s linked list of ISR routines is iterated and all user-defined ISRs there are called, and expected to return a gpio_irq_state informing if the device associated with that handler acknowledged or not the interrupt. In summary, an user-defined handler must:

  1. Exist for every connected device on a shared pin (and only one per device);
  2. Start by probing their device to check if it was the source of the interrupt. If the device acknowledges that it has indeed generated an interrupt, proceed with the interrupt handling and return an IRQ_HANDLED status at the end. If not return IRQ_NONE.

The current setup defines that each ISR on the linked list is called sequentially, but it can be done in parallel since each ISR is a separate task. When an ISR task returns the state information a counter is used to count how many ISR’s acknowledged the interrupt. If none acknowledged it flag it as a spurious interrupt.

 
Locking

Up to now the GPIO API has no locking mechanism in place. The main idea is to have a mutex lock for each GPIO pin, so that each API function starts by acquiring the locks to any pins it will be using at the start, and releasing them before returning.

This mutex would be included in the rpi_gpio_pin structure defined in include/gpio.h. It should also be made sure that the API is started only once, so the currently used static bool var “is_initialized” should be atomic (maybe using C11 atomic operations).

This will allow the GPIO functions to work on a multi-thread (and with PI 2, on a SMP) setup.

I2C

For I2C there is a need to port the last year GSOC I2C code to new the new I2C API which was introduced in November (https://lists.rtems.org/pipermail/devel/2014-November/009065.html), deprecating the previously used libi2c API for I2C buses. This new API, which is compatible with the Linux user-space API, divides in three modules:

  • I2C Bus Driver – Implements the generic bus code, which can be used by an I2C device to access/operate the bus (initiate/dentroy the bus, message tranfers, obtain/release the bus, …). Any I2C bus must be setup by using or adapting this driver, so the last year’s I2C code for the Raspberry Pi BSP will be used on this setup;
  • I2C Device Driver – The device API, which every I2C device will need to implement (device/device type dependent). Last year’s test device drivers may be ported to this new model to test the bus or new device drivers may be developed to test the bus;
  • Linux I2C User-Space API – This allows an user-space program to access an I2C bus or operate an I2C device.

SPI
The last year SPI code uses the libi2c API, which is still the API used for SPI buses although it was deprecated for I2C (as explained before). Although desired, there is no plan at this point for a new SPI API in RTEMS, so at this point the plan is to merge the last year SPI code (after some more testing, namely of the bi-directional mode which was implemented last year but not tested because the device I had to test it was somehow connected to the wrong power supply (ooops)) after the GPIO work is done.

SD Card

This section is more or less a transcript from the proposal, which explains the rationale behind my plan of working with the EMMC module for the SD host and card support.

The onboard SD card reader is connected internally to the following GPIOs (http://www.raspberrypi.org/documentation/hardware/raspberrypi/schematics/README.md):

GPIO47 – SD_CARD_DET – SD card detection line;
GPIO48 – SD_CLK_R – SD card clock line;
GPIO49 – SD_CMD_R – SD card command line;
GPIO50 – SD_DATA0_R – SD card data line 0;
GPIO51 – SD_DATA1_R – SD card data line 1;
GPIO52 – SD_DATA2_R – SD card data line 2;
GPIO53 – SD_DATA3_R – SD card data line 3.

These GPIOs can be operated (transparently) through the EMMC module, serving as an interface to the SD host and card and should be used to operate the SD cards in SD mode. To operate the card in SPI mode these GPIOs must be bit-banged to provide SPI communication, since these GPIOs are not connected to any SPI hardware (lowering even further the performance, as SPI mode is slower than SD).

In SPI mode the actual GPIO setup would be:

  • GPIO47 – GPIO input to detect the presence of the SD card (because in the PI the card must be always present, this can be done as a reassurance that the host controller recognizes the card);
  • GPIO48 – SD card clock line though a bit-banged spi clock line;
  • GPIO49 – SD card command line through a bit-banged spi MOSI (master-out-slave-in) data line;
  • GPIO53 – Chip select through a bit-banged spi chip select line.

SPI access to the PI SD card onboard reader is unusual, but there are some reports of success. As for the SD protocol in SPI mode, in the case of RTEMS there is a SD libchip driver using SPI mode (libchip/i2c/spi-sd-card.c) which could be used as a base, and improved if needed. The first step would be, however, to provide a big-banged SPI interface on the mentioned GPIOs using the libi2c API (which is still the SPI API used in RTEMS, and which is also at the base of the spi-sd-card driver).

An alternative would be to use the SD mode with the raspberry EMMC module, which I have already used in the past with success using the SD Host controller simplified specifications (v3.00)and the SD Physical layer simplified specifications (v3.01). This time a better approach would be to use the FreeBSD SD/MMC stack (such as the one Sebastian Huber ported to RTEMS (https://git.rtems.org/sebh/rtems-libusb.git/tree/rtems/freebsd/dev/mmc?id=3c82a1500da3192de2504a1360e065fd84a1f3a0)) which is also based on the simplified specifications, since it provides already a proven and tested implementation of the protocol instead of re-inventing the wheel as I did at the time (https://bitbucket.org/asuol/rtems-graduation-project/src). One thing to note is that the FreeBSD SD/MMC stack is currently on Sebastian’s private repository and not in libbsd yet, so that would have to be addressed.

Independently of the SD operating mode chosen the card’s partitions must be read and the filesystems loaded, so RTEMS can read and write from/to the card (in the past I have tested with a single FAT partition by reading an existing file’s content and writing a new file with some other content and checking the end result on a computer).

Model Identification

Information about the model, revision, memory, clocks, power state, temperature and other board specific details and managed in the Raspberry Pi by the Video Core (the Pi GPU) which is a black box within the Pi. The only way to communicate with the Video Core is through the Mailbox interface (https://asuolgsoc2014.wordpress.com/2014/05/26/mailbox-interface/), which works by sending request tags and retrieving the answers. In this interface there are several channels, such as the “Framebuffer” channel and the “Property tags (ARM -> VC)”, the latter being the channel of interest for model identification (https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface).

Knowing the board model and revision can help adjusting configurations that are revision specific, such as the I2C bus on the GPIO header (which uses the GPIOs 0/1 on revision 1 and GPIOs 2/3 on revision 2). It can also detect the actual memory size of the board, so it can be adjusted accordingly, and may also be used to get DMA channels.

A small set of functions such as rpi_get_board_revision() could return either the integer value of the revision (e.g.: 0009) or the human-readable value (in this case model A revision 2.0 (http://elinux.org/RPi_HardwareHistory#Board_Revision_History)), or could just print one of the values, or do all three depending on a parameter. Model identification and other relevant data would be probed at startup, and at any time if needed through a small set of functions. Functions can also be created to get SoC temperature readings or to dinamically adjust memory.

This identification would be made during the BSP startup at the bsp_predriver_hook function (RPI_BSP/startup/bspstart.c), along with the bus initializations according with the user’s preferences defined with BSP_OPTS.

Hello world (again)

I am participating again with RTEMS in this year’s GSOC iteration, so during this summer I will continue where I left last year and will continue to work with the Raspberry Pi in some other features.

The next post will introduce this new project in more detail.

Testing the project

This blog post will add to the project documentation on the RTEMS wiki by presenting actual test cases on the provided work. The test cases and device drivers used to test the work can be found on this github repository.

SPI bus

During the project I have used a microchip 23k256 SRAM memory device to test the SPI bus. The device driver I have created for it can be found here and the test case here. It is a minimal driver meant to just write a message to a fixed memory offset on the device and then to read it back.

Before using the SPI bus it must be enabled on the raspberrypi BSP configure.ac script (BSP_ENABLE_SPI) and its I/O mode selected (SPI_IO_MODE) to either polled or interrupt-driven access.

Connecting the Microchip 23k256 to the Raspberry Pi SPI bus.

The device must be connected to the pi like shown above, and care should be taken with the cable lengths of the MOSI, MISO and CLK lines, which should not be much greater than 20cm, above which errors can start to occur on the sent and received data through the bus.

The test case opens the device file associated with the device (which must be registered as mentioned on the wiki) and read and writes using the read() and write() calls using the device file file descriptor.

I2C bus

To test the I2C bus I have used a microchip mcp23008 GPIO port expander.  It uses the I2C bus to provide 8 extra GPIO pins and the device driver can be found here. The driver for this device is ioctl based, and is able to use all 8 GPIOs using polled-driven access. Future work for this driver will include support for interrupts on these GPIO pins and support for the microchip mcp23017 which is the same device but has twice the amount of GPIOS. These devices driver can be useful to have as they are commonly used with the raspberry pi to gain more GPIO pins.

Before using the I2C bus it must be enabled on the raspberrypi BSP configure.ac script (BSP_ENABLE_I2C) and its I/O mode selected (I2C_IO_MODE) to either polled or interrupt-driven access.

Connecting the Microchip mcp2308 to the Raspberry Pi I2C bus.

As with the SPI bus, the SDA and SCL cables should not be greater than 20cm in length.
The test case opens the device file associated with the device (refer to the wiki) and then performs ioctl requests using the ioctl() call to operate the device. In operates GPIO pin 4 as an output pin (connected to a LED) and GPIO pin 2 as an input pin (connected to a button). When the button is pressed the LED switches on.

GPIO

The GPIO pins can be used to perform digital I/O. Digital input pins may be polled or interrupt-driven. To use GPIO 2 and 3 the I2C bus must be disabled on the configure.ac script (at the root of the raspberrypi BSP), and the same applies to the SPI bus before using GPIO 7, 8, 9, 10 or 11.

Digital I/O test circuit.

 

Both the following test cases start by initializing the GPIO API, setting GPIO 2 and GPIO 17 as digital inputs (using the internal pull up resistor) and GPIO 3 and GPIO 7  as digital outputs.

LIBGPIO_TEST (Polled-driven)

GPIOs 3 and 7 are sent a logical 0 (so the LEDs start turned off), and then the Raspberry Pi polls forever the GPIO 2 and GPIO 17 pin for button presses. Because the internal pull up resistor is enabled, when a button in not being pressed the digital input pin values, or level, is 1, and when a button is pressed the pin level is 0. The test case reads the GPIO 2 pin level, and when the level is 0 it sets GPIO 3 , so the LED lits up, otherwhise GPIO 3 pin is cleared (and the LED is off). The same aplies to GPIO 17 and GPIO 7.

LIBGPIO_TEST_IRQ (Interrupt-driven)

The test case calculates how many clock time exist per second,  which will be used to debounce the swritches through sofwtare. GPIOs 3 and 7 are sent a logical 0, and the GPIO API debounce function is attached and set to require a wait between interrupts of 50 miliseconds. Both digital input pins (GPIO 2 and 17) are configured to generate a interrupt both on rising and on falling edge, so every time a switch is pressed or released a interrupt is generated. This interrupt will call the functions edge_test_1 and edge_test_2 respectively which will read the input pin values and depending on the button condition will turn on the corresponding LED. The digital output GPIO 2 is also configured to generate an interrupt on high level. This means that when the pin is high (meaning that the LED is on), the function level_test is called, which prints “LED ON” on the console.

Testing the GPIO API

As seen in the previous post, the current GPIO API can perform digital I/O on the Raspberry Pi and can setup a JTAG interface. To see these features in action, two test cases were made available: LIBGPIO_TEST and LIBGPIO_JTAG.

LIBGPIO_TEST

This test can be found at

https://github.com/asuol/rtems/blob/GPIO_API/testsuites/samples/LIBGPIO_TEST/init.c

and assumes the following configuration of the Raspberry Pi GPIO pin header:

Digital I/O test circuit.

The test case initializes the GPIO API, and sets GPIO 2 pin as a digital input (using the internal pull up resistor) and GPIO 3 pin as a digital output. The GPIO 3 pin is sent a logical 0 (so the LED starts turned off), and then the Raspberry Pi polls forever the GPIO 2 pin for a button press. Because the internal pull up resistor is enabled, when the button in not being pressed the GPIO 2 pin value, or level, is 1, and when the button is pressed the pin level is 0. The test case reads the GPIO 2 pin level, and when the level is 0 it sets GPIO 3 pin, so the LED lits up, otherwhise GPIO 3 pin is cleared (and the LED is off).

LIBGPIO_JTAG

This test can be found at

https://github.com/asuol/rtems/blob/GPIO_API/testsuites/samples/LIBGPIO_JTAG/init.c

and it just setups the Raspberry PI GPIO header for JTAG.

The Raspberry Pi GPIO header wiring setup and host computer setup to use the JTAG interface have been described by Alan Cudmore at https://github.com/alanc98/rpi-minimod.