August 12, 2010

GPIO drivers for i.MX51 – Part 1: input


There’s a lot you can do with just an input pin or two, but getting access to one from userspace can be difficult.

To try and make that access simple, we put together some drivers a while ago for our PXA-based products. I recently ported them to the i.MX51; in this post, I will describe how they can make accessing input pins easier.

The drivers are named mx51-gpio and mx51-gpio-trig (for triggered) and the source is available in our git repository.

Both are character devices that, together, can return the current state of input pins by capturing the state of the pins within interrupt handlers, queuing the data values and returning bit-masks of the status. mx51-gpio represents a single pin with a specific device. mx51-gpio-trig reads and returns the status of up to 8 pins at a time, on either the rising or falling edges of a given ‘trigger’ signal. This triggering is most useful when dealing with devices with separate clock and data pins.

Once loaded, you’ll need to create a single device file to access mx51-gpio-trig and one to represent each input pin for mx51-gpio. The minor number is used to define the GPIO itself.

root@freescale ~$ modprobe mx51-gpio ; modprobe mx51-gpio-trig
root@freescale ~$ cat /proc/devices | grep gpio
249 gpio-trig
250 gpio

I generally use this little snippet to grab the major number out of /proc/devices when setting up I/O pins.

root@freescale ~$ gpiomajor=`grep gpio$ /proc/devices | sed 's/ gpio//'` ;
root@freescale ~$ mknod /dev/gpi80 c $gpiomajor 80
root@freescale ~$ mknod /dev/gpi73 c $gpiomajor 73
root@freescale ~$ cat /dev/gpi80
<00>

The trailing <00> in the example shows that the gpio devices will report the initial state on open. Thereafter, they’ll only report changes. If I close and open the switch attached to GP80, I’ll see successive values of <00> and <01>. Note that this is a binary value, where <00> indicates a low state and a <01> indicates a high state, as reported by the CPU. Also note that with high-frequency transitions, it’s possible to have two identical values reported consecutively. What this means is that the CPU sees the second value and generates an interrupt, but by the time the interrupt handler reads the value of the pin, it has returned to its original state.

As you have probably guessed, no debouncing is done in the driver itself.

To illustrate the use of mx51-gpio-trig, I’ve connected an un-decoded TTL magnetic card reader such that the clock pin is connected to GP72 and the data pin is connected to GP71. This driver requires that an application supply the pin numbers, so we only need to create one device.

root@freescale ~$ trigmajor=`grep gpio-trig$ /proc/devices | sed 's/ gpio-trig//'` ;
root@freescale ~$ mknod /dev/gpio-trig c $trigmajor 0

In order for an application to configure the pins, it will need to call the GPIO_TRIG_CFG ioctl, specifying the trigger pin, a value indicating whether pins should be sampled on the rising or falling edge of the trigger signal, and a list of pins to sample. The data structure and ioctl are defined in the header file include/linux/gpio-trig.h:

#ifndef __GPIO_TRIG_H
#define __GPIO_TRIG_H
#define MAX_PINS 7

struct trigger_t {
unsigned char trigger_pin ;
unsigned char rising_edge ;
unsigned char num_pins ;
unsigned char pins[MAX_PINS];
};

#define GPIO_TRIG_CFG  _IOWR('xbd', 0x01, struct trigger_t)

#endif /* __GPIO_TRIG_H */

The following sample program illustrates how you might code things up.

int main( int argc, char const * const argv[] ){
	if( 4 <= argc ){
		unsigned trigNum = strtoul(argv[2],0,0);
		if( trigNum && trigNum < 256 ){
			struct trigger_t trig ;
			trig.trigger_pin = trigNum ;
			trig.rising_edge = ('r' == tolower(*argv[3]));
			trig.num_pins = 0 ;
			for( int arg = 4 ; arg < argc ; arg++ ) {
				unsigned pin = strtoul(argv[arg],0,0);
				if( pin && (pin < 256) ){
					trig.pins[trig.num_pins++] = pin ;
				}
				else {
					fprintf( stderr, "Invalid pin <%s>n", argv[arg] );
					return -1;
				}
			}
			int fdIn = open( argv[1], O_RDONLY );
			if( 0 <= fdIn ){
				int result = ioctl( fdIn, GPIO_TRIG_CFG, &trig );
				if( 0 == result ){
					printf( "configured GPIO triggern" );
					unsigned char inbuf[80];
					int numRead ;
					unsigned totalRead = 0 ;
					while( 0 < (numRead = read(fdIn,inbuf,sizeof(inbuf))) ){
						for( int i = 0 ; i < numRead ; i++ ){
							printf( "%5u:%5u: %02xn", totalRead, i, inbuf[i] );
						}
						totalRead += numRead ;
					}

				}
				else
					perror( "GPIO_TRIG_CFG" );
				close(fdIn );
			}
			else
				perror( argv[1] );
		} else
			printf( "Invalid trigger <%s>n", argv[2] );
	} else
		fprintf( stderr, "Usage: %s /dev/gpio-trig  triggerPin Rising|falling [...otherPins]n", argv[0] );
	return 0 ;
}

Used with GPIO pins 72 and 71 as I mentioned before, the program will come up with something like this:

root@freescale ~$ gpio-trig /dev/gpio-trig 72 R 71
configured GPIO trigger
    0:    0: 01
    1:    0: 01
    2:    0: 01
    3:    0: 01
    4:    0: 01
    5:    0: 01
    6:    0: 01
    7:    0: 01
    8:    0: 01

The output above (cleaned up, to highlight the important part) indicates that the card reader is giving me a bunch of leading 1s, which is the expected result.

To tie it all together, the mag-stripe reader I mentioned earlier has two pins that represent switches at the front and rear of the slot, so you can tell whether a card is partially or fully inserted. These are easily represented by mx51-gpio. If we add this and a little bit of decoding code to the mx51-gpio-trig example shown above, we can turn gpio-trig.c into magstripe.c:

root@freescale ~$ magstripe /dev/gpio-trig  72 r 71
F
R
f
F
f
r
601631094979200=0000
R
r
R
r
R
F
601631094979200=0000

The lines labeled F and f show transitions of the front switch to high and low respectively, while R and r show high and low values of the rear switch. Both switches are high when open, and low when closed. The example shows a single insertion and removal of one card. The data was first parsed upon closure of the rear switch, which is why the first line with card data is preceded by r.

Some additional particulars about the drivers, which you may not see unless you read the code:

  • The drivers support the poll and select system calls.
  • The drivers support non-blocking I/O through the regular O_NONBLOCK fcntl call.
  • The drivers queue up to 255 transitions in driver space and will wrap at 256, losing 256 entries. If your app is really slow and/or your transitions are really fast, you’ll need a more sophisticated driver.

We also have a GPIO output driver for PXA, but have not yet ported this code to the i.MX51. Something for a later blog post.