wiki:GSoC/2015/RaspberryPi_peripherals_and_SD_card

Version 1 (modified by André Marques, on Aug 21, 2015 at 12:02:49 PM) (diff)

--

Raspberry Pi Low Level Peripherals and SD Card

Students: André Marques.

Mentors: Alan Cudmore, Lou Woods, Jeff Mayes and Josh O'Guin.

Introduction: This project has provided RTEMS with a generic GPIO API and gave the Raspberry Pi BSP GPIO, I2C and SPI support, while giving some good head start on the SD card support.

Requirements: This project requires some familiarity with the RTEMS codebase, low level programming in C and some hardware knowledge.

Project Contributions

This page details the outcome of this GSOC project. A project intro can be found at my blog (a resume of the project proposal) in [1].

Additional information can be found on the development blog [2] and github [3].

RTEMS GPIO API

This section intends to document the RTEMS GPIO API developed during this project, which is already in the RTEMS tree and in use by both the raspberry and beagle BSPs. The development blog contains several posts where the evolution of the API throughout the project can be seen.

This section is intended to be used in the future as formal documentation for the API in some way, and is organized in three topics:

  • API operation;
  • BSP developer manual;
  • User manual.

API operation

This section will detail the inner workings of the API, how it processes the received data and how it manages the requests between an user application and a given hardware platform.

The API provides functions to user applications, and at the same time provides prototypes for functions that a BSP should implement before the API can be used. Each BSP function have a well defined purpose, so the BSP developer only has to fill in their specific GPIO hardware calls. The API acts as an abstraction layer between user and BSP code, meaning that applications can be used with different platforms with minimal modification (i.e.: pin numbers will probably vary, but they can be accounted for in application configuration tables).

GPIO bank nomenclature

In hardware GPIO pins are usually organized in groups called 'bank's which map to hardware registers of the same size where each bit correspond to a pin on that bank. This means that operating on a given pin requires one or more write/read operations on a given register, which can lead to race-conditions between pins within the same register/bank. For that reason the API also organizes the pins in banks, each with an individual lock (more details in the locking subsection) that is held during any given operation.

The API has two bank concepts:

GPIO bank: this is the type of bank described earlier. The amount of pins each bank can have is defined by the BSP_GPIO_PINS_PER_BANK constant (up to a maximum of 32 pins), and the amount of banks used is calculated by the API by dividing the total amount of pins for the amount of pins per bank. It also takes into account platforms where the number of pins per bank is not a multiple of the total number of pins, resulting in a shorter last bank.

GPIO select bank: given the description above for a bank we can say that to the API a bank is a register where each bit corresponds to a pin. However, since GPIO pins may be programmed for additional functions other than the basic digital input and output, the function selection registers may require more than one bit per pin to accurately select a function to a given pin. In these cases the platforms will define more registers allocated to the function selection operations than to the other operations, and so the API incorporates that through the concept of a select bank. This bank is managed internally to the API, and the amount of pins each of these banks may have is defined through the BSP_GPIO_PINS_PER_SELECT_BANK constant. Note, however, that this constant is optional and only affects the behavior of the rtems_gpio_multi_select function, which allows for multiple pin function selection through a single GPIO hardware call (calling the rtems_gpio_bsp_multi_select function). If the constant is not given then the pins are selected one by one through rtems_gpio_request_configuration.

Constants

Each BSP must provide at least the following constants (through bsp.h or maybe a BSPOPT):

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 can have have more that the two basic functions for a GPIO pin, it is likely that each function selection register will control fewer pins than the usual GPIO bank. By providing this constant the API can assign multiple GPIO functions on the fewer hardware calls possible when calling rtems_gpio_multi_select. Also note that by providing this constant the API may call the rtems_gpio_bsp_multi_select function, so its implementation will have to be complete. This is more detailed in the following sections.

Care should also be taken as each GPIO bank requires a semaphore, so depending on the number of banks used by a platform the application may have to adjust the confdefs.h CONFIGURE_MAXIMUM_SEMAPHORES constant.

