Two Methods of Button Debounce–Interrupt Delay Work and Timer

Two methods of button debounce – interrupt delay and timer

Article directory

  • Two Methods of Button Debounce–Interrupt Delay Work and Timer
  • interrupt delay work
    • 1. Related structures
    • 2. Interface function
      • Initialize delayed work
      • Scheduling delayed work
      • unscheduled
    • 3. Template
    • 4. Code implementation
  • timer
    • 1. Related structures
    • 2. Interface function
      • init_timer function
      • add_timer function
      • del_timer function
      • del_timer_sync function
      • mod_timer function
    • 3. Template
    • Code

Due to the physical characteristics of the button, it will be set to 1/0 many times at the moment of pressing, commonly known as jitter.
So we need to give a reasonable delay to determine whether to press the button. In order to achieve this delay, we can use interrupt delay work and timers to achieve it. The following is the specific implementation method.

Interrupt delay work

1. Related structures

The kernel uses the struct delayed_work structure to describe a delayed work, defined in include/linux/workqueue.h:
/

/describe a delayed job
struct delayed_work {<!-- -->
    struct work_struct work;
    struct timer_list timer;

    /* target workqueue and CPU ->timer uses to queue ->work */
    struct workqueue_struct *wq;
    int cpu;
};

2. Interface function

Initialize delayed work

Define and initialize the delayed work function:

#define INIT_DELAYED_WORK(_work, _func) \
    __INIT_DELAYED_WORK(_work, _func, 0)

Dynamically define and initialize lazy work

#define DECLARE_DELAYED_WORK(n, f) \
    struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)

Statically define and initialize lazy work

Scheduling delayed work

static inline bool schedule_delayed_work(struct delayed_work *dwork,
                     unsigned long delay)

Role: Schedule delayed work on a shared work queue.
Parameters: *dwork: Delayed work. delay: The time to delay. The unit is beat.

static inline bool queue_delayed_work(struct workqueue_struct *wq,
                      struct delayed_work *dwork,
                      unsigned long delay)

Role: Schedule delayed work on a custom work queue
parameter:
*wq: custom work queue.
*dwork: Delayed work.
delay: The time to delay. The unit is beat.

Cancel Scheduling

bool cancel_delayed_work_sync(struct delayed_work *dwork)

Function: Cancel the delayed work that has been scheduled.

3. Template

