Device drivers in user-space

Before you start writing a device driver, pause for a moment to consider whether it is really necessary. There are generic device drivers for many common types of device that allow you to interact with hardware directly from user space without having to write a line of kernel code. User space code is certainly easier to write and debug. It is also not covered by the GPL, although I don't feel that is a good reason in itself to do it this way.

They fall into two broad categories: those that you control through files in sysfs, including GPIO and LEDs, and serial buses that expose a generic interface through a device node, such as I2C.

GPIO

General Purpose Input/Output (GPIO) is the simplest form of digital interface since it gives you direct access to individual hardware pins, each of which can be configured as input or output. GPIO can even be used to create higher level interfaces such as I2C or SPI by manipulating each bit in the software, a technique that is called bit banging. The main limitation is the speed and accuracy of the software loops and the number of CPU cycles you want to dedicate to them. Generally speaking, it is hard to achieve timer accuracy better than a millisecond with kernels compiled with CONFIG_PREEMPT, and 100 microseconds with RT_PREEMPT, as we shall see in Chapter 14, Real-time Programming. More common use cases for GPIO are for reading push buttons and digital sensors and controlling LEDs, motors, and relays.

Most SoCs have a lot of GPIO bits which are grouped together in GPIO registers, usually 32 bits per register. On-chip GPIO bits are routed through to GPIO pins on the chip package via a multiplexer, known as a pin mux, which I will describe later. There may be additional GPIO bits available off-chip in the power management chip, and in dedicated GPIO extenders, connected through I2C or SPI buses. All this diversity is handled by a kernel subsystem known as gpiolib, which is not actually a library but the infrastructure GPIO drivers use to expose IO in a consistent way.

There are details about the implementation of gpiolib in the kernel source in Documentation/gpio and the drivers themselves are in drivers/gpio.

Applications can interact with gpiolib through files in the /sys/class/gpio directory. Here is an example of what you will see in there on a typical embedded board (a BeagleBone Black):

# ls  /sys/class/gpio
export  gpiochip0   gpiochip32  gpiochip64  gpiochip96  unexport

The gpiochip0 to gpiochip96 directories represent four GPIO registers, each with 32 GPIO bits. If you look in one of the gpiochip directories, you will see the following:

# ls /sys/class/gpio/gpiochip96/
base  label   ngpio  power  subsystem  uevent

The file base contains the number of the first GPIO pin in the register and ngpio contains the number of bits in the register. In this case, gpiochip96/base is 96 and gpiochip96/ngpio is 32, which tells you that it contains GPIO bits 96 to 127. It is possible for there to be a gap between the last GPIO in one register and the first GPIO in the next.

To control a GPIO bit from user space, you first have to export it from kernel space, which you do by writing the GPIO number to /sys/class/gpio/export. This example shows the process for GPIO 48:

# echo 48 > /sys/class/gpio/export
# ls /sys/class/gpio
export      gpio48    gpiochip0   gpiochip32  gpiochip64  gpiochip96  unexport

Now there is a new directory, gpio48, which contains the files you need to control the pin. Note that if the GPIO bit is already claimed by the kernel, you will not be able to export it in this way.

The directory gpio48 contains these files:

# ls /sys/class/gpio/gpio48
active_low  direction  edge  power  subsystem   uevent  value

The pin begins as an input. To change it to an output, write out to the direction file. The file value contains the current state of the pin, which is 0 for low and 1 for high. If it is an output, you can change the state by writing 0 or 1 to value. Sometimes, the meaning of low and high is reversed in hardware (hardware engineers enjoy doing that sort of thing), so writing 1 to active_low inverts the meaning so that a low voltage is reported as 1 in value and a high voltage as 0.

You can remove a GPIO from user space control by writing the GPIO number to /sys/class/gpio/unexport.

Handling interrupts from GPIO

In many cases, a GPIO input can be configured to generate an interrupt when it changes state, which allows you to wait for the interrupt rather than polling in an inefficient software loop. If the GPIO bit can generate interrupts, the file edge exists. Initially, it has the value none, meaning that it does not generate interrupts. To enable interrupts, you can set it to one of these values:

  • rising: Interrupt on rising edge
  • falling: Interrupt on falling edge
  • both: Interrupt on both rising and falling edges
  • none: No interrupts (default)

You can wait for an interrupt using the poll() function with POLLPRI as the event. If you want to wait for a rising edge on GPIO 48, you first enable interrupts:

# echo 48 > /sys/class/gpio/export
# echo rising > /sys/class/gpio/gpio48/edge

Then, you use poll() to wait for the change, as shown in this code example:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>

int main (int argc, char *argv[])
{
  int f;
  struct pollfd poll_fds [1];
  int ret;
  char value[4];
  int n;
  f = open("/sys/class/gpio/gpio48", O_RDONLY);
  if (f == -1) {
    perror("Can't open gpio48");
    return 1;
  }
  poll_fds[0].fd = f;
  poll_fds[0].events = POLLPRI | POLLERR;
  while (1) {
    printf("Waiting
");
    ret = poll(poll_fds, 1, -1);
    if (ret > 0) {
        n = read(f, &value, sizeof(value));
        printf("Button pressed: read %d bytes, value=%c
",
        n, value[0]);
    }
  }
  return 0;
}

LEDs

LEDs are often controlled though a GPIO pin, but there is another kernel subsystem that offers more specialized control specific to the purpose. The leds kernel subsystem adds the ability to set brightness, should the LED have that ability, and can handle LEDs connected in other ways than a simple GPIO pin. It can be configured to trigger the LED on an event such as block device access or just a heartbeat to show that the device is working. There is more information in Documentation/leds/ and the drivers are in drivers/leds/.

As with GPIOs, LEDs are controlled through an interface in sysfs, in /sys/class/leds. The LEDs have names in the form devicename:colour:function, as shown here:

# ls /sys/class/leds
beaglebone:green:heartbeat  beaglebone:green:usr2
beaglebone:green:mmc0       beaglebone:green:usr3

This shows one individual LED:

# ls /sys/class/leds/beaglebone:green:usr2
brightness    max_brightness  subsystem     uevent
device        power           trigger

The brightness file controls the brightness of the LED and can be a number between 0 (off) and max_brightness (fully on). If the LED doesn't support intermediate brightness, any non-zero value turns it on and zero turns it off. The file trigger lists the events that trigger the LED to turn on. The list of triggers is implementation-dependent. Here is an example:

# cat /sys/class/leds/beaglebone:green:heartbeat/trigger
none mmc0 mmc1 timer oneshot [heartbeat] backlight gpio cpu0 default-on

The trigger currently selected is shown in square brackets. You can change it by writing one of the other triggers to the file. If you want to control the LED entirely through brightness, select none. If you set the trigger to timer, two extra files appear that allow you to set the on and off times in milliseconds:

# echo timer > /sys/class/leds/beaglebone:green:heartbeat/trigger
# ls /sys/class/leds/beaglebone:green:heartbeat
brightness  delay_on    max_brightness  subsystem   uevent
delay_off   device      power           trigger
# cat /sys/class/leds/beaglebone:green:heartbeat/delay_on
500
# cat /sys/class/leds/beaglebone:green:heartbeat/delay_off
500
#

If the LED has on-chip timer hardware, the blinking takes place without interrupting the CPU.

I2C

I2C is a simple low speed 2-wire bus that is common on embedded boards, typically used to access peripherals which are not on the SoC board such as display controllers, camera sensors, GPIO extenders, and the like. There is a related standard known as SMBus (system management bus) that is found on PCs, that is used to access temperature and voltage sensors. SMBus is a subset of I2C.

I2C is a master-slave protocol, with the master being one or more host controllers on the SoC. Slaves have a 7-bit address assigned by the manufacturer – read the data sheet – allowing up to 128 nodes per bus, but 16 are reserved, so only 112 nodes are allowed in practice. The bus speed is 100 KHz in standard mode, or up to 400 KHz in fast mode. The protocol allows read and write transactions between the master and slave of up to 32 bytes. Frequently, the first byte is used to specify a register on the peripheral and the remaining bytes are the data read from or written to that register.

There is one device node for each host controller, for example, this SoC has four:

# ls -l /dev/i2c*
crw-rw---- 1 root i2c 89, 0 Jan  1 00:18 /dev/i2c-0
crw-rw---- 1 root i2c 89, 1 Jan  1 00:18 /dev/i2c-1
crw-rw---- 1 root i2c 89, 2 Jan  1 00:18 /dev/i2c-2
crw-rw---- 1 root i2c 89, 3 Jan  1 00:18 /dev/i2c-3

The device interface provides a series of ioctl commands that query the host controller and send read and write commands to I2C slaves. There is a package named i2c-tools which uses this interface to provide basic command-line tools to interact with I2C devices. The tools are as follows:

  • i2cdetect: This lists the I2C adapters and probes the bus
  • i2cdump: This dumps data from all the registers of an I2C peripheral
  • i2cget: This reads data from an I2C slave
  • i2cset: This writes data to an I2C slave

The i2c-tools package is available in Buildroot and the Yocto Project, as well as most mainstream distributions. So long as you know the address and protocol of the slave, writing a user space program to talk to the device is straightforward:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <i2c-dev.h>
#include <sys/ioctl.h>
#define I2C_ADDRESS 0x5d
#define CHIP_REVISION_REG 0x10

void main (void)
{
  int f_i2c;
  int val;

  /* Open the adapter and set the address of the I2C device */
  f_i2c = open ("/dev/i2c-1", O_RDWR);
  ioctl (f_i2c, I2C_SLAVE, I2C_ADDRESS);

  /* Read 16-bits of data from a register */
  val = i2c_smbus_read_word_data(f, CHIP_REVISION_REG);
  printf ("Sensor chip revision %d
", val);
  close (f_i2c);
}

Note that the header i2c-dev.h is the one from the i2c-tools package, not the one from the Linux kernel headers. The i2c_smbus_read_word_data() function is written inline in i2c-dev.h.

There is more information about the Linux implementation of I2C in Documentation/i2c/dev-interface. The host controller drivers are in drivers/i2c/busses.

SPI

The serial peripheral interface bus is similar to I2C, but is a lot faster, up to the low MHz. The interface uses four wires with separate send and receive lines which allows it to operate in full duplex. Each chip on the bus is selected with a dedicated chip select line. It is commonly used to connect to touchscreen sensors, display controllers, and serial NOR flash devices.

As with I2C, it is a master-slave protocol, with most SoCs implementing one or more master host controllers. There is a generic SPI device driver which you can enable through the kernel configuration CONFIG_SPI_SPIDEV. It creates a device node for each SPI controller which allows you to access SPI chips from user space. The device nodes are named spidev[bus].[chip select]:

# ls -l /dev/spi*
crw-rw---- 1 root root 153, 0 Jan  1 00:29 /dev/spidev1.0

For examples of using the spidev interface, refer to the example code in Documentation/spi.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.176.194