Kernel module for a push button on the OMNIFlash

During the maze embedded ARM robot project we needed an encoder to be able to calculate the distance the robot moved in order to generate a map that could be navigated properly.

Unfortunately we couldnt place a sensor near the wheels so we had to rely instead in a hacked solution using a push button. With this module we could calculate the distance by counting the amount
of times the button was pushed. We attached the button near the axis of the wheel and whenever it passed over it clicked it.

In order to receive interrupts in real time I had to write a small module, here is the source:


#include "linux/include/linux/kernel.h"
#include "linux/include/linux/module.h"
#include "linux/include/linux/tty.h"
#include "linux/include/linux/sched.h"

/* We want an interrupt */
#include "linux/include/asm-arm/uaccess.h"
#include "linux/include/linux/interrupt.h"
#include "linux/include/asm-arm/io.h"

MODULE_LICENSE("GPL");
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif

/* Global variables we will need */

#define DRIVER_AUTHOR "Adrian Montero www.adrianmontero.info"
#define DRIVER_DESC "Robothon driver"
#define SUCCESS 0
static int Device_Open = 0;
static struct file_operations fops;
static int Major;
int i = 0;
int j = 0;
static int clicker = 0;
static int sensor1 = 0;
static int sensor5 = 0;
int status = 0;
void *statusAdr, *map_base, *virt_addr_dir, *virt_addr_inten, *virt_addr_clear, *virt_addr_inten_a, *virt_addr_clear_a, *adata, *bdata;
#define BUF_LEN 3
static char Message[BUF_LEN];

static ssize_t robot_read(struct file * file, char * buf,
size_t count, loff_t *ppos)
{

char buff[64];
snprintf(buff, 64, "CL: %d S1: %d S5: %d\n\r", clicker, sensor1, sensor5);
int len = strlen(buff); /* Don't include the null byte. */
/*
* We only support reading the whole string at once.
*/
if (count < len)
return -EINVAL;
/*
* If file position is non-zero, then assume the string has
* been read and indicate there is no more data to be read.
*/
if (*ppos != 0)
return 0;
/*
* Besides copying the string to the user provided buffer,
* this function also checks that the user has permission to
* write to the buffer, that it is mapped, etc.
*/
if (copy_to_user(buf, buff, len))
return -EINVAL;
/*
* Tell the user how much data we wrote.
*/
*ppos = len;

return len;
}

static int robot_open(struct inode *inode, struct file *file)
{
if (Device_Open)
return -EBUSY;

Device_Open++;
MOD_INC_USE_COUNT;

return SUCCESS;
}

static int robot_release(struct inode *inode, struct file *file)
{
Device_Open --; /* We're now ready for our next caller */
MOD_DEC_USE_COUNT;
return 0;
}

