驱动程序采用Linux平台总线(Platform Bus)模型,通过 platform_driver 结构体注册驱动,利用设备树实现设备和驱动的自动匹配。驱动同时注册了一个混杂设备(miscdevice),主设备号为10,为用户空间提供 /dev/pio_button 设备节点。
这个驱动我们不采用编译进内核的方式,而是当作一个可以动态加载的模块。这个驱动模块由源文件和makefile组成。
驱动源文件pio_interrupt.c:
//platform driver
#include <linux/module.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/unistd.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/sched.h>
// define the interrupt registers, refer to embedded ip user guide for infomation
#define PIO_REGISTER_INTERRUPTMSK 2
#define PIO_REGISTER_EDGECAPTUE 3
struct irq_des
{
int button_irq;
void __iomem *pio_base;
};
static volatile int key_values;
static volatile int ev_press = 0;
//define the wait quene head
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
struct irq_des pio_irq;
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
if(irq ==pio_irq.button_irq){
key_values = readl(pio_irq.pio_base+PIO_REGISTER_EDGECAPTUE*4);
ev_press = 1;
wake_up_interruptible(&button_waitq);
}
//clear all edge caputre registers
writel(0x0f,pio_irq.pio_base+PIO_REGISTER_EDGECAPTUE*4);
return IRQ_RETVAL(IRQ_HANDLED);
}
static int pio_buttons_open(struct inode *inode, struct file *file)
{
int err = 0;
err = request_irq(pio_irq.button_irq, buttons_interrupt, 0,
"PIO_BUTTON", NULL);
if (err) {
disable_irq(pio_irq.button_irq);
free_irq(pio_irq.button_irq, NULL);
return -EBUSY;
}
ev_press = 0;
//enable pio interrupt
writel(0x0f,pio_irq.pio_base+PIO_REGISTER_INTERRUPTMSK*4);
return 0;
}
static int pio_buttons_close(struct inode *inode, struct file *file)
{
free_irq(pio_irq.button_irq, NULL);
return 0;
}
static ssize_t pio_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
unsigned long err;
wait_event_interruptible(button_waitq, ev_press);
ev_press = 0;
err = copy_to_user(buff, (const void *)&key_values, min(sizeof(key_values), count));
return err ? -EFAULT : (ssize_t)min(sizeof(key_values), count);
}
static unsigned int pio_buttons_poll( struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait);
if (ev_press)
mask |= POLLIN | POLLRDNORM;
return mask;
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = pio_buttons_open,
.release = pio_buttons_close,
.read = pio_buttons_read,
.poll = pio_buttons_poll,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "pio_button",
.fops = &dev_fops,
};
static int my_plat_probe(struct platform_device *pdev)
{
int ret;
struct resource *res;
int irq;
int error;
res = platform_get_resource(pdev,IORESOURCE_MEM,0);
if(res == NULL)
return -ENOENT;
if(!request_mem_region(res->start,resource_size(res),pdev->name)){
error=-EBUSY;
return error;
}
pio_irq.pio_base=ioremap(res->start,resource_size(res));
irq=platform_get_irq(pdev,0);
if(irq<0)
{
return irq;
}
pio_irq.button_irq=irq;
ret = misc_register(&misc);
if(ret)
return ret;
return 0;
}
static void my_plat_remove(struct platform_device *pdev)
{
struct resource *res;
printk("my platfrom device has removed.\n");
res=platform_get_resource(pdev,IORESOURCE_MEM,0);
release_mem_region(res->start,resource_size(res));
misc_deregister(&misc);
//return 0;
}
static struct of_device_id pio_button_match[]={
{.compatible="terasic,pio-button",.data=NULL},
{}
};
MODULE_DEVICE_TABLE(of,pio_button_match);
struct platform_driver my_buttons_drv = {
.probe = my_plat_probe,
.remove = my_plat_remove,
.driver = {
.owner = THIS_MODULE,
.name = "my_buttons",
.of_match_table=pio_button_match,
},
};
static int __init platform_drv_init(void)
{
int ret;
ret = platform_driver_register(&my_buttons_drv);
return ret;
}
static void __exit platform_drv_exit(void)
{
platform_driver_unregister(&my_buttons_drv);
}
module_init(platform_drv_init);
module_exit(platform_drv_exit);
MODULE_AUTHOR("Terasic");
MODULE_LICENSE("GPL");注意上面Makefile文件当中 KDIR := /home/doreen/de25_nan0_sdmmc/linux-socfpga 是指定Linux Kernel 源码路径,读者要改成自己当前开发路径。
设置好编译链环境变量,执行make编译出pio_interrupt.ko文件:
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
export PATH=/usr/bin:$PATH
make
本文源码参考:https://github.com/hbwangjinwu/PIO_Interrupt/tree/master
文章来源:友晶科技