.....
struct delayed_work test_workqueue_work;
    struct workqueue_struct *test_workqueue;
 .....
 /* create and initialize */
 test_workqueue = create_workqueue("test_workqueue");
    INIT_DELAYED_WORK( & irq_keydesc.test_workqueue_work,test_work);
 ?…
  /*Upper half*/
 static irq return_t key0_handler(int irq, void *dev_id)
{<!-- -->
.....
    
/*1ms delay*/
    queue_delayed_work(irq_keydesc.test_workqueue, &test_workqueue_work, (1*HZ)/1000);
    .....
    return IRQ_RETVAL(IRQ_HANDLED);
}
.....
 /*bottom half*/
 void test_work(struct work_struct* work){<!-- -->

    ?…
    printk("test\\
");

}

 /* cancel scheduling */
 cancel_delayed_work_sync( &test_workqueue_work);

4. Code implementation

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/workqueue.h>


#define IMX6UIRQ_CNT 1 /* Number of device numbers */
#define IMX6UIRQ_NAME "imx6uirq" /* name */
#define KEY0VALUE 0X01 /* KEY0 key value */
#define INVAKEY 0XFF /* invalid key value */
#define KEY_NUM 1 /* number of keys */

/* Interrupt IO description structure */
struct irq_keydesc {<!-- -->
    int gpio; /* gpio */
    int irqnum; /* interrupt number */
    unsigned char value; /* key value corresponding to key */
    char name[10]; /* name */
    irqreturn_t (*handler)(int, void *); /* interrupt service function */
    struct delayed_work test_workqueue_work;
    struct workqueue_struct *test_workqueue;
}irq_keydesc;
//static struct delayed_work test_workqueue_work;
//static struct workqueue_struct *test_workqueue;

void test_work(struct work_struct* work){<!-- -->

    
    printk("test\\
");

}
/* imx6uirq device structure */
struct imx6uirq_dev{<!-- -->
    dev_t devid; /* device number */
    struct cdev cdev; /* cdev */
    struct class *class; /* class */
    struct device *device; /* device */
    int major; /* major device number */
    int minor; /* Minor device number */
    struct device_node *nd; /* device node */
    atomic_t keyvalue; /* valid key value */
    atomic_t releasekey; /* Mark whether to complete a completed key, including pressing and releasing */
    struct timer_list timer; /* define a timer */
    struct irq_keydesc irqkeydesc[KEY_NUM]; /* key description array */
    unsigned char curkeynum; /* current key number */
};

struct imx6uirq_dev imx6uirq; /* irq device */

/* @description : Interrupt service function, start timer, delay 10ms,
 * The timer is used for button debounce.
 * @param - irq : interrupt number
 * @param - dev_id : Device structure.
 * @return : interrupt execution result
 */
static irq return_t key0_handler(int irq, void *dev_id)
{<!-- -->
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
    
    queue_delayed_work(irq_keydesc.test_workqueue, & amp;irq_keydesc.test_workqueue_work, (1*HZ)/100000000);
    
    return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description : Timer service function, used for button debounce, when the timer is up
 * Read the button value again, if the button is still pressed, it means the button is valid.
 * @param - arg : device structure variable
 * @return : none
 */

/*
 * @description : Key IO initialization
 * @param : None
 * @return : none
 */
static int keyio_init(void)
{<!-- -->
    unsigned char i = 0;
    int ret = 0;
    
    imx6uirq.nd = of_find_node_by_path("/key");
    if (imx6uirq.nd== NULL){<!-- -->
        printk("key node not found!\r\\
");
        return -EINVAL;
    }

    /* extract GPIO */
    for (i = 0; i < KEY_NUM; i ++ ) {<!-- -->
        imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
        if (imx6uirq.irqkeydesc[i].gpio < 0) {<!-- -->
            printk("can't get key%d\r\\
", i);
        }
    }
    
    /* Initialize the IO used by the key and set it to interrupt mode */
    for (i = 0; i < KEY_NUM; i ++ ) {<!-- -->
        memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name)); /* clear buffer */
        sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* combination name */
        gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
        gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
        imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
        imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
        printk("key%d:gpio=%d, irqnum=%d\r\\
",i, imx6uirq.irqkeydesc[i].gpio,
                                         imx6uirq.irqkeydesc[i].irqnum);
    }
    /* request interrupt */
    imx6uirq.irqkeydesc[0].handler = key0_handler;
    imx6uirq.irqkeydesc[0].value = KEY0VALUE;
    
    for (i = 0; i < KEY_NUM; i ++ ) {<!-- -->
        ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler,
                         IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
        if(ret < 0){<!-- -->
            printk("irq %d request failed!\r\\
", imx6uirq.irqkeydesc[i].irqnum);
            return -EFAULT;
        }
    }
    
    irq_keydesc.test_workqueue = create_workqueue("test_workqueue");
    INIT_DELAYED_WORK( & irq_keydesc.test_workqueue_work,test_work);
    return 0;
}

/*
 * @description : open the device
 * @param - inode : the inode passed to the driver
 * @param - filp : device file, the file structure has a member variable called private_data
 * Generally point private_data to the device structure when opening.
 * @return : 0 success; other failure
 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{<!-- -->
    filp->private_data = & amp;imx6uirq; /* set private data */
    return 0;
}

 /*
  * @description : read data from device
  * @param - filp: device file to open (file descriptor)
  * @param - buf : data buffer returned to user space
  * @param - cnt : length of data to be read
  * @param - offt : offset relative to the first address of the file
  * @return : The number of bytes read, if it is negative, it means the read failed
  */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{<!-- -->
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    keyvalue = atomic_read( & dev->keyvalue);
    releasekey = atomic_read( & dev->releasekey);

    if (releasekey) {<!-- --> /* A key is pressed */
        if (keyvalue & amp; 0x80) {<!-- -->
            keyvalue & amp;= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        } else {<!-- -->
            goto data_error;
        }
        atomic_set( & amp;dev->releasekey, 0);/* press the flag to clear */
    } else {<!-- -->
        goto data_error;
    }
    return 0;
    
data_error:
    return -EINVAL;
}

/* Device operation function */
static struct file_operations imx6uirq_fops = {<!-- -->
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
};

/*
 * @description : Driver entry function
 * @param : None
 * @return : none
 */
static int __init imx6uirq_init(void)
{<!-- -->
    /* 1. Build device number */
    if (imx6uirq.major) {<!-- -->
        imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    } else {<!-- -->
        alloc_chrdev_region( & imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }

    /* 2. Register character device */
    cdev_init( &imx6uirq.cdev, &imx6uirq_fops);
    cdev_add( & imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);

    /* 3. Create class */
    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.class)) {<!-- -->
        return PTR_ERR(imx6uirq.class);
    }

    /* 4. Create device */
    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.device)) {<!-- -->
        return PTR_ERR(imx6uirq.device);
    }
    
    /* 5. Initialize button */
    atomic_set( &imx6uirq.keyvalue, INVAKEY);
    atomic_set( &imx6uirq.releasekey, 0);
    keyio_init();
    return 0;
}