static ssize_t robot_write(struct file *file,
const char *buffer,
size_t length,
loff_t *offset)
{
for(i=0; itty; // The tty for the current task

if (my_tty != NULL) {

(*(my_tty->driver).write)(
my_tty, // The tty itself
0, // We don't take the string from user space
str, // String
strlen(str)); // Length

(*(my_tty->driver).write)(my_tty, 0, "\015\012", 2);
}
}

/* Our interrupt handler */

void irq_handler(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned long tmp = 0;
//disable_irq();

//tmp = *((unsigned long *) adata);

//if (tmp & 0x20)
// sensor5++;

//if (tmp & 0x04)
// sensor1++;
// disable_irq();
// bdata = __ioremap(0x80840004,32,0);
tmp = *((unsigned long *) bdata);

if (tmp & 0x40)
{
*((unsigned long*) virt_addr_clear) = 0x40;
return;
}
// enable_irq();

if (!(tmp & 0x40))
{
printk("Values: %x\n\r", tmp);
clicker++;
printk("Interrupt detect! %d total! \n\r", clicker);
}

//*((unsigned long*) virt_addr_clear_a) = 0x24;
if (virt_addr_clear)
*((unsigned long *) virt_addr_clear) = 0x40; // clear the interrupt

//printk("Interrupt detected! %d total! \n", i); //report number

//enable_irq();

}

/* Initialize the module - register the IRQ handler */
int init_module()
{
unsigned long temp = 0;

Major = register_chrdev(0, "robot", &fops);

if (Major == -1) {
print_string(" Dynamic Major number allocation failed\n");
return Major;
}

printk("Remember to use 'mknod /dev/robot c %d 0' at the command prompt to create the device file.\n", Major);

/* Interrupt 59 is the block interrupt for any port A/B pins.
* SA_SHIRQ means we're willing to have othe handlers on this IRQ.
* SA_INTERRUPT can be used to make the handler into a fast interrupt.
* At the time of writing this, i have not checked if SA_SHIRQ will work
* on this architecture. For the purpose of I/O on port A and B it seems
* a moot point as all of the i/o are tied to this interrupt, and sharing
* it with other things would seem silly.
*/
status = request_irq(59,
(void *)irq_handler,
SA_INTERRUPT,
"IRQ_Example", NULL);
/* Check for errors */
if (status == -EBUSY) {
print_string("IRQ number allocation failed\n");
return status;
}
if (status == -ENOMEM){
print_string("No memory error \n");
return status;
}
if (status == -EINVAL){
print_string("Invalid error \n");
return status;
}
if (status < 0){
print_string("Irq is Invalid \n");
return status;
}

/* PORT A */

//print_string("Starting mapping drives\n");

//map_base = __ioremap(0x80840090, 32, 0); // map physical address

//if (map_base)
// print_string("Remapped port A\n");

//*((unsigned long *) map_base) = 0x24; // set to edge triggered

//map_base = __ioremap(0x808400a8, 32, 0);

//if (map_base)
// print_string("Debouncing A\n");

//*((unsigned long *) map_base) = 0x24;

/* PORT B */
map_base = __ioremap(0x808400ac,32,0); //map physical address

if (map_base)
print_string("Remapped port B\n");

temp = *((unsigned long *) map_base);
temp |= 0x40;
*((unsigned long *) map_base) = temp;

map_base = __ioremap(0x808400c4,32,0); // trigger edge

if (map_base)
print_string("Debounced port B\n");

temp = *((unsigned long *) map_base);
temp |= 0x40;
*((unsigned long *) map_base) = temp;
//debounce. Leaving it raw makes most switches trigger in both directions

virt_addr_inten = __ioremap(0x808400b8,32,0);
virt_addr_clear = __ioremap(0x808400b4,32,0); //set up address so we can clear the interrupt

// virt_addr_inten_a = __ioremap(0x8084009c,32,0);
// virt_addr_clear_a = __ioremap(0x80840098, 32, 0);

// adata = __ioremap(0x80840000,32,0);
bdata = __ioremap(0x80840004,32,0);

/*
* Probably want to put some additional error checking here.
* May want to clear any interrupts.
* Additionally this program does not check if the status of
* the port is changed after module insertion. End users will
* likely want to ensure such.
*/

// if (virt_addr_inten)
// print_string("Enabled interrupts on B\n");

// if (virt_addr_inten_a)
// print_string("Enabled interrupts on A\n");

temp = *((unsigned long *) virt_addr_inten);
temp |= 0x40;
*((unsigned long *) virt_addr_inten) = temp;
//enable interrupts on the line b
// *((unsigned long *) virt_addr_inten_a) = 0x24; //enable interrupts on the line a

printk("Robothon driver inserted\n");
printk("GPIO interrupts in use\n");
return 0;
}

/* Cleanup */
void cleanup_module()
{
printk("Removing robothon\n");
free_irq(59, NULL); //make sure to free up the IRQ
unregister_chrdev(Major, "robot"); //make sure to unregister the device
}