Beagleboard-xM GPIO Kernel Module Driver

Posted on November 25, 2012
Tags: ,
by Sanchayan Maity

Whenever i search for a particular implementation, of let’s say GPIO, only user space implementation comes up in search results which use the sysfs interface. Being more interested in hardware and the kernel side of things, I thought I did start practicing writing some kernel module drivers. So, here is my first simple GPIO kernel module driver for the Beagleboard-xM.

I have set up my environment using Buildroot from the below link. I used this, because at some point I intend to use the camera module and Max Galemin has integrated those packages in his buildroot version. OpenCV 2.3.1 is supported in this buildroot, so that’s a very good plus point. Set up your environment using the instructions given on the below link or I assume you can use the standard buildroot available as well.

http://blog.galemin.com/2012/03/buildroot-2012-02-for-beagleboard-xm-with-li-5m03-mt9p031-camera-support/

You can do the default build or you might want to add support for php, python, lighthttpd, nfs, thttpd and a few other things as well.

Below I am posting the source code for the driver module, the user space application and the Makefile.

The Driver Module: gpio.ko

#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/init.h"
#include "linux/platform_device.h"
#include "linux/gpio.h"
#include "linux/fs.h"
#include "linux/errno.h"
#include "asm/uaccess.h"

#define GPIO_NUMBER 149 //User LED 0. GPIO number 149. Page 71 of BB-xM Sys Ref Manual.

static int init_result;

static ssize_t gpio_read( struct file* F, char *buf, size_t count, loff_t *f_pos )
{
	char buffer[10];

	int temp = gpio_get_value(GPIO_NUMBER);

	sprintf( buffer, "%1d" , temp );

	count = sizeof( buffer );

	if( copy_to_user( buf, buffer, count ) )
	{
		return -EFAULT;
	}

	if( *f_pos == 0 )
	{
		*f_pos += 1;
		return 1;
	}
	else
	{
		return 0;
	}

}

static ssize_t gpio_write( struct file* F, const char *buf, size_t count, loff_t *f_pos )
{
	printk(KERN_INFO "Executing WRITE.\n");

	switch( buf[0] )
	{
		case '0':
		gpio_set_value(GPIO_NUMBER, 0);
		break;

		case '1':
		gpio_set_value(GPIO_NUMBER, 1);
		break;

		default:
		printk("Wrong option.\n");
		break;
	}

	return count;
}

static int gpio_open( struct inode *inode, struct file *file )
{
	return 0;
}

static int gpio_close( struct inode *inode, struct file *file )
{
	return 0;
}

static struct file_operations FileOps =
{
	.open         = gpio_open,
	.read         = gpio_read,
	.write         = gpio_write,
	.release     = gpio_close,
};

static int init_gpio(void)
{
	init_result = register_chrdev( 0, "gpio", &FileOps );

	if( 0 > init_result )
	{
		printk(KERN_ALERT "Device Registration failed\n");
		return -1;
	}
	else
	{
		printk(KERN_ALERT "Major number is: %d\n",init_result);
		return 0;
	}
}

void cleanup_gpio(void)
{
	unregister_chrdev( init_result, "gpio" );
	printk(KERN_ALERT "Device unregistered\n");
}

module_init(init_gpio);
module_exit(cleanup_gpio);

MODULE_AUTHOR("Sanchayan");
MODULE_LICENSE("GPL");

The User Space Application: gpio_app.c

#include <stdio.h>
#include <fcntl.h>

int main(void)
{
	int fd;
	char gpio_buffer[10];
	char choice[10];

	fd = open( "/dev/gpio", O_RDWR );

	printf( "Value of fd is: %d", fd );

	if( fd < 0 )
	{
		printf("Cannot open device \t");
		printf(" fd = %d \n",fd);
		return 0;
	}

	printf("\nPlease enter choice: \t");
	scanf( "%s", choice );
	printf("Your choice is: %s \n", choice );
	write( fd, choice, 1 );
	read( fd, gpio_buffer, 1);
	printf("GPIO value is: %s \n", gpio_buffer );

	if( 0 != close(fd) )
	{
		printf("Could not close device\n");
	}

	return 0;
}

Makefile

KERN_SRC=/home/vm/buildroot/output/build/linux-3.2.8
KERN_COMPILER=/opt/CodeSourcery/Sourcery_CodeBench_Lite_for_ARM_GNU_Linux/bin

obj-m := gpio.o

all:
make -C $(KERN_SRC) ARCH=arm CROSS_COMPILE=$(KERN_COMPILER)/arm-none-linux-gnueabi- M=`pwd` modules

clean:
make -C $(KERN_SRC) ARCH=arm CROSS_COMPILE=$(KERN_COMPILER)/arm-none-linux-gnueabi- M=`pwd` clean

The path for your kernel source and path for the CodeSourcery toolchain can and mostly will be different. So change them, as per your environment.

After this, compile your kernel driver module by typing “sudo make” at the command line prompt. After this, cross compile your user space application by typing

$ arm-none-linux-gnueabi-gcc  gpio_app.c -o gpio_app

I have assumed that the cross compiler path has been added to your path environment variable.

You will now have a gpio.ko file and a gpio_app executable. Transfer these files to your beagleboard. I use the “scp” command to transfer files to my board over the ssh connection.

On the command prompt of your beagleboard, do “insmod gpio.ko”. Find out the major number by checking the dmesg output or /proc/devices.

Assuming for example, that the major number is 248, do “mknod /dev/gpio c 248 0”.

The mknod command has to be run for creation of the device node. If mknod is not run, you can see your module loaded using lsmod, but, you won’t notice any entry in the /dev directory with which to access it through your user space application. After running mknod you can see the gpio driver entry in the /dev directory with ls /dev.

By default, the user led’s 0 and 1 which are connected on GPIO 149 and 150, are used for indicating mmc card access activity and heartbeat.

You can turn these dafault behaviour off by first entering the /sys/class/gpio/beagleboard::usr0 and /sys/class/gpio/beagleboard::usr1 directory. After this, do “cat trigger”. You should see the default behaviour marked.

Now, change the default behaviour for both by doing “echo none > trigger” in their respective directories.

Now, run the user space application by typing ./gpio_app on the command line prompt. You will be prompted to enter the value “0” or “1” to turn off or turn on the led. You can observe the state of the led pin on your board after this.

Refer the Linux Device Drivers book by Corbet, Rubini and Greg Kroah Hartman. The second and third chapters are important for this tutorial.