GPIO pin numbering

When referring to a specific GPIO pin the API expects 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 when needed.

BSP developers may still provide constants and pin configurations for applications to refer to, instead of hard coded 'magic' pin numbers. More details in the BSP developer manual section.

GPIO configurations

The API supports configuration tables, allowing for better platform management in applications. More details in the User manual section.

Pin tracking

One key role of the API is to keep track of the GPIO pins 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 have enabled interrupts that information will be allocated in the interrupt_state pointer, and its definition is as follows:

typedef struct
{
  /* Currently active interrupt. */
  rtems_gpio_interrupt active_interrupt;

  /* 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;

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

Apart from single pin operations, the API also allows operations on groups of pins. This can be useful to simplify GPIO operations (i.e.: address multiple pins at once) or to bit-bang data buses and other GPIO-based interfaces.

Pin groups are completely managed within the API, and used by applications through an opaque pointer. They request a pin group and provide a group definition (refer to the User manual section) to the following function:

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

This function then parses the configuration to the 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;
};

A group is defined by at least one and at most three sets of pins, one set per pin function. All pins within a pin set must belong to the same pin bank (the actual pin numbers stored on the structure are always relative to the registered bank, avoiding further conversion from processor pin number to the bank-pin pair), but different pin sets may belong to different banks. All operations on a pin group are done on the corresponding pin set (i.e.: reading a group will return the values of all pins in the digital_inputs set), and any operation on the group requires the group_lock to be held.

This means that any operation on a group requires both the corresponding set bank lock and group lock to be held.

The API uses a rtems chain to store all pin groups, so each pin group is a node of that chain. Since the pin definition is an opaque type, when an application requests a pin group it receives a pointer to that pin group, which it passes to the API whenever it requires an group operation. This eliminates the need to iterate through the chain to access pin group data, and eases the process of group addition and removal.

The group operations actuate on the pins accordingly to 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 hardware 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 to 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 data needed for the operation or any data to be retrieved goes through the arg void pointer.

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 function 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).

Locking

Locking is implemented on the API through a mutex (binary semaphore) per bank. Since each bank of pins maps 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.

Interrupt handling in the API

As noted previously, a GPIO bank is composed of several pins. Each bank also has an interrupt vector associated which is triggered every time an interrupt is sensed on any pin within that bank. On the other hand applications usually rely on interrupts on specific pins. Each pin may have more than one handler if more than one device is connected to the same pin, in which case each device will have a handler which should begin by probing their corresponding device to know if the culprit is their device, and if so handle it.

Multiple handlers per pin the API allows GPIO pins to be used as a shared IRQ line for multiple peripheral devices.

To summarize, all pins on a GPIO bank will trigger a common vector, each pin being a separate interrupt source that may in turn have one or more handlers of their own. This does not mean, however, that the pins interrupt handling is completely independent as although each pin has their own handler, we can not simply call all pin handlers on a GPIO bank every time a interrupt is sensed on the vector, as the pins themselves do not have interrupt state data.

The API installs one interrupt handler per GPIO bank to which job is to read the bank's interrupt line in the form of a bitmask, iterates the pins with pending interrupts and call the corresponding pin's handlers. Each pin has a list of handlers (defined by the applications) which are called in sequence. If an interrupt is enabled on a pin it always has at least one handler associated, but in the case of a shared IRQ line it may have multiple handlers - one per device connected to the pin.

Each handler is expected to return an handled state, but even if the first handler assumes the interrupt the remaining handlers/devices are still called/queried. If no handler assumes the interrupt the API treats it as a spurious interrupt.

The API installs a system interrupt handler for a GPIO bank when an application first tries to enable an interrupt on one of its pins, and is removed when the last interrupt is disabled on a pin from that bank. This system interrupt handler may be a regular handler (installed through rtems_interrupt_handler_install), meaning that the application handlers are called from ISR context, or may be a threaded handler (through an interrupt server) meaning that the application handlers are called from a thread/task. Threaded handlers allow for minimal time spent on ISR context, but also means that the interrupt may not be handled right away.

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 handler(s) called, and when only when that is finished can the interrupt line be cleared and interrupts enabled again on that bank.

Before calling the pin application handler(s) the API handler begins by applying the software switch debouncing function (if activated), and then all interrupt handlers associated with that pin are called in sequence.

BSP developer manual

To use the API a BSP should provide their implementation for the BSP specific functions required by the API.

This section will detail what the API requires from a BSP.

Using the API

To use the API the BSP Makefile should include the API source and header for compilation.

The source file is located at rtems-root/c/src/lib/libbsp/shared/gpio.c and the header at rtems-root/c/src/lib/libbsp/shared/include/gpio.h.

The API header should be used as a BSP header.

BSP-defined functions

Every function that should be implemented by a BSP can be identified in the GPIO API header file by the rtems_gpio_bsp_* that starts the function name. The doxygen documentation on the header is clear of what is expected from every such function.

Platform GPIO identifiers

Since the API refers to GPIO pins by their processor pin number and can handle full configuration tables, a BSP can provide human-readable references to pins through constants or pre-defined configurations.

For instance a GPIO pin reference may look like this:

#define GPIO_40 40

or

#define ACTIVITY_LED 40

to refer to GPIO pin 40 (processor numbering).

If a GPIO pin is connected to an on-board peripheral (i.e.: an on-board LED) the BSP might provide a complete GPIO configuration such as the following:

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

These configurations can be made available through a BSP header.

User manual

This section will develop more on how an user application or device driver can use the API. More details on the functions can be seen on the API header doxygen documentation in [4].

Initializing the API

API initialization is done by calling the following function:

rtems_status_code rtems_gpio_initialize(void);

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

Pin configurations

The GPIO API makes it possible to define pin configuration tables to request any given pin configuration. It is also possible to define several configurations for a given pin, 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;

  bool threaded_interrupts;

  /* 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 either both edges or both levels at the same time. The handler_flag defines if the pin will have a single handler, or potentially more (IRQ shared line among several peripheral devices). At configuration time is only possible to define one handler, so any remaining handler should be installed with rtems_gpio_interrupt_handler_install.

The interrupt configuration is separate from the base configuration table as not all pins will have interrupts (i.e.: output pins), so it allows for better modularity. In the event that multiple pins share the same applicational interrupt handler then the same interrupt configuration can be shared among those pins.

These configurations can be defined in configuration files included by applications, so if an application is to be used in multiple platforms the application can specify the number of pins and the requirements for those pins for which each affected BSP would provide a configuration file. The application can then be used unmodified just by pulling the right configuration file at compile time.

The API deals with configurations through the following set of functions:

rtems_status_code rtems_gpio_request_configuration(
  const rtems_gpio_pin_conf *conf
);

rtems_status_code rtems_gpio_update_configuration(
  const rtems_gpio_pin_conf *conf
);

rtems_status_code rtems_gpio_release_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_release_multiple_pins(
  const rtems_gpio_pin_conf *pins,
  uint32_t pin_count
);

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 allows several pins to be configured at the same time (single hardware call), or in the worst case, in a single API call (but using one hardware call per pin). 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 are 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 (in terms of register accesses) if the pins being selected belong to the same selection bank, as accessing multiple selection banks will obviously result in distinct calls per selection bank. This is however all managed by the API, and applications should use this function as it not only can ease the code (all pin configurations can be made in a single call) but also at the worst case scenario it translates to a series of rtems_gpio_request_configuration calls, while in the best case it can perform some register access optimizations for BSPs with support for it.

When an application no longer requires a pin and wishes to repurpose its function they can be released from the API through either rtems_gpio_release_configuration or rtems_gpio_release_multiple_pins. After being released the pins can be requested again to the desired new function, all during run-time. If the pin function stays the same but only some configuration details change then it can also be accounted for in applications by requiring multiple configurations for a single pin and by simply calling rtems_gpio_update_configuration when needed. If the API detects that the new configuration has some different configuration parameter (other than the function) the API itself updates the current configuration.

Digital I/O

These functions calls are to be used to operate pins configured as digital input or output pins. In the case of the multi-pin functions they return/receive a bitmask with the corresponding logical values to be retrieved/applied in the same order as the given array of pins. To applications a logical high is always the value 1 and a logical low is always 0. When requesting a pin an application can also decide to invert the pin logic, on which case if the application sets that pin the API would convert it to a clear operation.

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);

int rtems_gpio_get_value(uint32_t pin_number);

API direct operations

Although the API can be operated through configuration tables, it can also be used with direct instructions using the following set of functions:

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_release_pin(uint32_t pin_number);

rtems_status_code rtems_gpio_debounce_switch(
  uint32_t pin_number,
  int ticks
);

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,
  bool threaded_handling,
  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);

The API uses these functions internally, and applications for these functions by user applications may include, for instance, shell programs to control GPIO hardware via direct commands.

API pin release functions

The functions mentioned previously to release pins from the API 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

The switch debouncing function (rtems_gpio_debounce_switch) 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.

Pin groups

An application can define groups of pins and perform operations on them as if it was a single pin or entity (as a group may in fact be made of pins with different functions).

A group is defined by an application 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;

As shown a GPIO group is defined by sets of pins, one set per function type. Each group must have at least one set, but may have up to three sets. Every pin within a set must belong to the same GPIO bank, but different sets can have pins from different banks. This also means that a single set can have at most 32 pins, as that is also the limit of a GPIO bank. All operations within a group are synchronized, so only one operation in the whole group can be executed at any given time, and there are four operations available for groups, one per pin set plus the group release function.

To define a pin group an id must first be requested from the API with the following function call:

rtems_gpio_group *rtems_gpio_create_pin_group(void);

This id should then be used with the API on all group operations.

Having the id the application may send in their configuration to the API using the following function:

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

With the group defined there are a total of four possible operations:

rtems_status_code rtems_gpio_write_group(
  uint32_t data,
  rtems_gpio_group *group
);

uint32_t rtems_gpio_read_group(rtems_gpio_group *group);

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

rtems_status_code rtems_gpio_release_pin_group(
  rtems_gpio_group *group
);

The write and read operations receive/return a 32-bit bitmask which is to be applied to the set pins in the same order as defined in the group configuration.

The bsp specific function is, as the name implies, bsp specific. If a BSP provides a GPIO function other than digital I/O that can be used by applications in a group then they will/should provide further documentation on what should be passing in the arg void pointer, which may include among other data the actual bsp specific function that is to be applied. The API simply validates the data received (except the void pointer) and transmits that information to the BSP code.

Sample applications

Sample applications using this API can be found on my raspberry pi testing repository:

https://github.com/asuol/RTEMS_rpi_testing/tree/master/GPIO

Next is a short summary of what each test case uses/tests. All of these tests use the same circuit requiring two switches and two leds. They poll the switches or wait for an interrupt to lit the LEDs accordingly, with some variations (i.e.: the group test does not require the switches).

  • LIBGPIO_MULTI_TEST - Uses a configuration table to configure the pins, polling the switches;
  • LIBGPIO_MULTI_TEST_IRQ - Uses a configuration table to configure the pins with interrupts on both edges for both switches;
  • LIBGPIO_TEST - Uses direct API calls to poll the two switches;
  • LIBGPIO_TEST_IRQ - Uses direct API calls to enable interrupts on both edges of the switches;
  • LIBGPIO_TEST_GROUP - Defines a group with a output pin set, and writes a value to the group. This lits the LEDs accordingly to the value written.

The following test case is raspberry pi specific:

LIBGPIO_JTAG - Setups the JTAG pin interface on the raspberry pi. More information on JTAG and the PI can be seen in my blog in [5].

Potential Improvements

This section provides some quick drafts on potential future improvements on the API.

Parameter validation

Each function validates the data received as parameters, but most validations can be superfluous if a certain application 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 checking optional, so they could be used during the development of the application, but as soon as the configurations stabilize these checks could be removed to avoid wasting resources during run-time.

Interrupt handling SMP support

Using threaded handlers means that on a SMP setup multiple cores can process multiple interrupts at the same time. On a single core system, however, it is better to have a task per bank, to minimize context switching (as it currently is).

A SMP system could benefit of multiple tasks/interrupt servers to allow interrupt handling parallelism, although the current interrupt server implementation only allows a single server in the whole system. In this situation it would also be useful if the vector was re-enabled as soon as possible so any interrupts generated during the handling of a pin's interrupt can be quickly handled in parallel, instead of waiting from the previous interrupts to be processed - remember that in a GPIO vector each pin interrupt is independent - unless it is an interrupt on the same pin).

I2C bus driver

In the last year I have developed an I2C bus driver for the raspberry pi using the libi2c API, however in the meantime a new API ported from Linux (I2C User-Space API) is now in use and so the previous bus driver was ported.

The new API allows a bus driver to provide several levels of functionality to device drivers, namely:

  • I2C_FUNC_I2C - The basic I2C support. All bus drivers using the new API must support this functionality. It also states that the bus driver must recognize/process the message flag I2C_M_RECV_LEN, which is detailed next;
  • I2C_FUNC_10BIT_ADDR - States that the bus is capable of addressing 10-bit slave devices;
  • I2C_FUNC_PROTOCOL_MANGLING - States that the bus support variations outside the I2C protocol, such as ignoring data acknowledgments, reversing the bus direction and sending stop conditions at will;
  • I2C_FUNC_NOSTART - States that the device driver may send multiple messages through the bus without a start condition between messages. In Linux this functionality is included in the mangling functionality;
  • SMBUS functionality - States that the bus driver also supports System Management Bus (SMBUS) operations.

With the new API device drivers gather the information they want to send or receive in i2c_msg arrays which take the slave address, data buffer, buffer size and message flags. The flags indicate to the bus how that particular piece of data should be sent or received through the bus. Since this requires the messages to be parsed and processed accordingly it required some changes on how the bus sent/received the data to/from the bus compared to when the libi2c API was used.

The bus driver supports the I2C basic functionality (mandatory) and also 10-bit slave addressing, with some caveats.

As noted previously, the I2C basic functionality (I2C_FUNC_I2C) states that the bus must be able to recognize the I2C_M_RECV_LEN message flag. This flag allows the slave device to define the size of a transfer, so in practice when the bus receives a message with this flag it should contain a 32 bytes buffer and a buffer length of 1 byte. The bus reads 1 byte of data from the slave device which indicates how many bytes it will be sending, and assign that value to the buffer size of the message while receiving and storing the message on the buffer (which is limited to 32 bytes). However the Raspberry Pi may not be compatible with this as it will likely require two transfers, with a stop-start condition in-between. This happens because before any transfer the size is defined on the DLEN register, which decrements the value as data is transferred. When the value reaches 0 the Pi sends automatically a stop condition. This may be solved by setting a transfer size of 33 (32 maximum bytes plus the size byte), but on a transfer shorter that 32 bytes the transfer would have to be terminated and it is not sure if the Pi would send a stop condition in that situation.

As for the 10-bit slave addressing I was unable to find a device to test it.

Both these caveats can be solved with devices that use these functionalities, but that can be hard to find and also time consuming as they would also require device drivers to test them all within the GSOC program. The plan is that if someone happens to have one of such devices and wants/needs to use it with the pi may test those functionalities in the future.

The bus driver makes these supported functionalities available to the device drivers (which can retrieve them via an ioctl command to the bus), so they can know if the bus driver supports their I2C bus needs.

To use the I2C bus an application must first call one or more of the following functions:

void rpi_i2c_init(void);

int rpi_i2c_register_bus(
  const char *bus_path,
  uint32_t bus_clock
);

int rpi_setup_i2c_bus(void);

rpi_i2c_init setups the Raspberry Pi GPIO header to activate the BSC I2C bus, while rpi_i2c_register_bus registers the Raspberry Pi BSC I2C bus with the Linux I2C User-Space API. It takes in the desired path to the bus, as well as the bus clock. Using these functions allow for complete control of these two parameters.

However the Pi also provides the rpi_setup_i2c_bus function call which setups the Raspberry Pi BSC I2C bus on the "/dev/i2c" device file, using the default bus clock of 100 KHz.

More details on these functions can be found on the raspberry pi I2C header doxygen documentation.

A sample device driver and test case using the I2C bus can be found in:

https://github.com/asuol/RTEMS_rpi_testing/tree/master/I2C_bus

SPI bus driver

The SPI bus core logic is mostly untouched from last year, featuring some code refactorings and minor changes (e.g.: interrupts no longer wait to acquire a semaphore, but instead wait for a system event). As with I2C the bus is registered in the API and the system with a function call:

int rpi_spi_init(bool bidirectional_mode);

This function setups the Raspberry Pi SPI bus (located on the GPIO header) on the "/dev/spi" device file, and registers the bus on the libi2c API. The flag it receives defines if the bidirectional mode (2-wire SPI) should be enabled.

The bidirectional mode and write only devices (which do not return anything to the pi/master) were not tested with the bus driver.

A sample device driver and test case using the SPI bus can be found in:

https://github.com/asuol/RTEMS_rpi_testing/tree/master/SPI_bus

Notes on I2C and SPI bus drivers

Because I2C and SPI bus code no longer share the same API, I have separated the two buses into two separate directories in the raspberry BSP. Also the bsppredriverhook function which registered the buses depending on a BSPOPT was replaced back to the shared one, and now both buses provide a function that an application can call to setup the GPIO pins and register the bus in the corresponding API. The test device drivers also provide a device register function, which makes more sense than placing the device registration in the bsp code as it were last year.

BSPOPTs are still used to define if either of the buses should be polled or interrupt driven.

SD card support

The raspberry pi uses provides an EMMC (External Mass Media Controller) module by Arasan to operate the SD card host controller. It is compliant to the SD Host controller specification 3.0, and since rtems-libbsd already as a SDHCI and MMC/SD drivers they can be used with the Pi to access the SD card.

The sources for the SDHCI driver can be found in:

https://git.rtems.org/rtems-libbsd/tree/freebsd/sys/dev/sdhci

The driver in the tree defines that any BSP other than powerpc/qoriq results in a kernel panic at given operations. An initial plan is to add the Pi logic and quirks.

Since the Pi requires the SDHCI and MMC/SD bus drivers they are registered as nexus devices, and a test case was created (based on testsuites/init01) that links the drivers and allows the SD support to be tested.

The current work for SD card support within the rtems-libbsd can be found in:

https://github.com/asuol/rtems-libbsd/tree/RPI_SD_CARD

When the SDHCI driver is registered with the nexus bus it receives the EMMC base register address, which is defined on the raspberrypi.h header in the raspberry BSP. Since the base address for peripherals change from Pi1 to Pi2, the raspberrypi.h can always provide a constant for it.

Some of the changes done to the SDHCI driver was the host reset operation, which the Pi seems to require that both the SD and host controller clocks are disabled during the reset. However for some reason after a few days the reset no longer worked.

There is still a need to work out the best way to handle the SD card interrupts. In the past I have simply queried the EMMC interrupt pending registers when the driver expected the SD host or card to be doing something, but in theory the interrupts may be being sent to one of the GPIO interrupt vectors which could then call the SDHCI handler that the driver expects to be called. That is the current status of the SD support, as currently (even with reset problems) the host and card are detected and the host sends CMD0 to the card, but since the handler is not listening to the answer the operation obviously blocks.

References

[1] - https://asuolgsoc2014.wordpress.com/2015/05/03/project-intro/

[2] - https://asuolgsoc2014.wordpress.com/

[3] - https://github.com/asuol

[4] - https://git.rtems.org/rtems/tree/c/src/lib/libbsp/shared/include/gpio.h

[5] - https://asuolgsoc2014.wordpress.com/2015/07/14/jtag-code-loading-and-debugging/