/*
 * @description : Driver export function
 * @param : None
 * @return : none
 */
static void __exit imx6uirq_exit(void)
{<!-- -->
    unsigned int i = 0;
    
        
    /* release interrupt */
    for (i = 0; i < KEY_NUM; i ++ ) {<!-- -->
        free_irq(imx6uirq.irqkeydesc[i].irqnum, & imx6uirq);
        gpio_free(imx6uirq.irqkeydesc[i].gpio);
    }
    cdev_del( &imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
    device_destroy(imx6uirq.class, imx6uirq.devid);
    class_destroy(imx6uirq.class);
    
    
    cancel_delayed_work_sync( &irq_keydesc.test_workqueue_work);
    flush_workqueue(irq_keydesc.test_workqueue);
    
    
}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");

Makefile

# 1. When using different development board kernels, be sure to modify KERN_DIR
# 2. The kernel in KERN_DIR must be configured and compiled in advance. In order to compile the kernel, the following environment variables must be set first:
# 2.1 ARCH, for example: export ARCH=arm64
# 2.2 CROSS_COMPILE, for example: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, for example: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# Note: The above 3 environment variables are not necessarily the same for different development boards and different compilers.
# Please refer to the advanced user manual of each development board

# ROOTFS_DIR The directory where *.ko files are stored in the root file system
# PROJECT_NAME Create a directory corresponding to the project in the directory where the .ko file is stored
# DRIVER_NAME The driver that needs to be compiled from .ko in the project
# Application test files in the APP_NAME project

#make Compile the project
#make file Create a directory corresponding to the project in the directory where the .ko file is stored
#make install Move *.ko and its application test files into the root file

KERN_DIR = /home/alientek/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
ROOTFS_DIR = /home/alientek/linux/nfs/rootfs/experiment
#Project name
PROJECT_NAME = wq_button
#Each drive name, ko
DRIVER_NAME1 = imx6uirq
DRIVER_NAME2 =
#app name
APP_NAME = imx6uirqApp
all:
    make -C $(KERN_DIR) M=`pwd` modules


clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
    rm -f $(APP_NAME)
file:
    mkdir $(ROOTFS_DIR)/$(PROJECT_NAME)
install:
    cp *.ko $(ROOTFS_DIR)/$(PROJECT_NAME)

# Refer to the kernel source code drivers/char/ipmi/Makefile
# If you want to compile a.c, b.c into ab.ko, you can specify it like this:
# ab-y := a.o b.o
# obj-m += ab.o

obj-m += $(DRIVER_NAME1).o

Timer

1. Related structures

The Linux kernel uses the timer_list structure to represent the kernel timer, and timer_list is defined in the file include/linux/timer.h as follows

struct timer_list {<!-- -->
    /*
     * All fields that change during normal runtime grouped to the
     * same cacheline
     */
    struct list_head entry;//kernel use
    unsigned long expires;//timeout jiffies value
    struct tvec_base *base;//kernel use, management

    void (*function)(unsigned long);//timeout processing function
    unsigned long data;//timeout processing function parameters

    int slack;

#ifdef CONFIG_TIMER_STATS
    int start_pid;
    void *start_site;
    char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

2. Interface function

init_timer function

The init_timer function is responsible for initializing the timer_list type variable. When we define a timer_list variable, we must initialize it with init_timer first. The prototype of the init_timer function is as follows:

void init_timer(struct timer_list *timer)

Function parameters and return values have the following meanings:
timer: To initialize the timer.
Return Value: There is no return value.

add_timer function

The add_timer function is used to register the timer with the Linux kernel. After using the add_timer function to register the timer with the kernel,
The timer will start running, and the function prototype is as follows:

void add_timer(struct timer_list *timer)

Function parameters and return values have the following meanings:
timer: The timer to be registered.
Return value: no return value.

del_timer function

The del_timer function is used to delete a timer, regardless of whether the timer is activated or not, it can be deleted using this function.
On a multi-processor system, the timer may run on other processors, so when calling the del_timer function to delete the timer
Wait for the timed handler functions of other processors to exit before executing the handler. The del_timer function prototype is as follows:

int del_timer(struct timer_list * timer)

Function parameters and return values have the following meanings:
timer: The timer to delete.
Return value: 0, the timer has not been activated; 1, the timer has been activated.

del_timer_sync function

The del_timer_sync function is a synchronous version of the del_timer function, which waits for other processors to finish using the timer before deleting it.
del_timer_sync cannot be used in interrupt context. The del_timer_sync function prototype is as follows:

int del_timer_sync(struct timer_list *timer)

Function parameters and return values have the following meanings:
timer: The timer to delete.
Return value: 0, the timer has not been activated; 1, the timer has been activated.

mod_timer function

The mod_timer function is used to modify the timing value. If the timer has not been activated, the mod_timer function will activate the timing
device! The function prototype is as follows:

int mod_timer(struct timer_list *timer, unsigned long expires)

Function parameters and return values have the following meanings:
timer: The timer to modify the timeout time (timing value).
expires: the modified timeout period.
Return value: 0, the timer is not activated before calling the mod_timer function; 1, the timer is active before calling the mod_timer function
Activated.

3. Template

struct timer_list timer; /* define timer */

/* Timer callback function */
void function(unsigned long arg){<!-- -->
  /*
 * Timer processing code
*/

/* Use mod_timer if you need the timer to run periodically
 * The function resets the timeout value and starts the timer.
*/
mod_timer( &dev->timertest, jiffies + msecs_to_jiffies(2000));
}

/* initialization function */
void init(void) {<!-- -->
init_timer( & amp;timer); /*Initialize the timer */


timer.function = function; /* set timing processing function * /
timer.expires=jffies + msecs_to_jiffies(2000);/*timeout 2 seconds*/
timer.data = (unsigned long) & amp;dev; /* take the device structure as a parameter */

add_timer( & amp;timer); /* start timer */
}

/* exit function */
 void exit(void){
del_timer( & amp;timer); /*delete timer*/
/* or use */
 del_timer_sync( & timer);
}

Code Implementation

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IMX6UIRQ_CNT 1 /* Number of device numbers */
#define IMX6UIRQ_NAME "imx6uirq" /* name */
#define KEY0VALUE 0X01 /* KEY0 key value */
#define INVAKEY 0XFF /* invalid key value */
#define KEY_NUM 1 /* number of keys */

/* Interrupt IO description structure */
struct irq_keydesc {<!-- -->
    int gpio; /* gpio */
    int irqnum; /* interrupt number */
    unsigned char value; /* key value corresponding to key */
    char name[10]; /* name */
    irqreturn_t (*handler)(int, void *); /* interrupt service function */
};

/* imx6uirq device structure */
struct imx6uirq_dev{<!-- -->
    dev_t devid; /* device number */
    struct cdev cdev; /* cdev */
    struct class *class; /* class */
    struct device *device; /* device */
    int major; /* major device number */
    int minor; /* Minor device number */
    struct device_node *nd; /* device node */
    atomic_t keyvalue; /* valid key value */
    atomic_t releasekey; /* Mark whether to complete a completed key, including pressing and releasing */
    struct timer_list timer; /* define a timer */
    struct irq_keydesc irqkeydesc[KEY_NUM]; /* key description array */
    unsigned char curkeynum; /* current key number */
};

struct imx6uirq_dev imx6uirq; /* irq device */

/* @description : Interrupt service function, start timer, delay 10ms,
 * The timer is used for button debounce.
 * @param - irq : interrupt number
 * @param - dev_id : Device structure.
 * @return : interrupt execution result
 */
static irq return_t key0_handler(int irq, void *dev_id)
{<!-- -->
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;
    mod_timer( & amp;dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms timing */
    return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description : Timer service function, used for button debounce, when the timer is up
 * Read the button value again, if the button is still pressed, it means the button is valid.
 * @param - arg : device structure variable
 * @return : none
 */
void timer_function(unsigned long arg)
{<!-- -->
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];

    value = gpio_get_value(keydesc->gpio); /* read IO value */
    if(value == 0){<!-- --> /* Press the button */
        atomic_set( & amp; dev->keyvalue, keydesc->value);
    }
    else{<!-- --> /* key release */
        atomic_set( & amp; dev->keyvalue, 0x80 | keydesc->value);
        atomic_set( & amp;dev->releasekey, 1); /* Mark release key, that is to complete a complete key process */
    }
}

/*
 * @description : Key IO initialization
 * @param : None
 * @return : none
 */
static int keyio_init(void)
{<!-- -->
    unsigned char i = 0;
    int ret = 0;
    
    imx6uirq.nd = of_find_node_by_path("/key");
    if (imx6uirq.nd== NULL){<!-- -->
        printk("key node not found!\r\\
");
        return -EINVAL;
    }

    /* extract GPIO */
    for (i = 0; i < KEY_NUM; i ++ ) {<!-- -->
        imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
        if (imx6uirq.irqkeydesc[i].gpio < 0) {<!-- -->
            printk("can't get key%d\r\\
", i);
        }
    }
    
    /* Initialize the IO used by the key and set it to interrupt mode */
    for (i = 0; i < KEY_NUM; i ++ ) {<!-- -->
        memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name)); /* clear buffer */
        sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* combination name */
        gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
        gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
        imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
        imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
        printk("key%d:gpio=%d, irqnum=%d\r\\
",i, imx6uirq.irqkeydesc[i].gpio,
                                         imx6uirq.irqkeydesc[i].irqnum);
    }
    /* request interrupt */
    imx6uirq.irqkeydesc[0].handler = key0_handler;
    imx6uirq.irqkeydesc[0].value = KEY0VALUE;
    
    for (i = 0; i < KEY_NUM; i ++ ) {<!-- -->
        ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler,
                         IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
        if(ret < 0){<!-- -->
            printk("irq %d request failed!\r\\
", imx6uirq.irqkeydesc[i].irqnum);
            return -EFAULT;
        }
    }

    /* create timer */
    init_timer( &imx6uirq.timer);
    imx6uirq.timer.function = timer_function;
    return 0;
}

/*
 * @description : open the device
 * @param - inode : the inode passed to the driver
 * @param - filp : device file, the file structure has a member variable called private_data
 * Generally point private_data to the device structure when opening.
 * @return : 0 success; other failure
 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{<!-- -->
    filp->private_data = & amp;imx6uirq; /* set private data */
    return 0;
}

 /*
  * @description : read data from device
  * @param - filp: device file to open (file descriptor)
  * @param - buf : data buffer returned to user space
  * @param - cnt : length of data to be read
  * @param - offt : offset relative to the first address of the file
  * @return : The number of bytes read, if it is negative, it means the read failed
  */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{<!-- -->
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    keyvalue = atomic_read( & dev->keyvalue);
    releasekey = atomic_read( & dev->releasekey);

    if (releasekey) {<!-- --> /* A key is pressed */
        if (keyvalue & amp; 0x80) {<!-- -->
            keyvalue & amp;= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        } else {<!-- -->
            goto data_error;
        }
        atomic_set( & amp;dev->releasekey, 0);/* press the flag to clear */
    } else {<!-- -->
        goto data_error;
    }
    return 0;
    
data_error:
    return -EINVAL;
}

/* Device operation function */
static struct file_operations imx6uirq_fops = {<!-- -->
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
};

/*
 * @description : Driver entry function
 * @param : None
 * @return : none
 */
static int __init imx6uirq_init(void)
{<!-- -->
    /* 1. Build device number */
    if (imx6uirq.major) {<!-- -->
        imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    } else {<!-- -->
        alloc_chrdev_region( & imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }

    /* 2. Register character device */
    cdev_init( &imx6uirq.cdev, &imx6uirq_fops);
    cdev_add( & imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);

    /* 3. Create class */
    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.class)) {<!-- -->
        return PTR_ERR(imx6uirq.class);
    }

    /* 4. Create device */
    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.device)) {<!-- -->
        return PTR_ERR(imx6uirq.device);
    }
    
    /* 5. Initialize button */
    atomic_set( &imx6uirq.keyvalue, INVAKEY);
    atomic_set( &imx6uirq.releasekey, 0);
    keyio_init();
    return 0;
}

/*
 * @description : Driver export function
 * @param : None
 * @return : none
 */
static void __exit imx6uirq_exit(void)
{<!-- -->
    unsigned int i = 0;
    /* delete timer */
    del_timer_sync( & amp;imx6uirq.timer); /* delete timer */
        
    /* release interrupt */
    for (i = 0; i < KEY_NUM; i ++ ) {<!-- -->
        free_irq(imx6uirq.irqkeydesc[i].irqnum, & imx6uirq);
        gpio_free(imx6uirq.irqkeydesc[i].gpio);
    }
    cdev_del( &imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
    device_destroy(imx6uirq.class, imx6uirq.devid);
    class_destroy(imx6uirq.class);
}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");