Raspberry Pi 4 mini UART driver example

The mini UART driver uses io-char to handle all client communication with a resource manager that creates new character devices under /dev/ser1, /dev/ser2, ..., /dev/serN, depending on the number of devices configured, as shown in the following diagram:
Figure 1UART driver communication

You can find a mini UART sample driver in the RPi4 BSP download package, com.qnx.qnx800.bsp.hw.raspberrypi_bcm2711_rpi4, under src/hardware/devc/serminiuart.

Unlike the stub io-char driver in the previous section, this mini UART driver:
  • Sends and receives data from the mini UART.
  • Supports multiple devices.
  • Is implemented using many functions in multiple source files:

    • externs.c - defines the global data.

    • externs.h - includes the required headers, defines device-specific data structure, and declares the global data.

    • init.c - initialization code.

    • intr.c - interrupt handler routines.

    • main.c - the main entry point of the driver and device clean-up code.

    • mini_uart.h - defines macros containing RPi4 mini UART-specific values.

    • options.c - defines initial parameters and parses the driver's command-line arguments.

    • proto.h - prototypes for the driver's interface routines and logging utility macros.

    • tto.c - a routine to transmit data, called by io-char. It also provides support to control and read hardware-control lines status, and provides support for the stty utility. If there's no TX FIFO, tto() writes only a single character.

Interrupts

Different chips use interrupts in different ways. Typically, interrupts occur when:

  • the RX FIFO's receive level/threshold is reached.

  • the TX FIFO empties.

  • a change in the modem status lines occurs.

The driver code adds characters only to the input queue/buffer. The io-char module handles the canonical buffer, which is manipulated when the client read request is handled.

Customizing a serial driver

You may need to make changes to create a driver for your device. If:
  • Your hardware is almost compatible with the mini UART, you might have to change the register addresses.

  • Compatibility is in question, you may have to change the source code.

You might not need to make any changes if:

  • Your serial hardware is completely compatible with the mini UART.

Implementation

The mini UART sample driver:

  • has a main() function that:

    • uses TTC_INIT_PROC to initialize the io-char library and set its priority,

    • uses the options() function, which:

      • initializes the TTYINIT struct.

      • calls the ttc() function to set device to raw mode.

      • uses TTC_SET_OPTION to parse default devc-* and devc-ser* options.

      • parses custom options.

      • calls create_device() function for all serial devices specified, which:

        • initializes the TTYDEV struct.

        • uses TTC_INIT_TTYNAME to setup device name.

        • calls the ser_attach_intr() function to setup interrupt handling.

        • calls the stty() function to initialize mini UART hardware communication.

        • uses TTC_INIT_ATTACH to register driver device path.

    • uses TTC_INIT_START to run the driver.

  • provides implementation of the tto() function which:

    • sets the baud rate.

    • sets line control.

    • checks the line status.

    • uses tto_getchar() to get data from output buffer.

    • sends data to UART.

    • uses tto_checkclients() to check if clients need to be notified.

  • provides implementation that receives data from mini UART when woken by an interrupt:

    • reads data from mini UART.

    • uses tti() to send data to input buffer.

    • notifies clients.

Functions

The devc-serminiuart driver includes the following functions, declared in proto.h:

create_device(TTYINIT *dip)
  • Gets a device entry and its input/output buffers and creates a new device based on the options passed in.

  • Called by options().

  • Calls ttc() with an argument of TTC_INIT_CC to initialize TTYDEV instance.

  • Calls ttc() with an argument of TTC_INIT_TTYNAME to setup device name.

  • Calls ser_attach_intr() to setup interrupt handling.

  • Calls ser_stty() to initialize mini UART hardware.

void ser_stty(DEV_MINIUART *dev)
  • Configures registers that can be changed dynamically at runtime (baud, parity, stop bits, etc.).

  • Called by create_device() and by tto() function when called with TTO_STTY.

  • Don't use this function for any initialization that you need to do when first setting up the driver; use create_device() instead.

void ser_attach_intr(DEV_MINIUART *dev)
  • Configures hardware interrupt handler.

  • Called by create_device().

void ser_detach_intr(DEV_MINIUART *dev)
  • Disables hardware interrupt and cleans-up the interrupt handler.

  • Called by dev_cleanup(), which is called by io-char’s clean-up thread.

unsigned options(int argc, char *argv[])
  • Parses the driver's command-line arguments. For information about the arguments, refer to devc-*, devc-ser* options.

  • Depending on the options specified, this function may call:

    • ttc() with an argument of TTC_INIT_RAW to configure the terminal to RAW mode.

    • ttc() with an argument of TTC_SET_OPTION to pass standard terminal configuration options to io-char to be executed.

    • create_device() to create a device.

  • Returns the number of ports that are enabled.

The driver's main() routine (defined in main.c) calls:

  • ttc() with an argument of TTC_INIT_PROC to allocate and configure the resources shared by all devices, e.g., the resource manager

  • ttc() with an argument of TTC_INIT_START to allow the driver to start accepting messages, i.e., work.

  • options() to parse the driver's command-line options.

Page updated: