Page 1 of 1

Accessing GPIO from Linux (EP9302)

PostPosted: Fri May 14, 2010 6:49 pm
by kakanakov
http://www.simtec.co.uk/appnotes/AN0014/
All ARM processors access peripherals with memory mapped I/O. There are no specific instructions for manipulating I/O unlike some other processors like the Intel X86, instead all registers used to control a peripheral appear at some location in memory.
This introduces the first important issue to consider, that of alignment. The ARM CPU can only manipulate data on "naturally" aligned boundaries that is boundaries rounded to the same size as the datatype. For example a byte may be accessed on any byte alignment (with the LDRB instruction) a sixteen bit value may be accessed from any sixteen bit alignment (at address 0,2,4 but not 1,3,5) and a thirty two bit value (a word) may only be accessed on 32bit alignments (at address 0,4,8 but not 1,2,3)
The second issue to consider is access length, the access to the data must be exactly the correct length, this implies the data type of accesses from a typed language (such as C) must be correct. For example, to access a sixteen bit register a single access must be performed by the CPU, a pair of byte reads would cause two reads from the hardware and would probably cause completely incorrect results. The datasheet for the device being accessed should provide guidance on the width of the data being accessed.
The third major issue to consider is the locations of the registers within the CPU address space. Typically the registers will be specified in the devices datasheet in physical memory locations, that is addresses the CPU must physically present on its address bus to obtain access to the device. The Memory Management Unit (MMU) will almost always be active while an operating system is running and the logical addresses used by applications will have no relation whatsoever to the physical addressing scheme. Because of the MMU almost all OS provide a method of obtaining access to physical address space, these two forms of addressing should not be confused. Although not usually relevant for GPIO access sometimes designers will attach peripheral chips to address lines starting from the second or third address line, thus multiplying all accesses by two or four (indeed sometimes addresses of registers may be many megabytes apart) this is implementation specific but is common enough to mention as it is a source of confusion.
The final issue to consider is that of multiplexed or multifunction pins. There are a finite number of pins that can be attached to a physical processor package, most modern Processors are System on chip(SOC) type devices where there are a huge number of functions, often this means that there are many more possible signals than physical external pins. In order to deal with this GPIO pins are often multiplexed with other signals. A choice often has to be made between a special function signal or a GPIO, some devices may have more than one alternate function for a pin. The multiplexing on a pin is controlled by an additional register within the memory map.

ARM Linux provides a very simple way for user space applications to acquire access to Physical address space, while this is powerful, it may be dangerous as incorrect programming of hardware registers without the OS involvement will at best result in a crash and at worst damage to hardware. Care must be taken when manipulating hardware in this way and We cannot accept responsibility for any errors made whilst using this information.
To access arbitrary addresses from userspace the /dev/mem file is used, this file allows a process to use the mmap call to obtain a user space pointer to a physical address. In this context mmap can only map complete pages. A general mapping of an arbitrary address is achieved using code such as this:
Code: Select all
// GPIO Base Address
#define GPIO_BASE 0x80840000
// PORT H
#define PORTE_DR_OFFSET 0x20
#define PORTE_DDR_OFFSET 0x24

typedef volatile union {
    unsigned char byte;
    struct {
   unsigned  bit0   : 1;
        unsigned  bit1   : 1;
        unsigned  bit2   : 1;
        unsigned  bit3   : 1;
        unsigned  bit4   : 1;
        unsigned  bit5   : 1;
        unsigned  bit6   : 1;
        unsigned  bit7   : 1;
    } bits;
} port;

port *PORTE;
port *PORTE_DIR;

int gpio_init(void) {
   int iofd = -1;
   unsigned char *gpio_page, *syscon_page;

   iofd = open ("/dev/mem", O_RDWR | O_SYNC );
   if(iofd == -1) {
      err(1,"\n[DSNET]open in shtapi.c\n");
      return(-1);
   }
   gpio_page = (unsigned char *) mmap(0, getpagesize(),PROT_READ|PROT_WRITE, MAP_SHARED, iofd, GPIO_BASE);
   if (gpio_page == MAP_FAILED) {
      err(1,"\n[DSNET]mmap in shtapi.c\n");
      return (-2);
   }

   PORTE = (port *)(gpio_page+PORTE_DR_OFFSET);
   PORTE_DIR = (port *)(gpio_page+PORTE_DDR_OFFSET);
   return iofd;
}

Afterwards, we have mapped the GPIO addresses and can access port E bits trough a defined structure.
We can use this initialization to modify the Led states.
Code: Select all
int main (void) {
      int fd;
      fd = gpio_init();

     (*PORTE_DIR).bits.bit0 = 1;
     (*PORTE_DIR).bits.bit1 = 1;
     /*
        This is equal to:
      (*PORTE_DIR).byte |= 0x03;
     */
     (*PORTE).bits.bit0 = 1; // Turn on Green
     (*PORTE).bits.bit1 = 1; // Turn on Red
     /*
        This is equal to:
      (*PORTE).byte |= 0x03; 
       Turn on Both Leds
     */

      close(fd);
      return 0;
}

The major important areas here are:
* open the /dev/mem file with the O_SYNC flag, without this the mapping will be cached
* The mmap() must be a shared mapping
* The mmap() must be to page aligned (4k) boundaries and a multiple of page lengths
* The correct type must be used to access the hardware properly.
* The volatile keyword to the compiler to ensure results are not cached. This is important or results will be inconsistant as the external I/O acesses may not be made without the directive.
* Proper error handling is necessary in such code