Linux设备驱动之platform按键驱动

转自:点击打开

1. platform设备模型原理

从Linux 2.6起引入了一套新的驱动管理和注册机制,platform_device和platform_driver,Linux中大部分的设备驱动都可以使用这套机制。platform是一条虚拟的总线。设备用platform_device表示,驱动用platform_driver进行注册,Linux platform driver机制和传统的device driver机制(通过driver_register进行注册)相比,一个明显的优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动中使用这些资源时通过platform device提供的标准结构进行申请并使用。这样提高了驱动和资源的独立性,并且具有较好的可移植性和安全性(这些标准接口是安全的)。

    pltform机制本身使用并不复杂,由两部分组成:platform_device和platform_driver。通过platform机制开发底层驱动的大致流程为:定义platform_deive->注册platform_device->定义platform_driver->注册platform_driver。

    首先要确认的就是设备的资源信息,例如设备的地址,中断号等。

1.1 platform_device

在 2.6 内核中 platform 设备用结构体 platform_device 来描述,该结构体定义在 kernel/include/linux/platform_device.h 中:

struct platform_device {

 const char * name;

 u32  id;

 struct device dev;

 u32  num_resources;

 struct resource * resource;

};

该结构一个重要的元素是resource ,该元素存入了最为重要的设备资源信息,定义在kernel/include/linux/ioport.h 中:

struct resource {

 const char *name;//资源的名称

 unsigned long start, end;//资源起始的和结束的物理地址

 unsigned long flags;//资源的类型,比如MEM,IO,IRQ类型

 struct resource *parent, *sibling, *child;//资源链表的指针

};

struct platform_device的分配使用:

struct platform_device *platform_device_alloc(const char *name, int id)

name是设备名,id,设备id,一般为-1,如果是-1,表示同样名字的设备只有一个。举个简单的例子,name/id是“serial/1”则它的bus_id就是serial.1  如果name/id是“serial/0”则它的bus_id就是serial.0 ,如果它的name/id是“serial/-1”则它的bus_id就是serial。
注册平台设备,使用函数:

int platform_device_add(struct platform_device *pdev)

注销使用:

void platform_device_unregister(struct platform_device *pdev)

1.2 platform_driver

在平台设备驱动中获取平台设备资源使用:

struct resource *platform_get_resource(struct platform_device *dev, unsigned int type,unsigned int num)

该函数用于获取dev设备的第num个类型为type的资源,如果获取失败,则返回NULL。例如 platform_get_resource(pdev,IORESOURCE_IRQ, 0)。

平台驱动描述使用:

struct platform_driver {

 int (*probe)(struct platform_device *);

 int (*remove)(struct platform_device *);

 void (*shutdown)(struct platform_device *);

 int (*suspend)(struct platform_device *, pm_message_t state);

 int (*suspend_late)(struct platform_device *, pm_message_t state);

 int (*resume_early)(struct platform_device *);

 int (*resume)(struct platform_device *);

 struct device_driver driver;

};

Probe()函数必须验证指定设备的硬件是否真的存在,probe()可以使用设备的资源,包括时钟,platform_data等。一般来说设备是不能被热插拔的,所以可以将probe()函数放在init段里面来节省driver运行时候的内存开销:

int platform_driver_probe(struct platform_driver *drv, int (*probe)(structplatform_device *))

Platform driver可以通过下面的函数完成对驱动的注册:

int platform_driver_register(structplatform_driver *drv)

注销使用:

void platform_driver_unregister(struct platform_driver *drv)

2. 中断处理

在Linux驱动程序中,为设备实现一个中断包含 两个步骤1.向内核注册(申请中断)中断 2.实现中断处理函数

request_irq用于实现中断的注册:

int request_irq(unsigned in irq, void(*handler)(int, void *, struct pt_regs *), unsigned long flags, const char *devname, void*dev_id)

向内核申请中断号为irq,中断处理函数为handler指针指向的函数,中断标志为flag,设备名为devname的中断。成功返回0,或者返回一个错误码。

当request_irq不用于共享中断时,dev_id可以为NULL,或者指向驱动程序自己的私有数据。但用于共享中断时dev_id必须唯一。因为free_irq时也需要dev_id做参数,这样free_irq才知道要卸载共享中断上哪个中断服务处理函数。共享中断会在后面讲到。

在flag参数中,可以选以下参数
 

IRQF_DISABLED(SA_INTERRUPT)

如果设置该位,表示是一个“快速”中断处理程序,如果没有,那么就是一个“慢速”中断处理程序。

IRQF_SHARED(SA_SHITQ)

该位表示中断可以在设备间共享。

2.1 快速/慢速中断

这两种类型的中断处理程序的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。换句话说,也就是开启中断标志位在运行快速中断处理程序时

关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其他类型中断扔可以得到服务。

2.2 共享中断

共享中断就是将不同的设备挂到同一个中断信号线上。linux对共享的支持主要是位PCI设备服务。

2.3 释放中断

void free_irq(unsigned int irq)

当设备不再需要使用中断时(通常是设备关闭和驱动卸载时),应该使用该函数把他们返回给内核使用。

2.4 禁用中断

void disable_irq(int irq)

当一些代码中不能使用中断时(如支持自旋锁的上下文中)使用该函数禁用中断。

2.5 启用中断

void enable_irq(int irq)

当禁止后可以使用该函数重新启用。

3. 驱动代码

该驱动实现能够读取按键按下的键值,比如说如果是第一个键按下读取的键值就为1。

3.1 platform平台设备

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
static struct resource key_resource[]=
{ 	
	[0] = {
		.start = IRQ_EINT8,
		.end = IRQ_EINT8,
		.flags = IORESOURCE_IRQ,
	},
	[1] = {
		.start = IRQ_EINT11,
		.end = IRQ_EINT11,
		.flags = IORESOURCE_IRQ,
	},
	[2]= {
		.start = IRQ_EINT13,
		.end = IRQ_EINT13,
		.flags = IORESOURCE_IRQ,
	},
	[3] = {
		.start = IRQ_EINT14,
		.end = IRQ_EINT14,
		.flags = IORESOURCE_IRQ,
	},
	[4] = {
		.start = IRQ_EINT15,
		.end = IRQ_EINT15,
		.flags = IORESOURCE_IRQ,
	},
	[5] = {
		.start = IRQ_EINT19,
		.end = IRQ_EINT19,
		.flags = IORESOURCE_IRQ,
	},
};
 
struct platform_device *my_buttons_dev;
 
static int __init platform_dev_init(void)
{
	int ret;
	
	my_buttons_dev = platform_device_alloc("my_buttons", -1);
	
	platform_device_add_resources(my_buttons_dev,key_resource,6);//添加资源一定要用该函数,不能使用对platform_device->resource幅值
																//否则会导致platform_device_unregister调用失败,内核异常。
	
	ret = platform_device_add(my_buttons_dev);
	
	if(ret)
		platform_device_put(my_buttons_dev);
	
	return ret;
}
 
static void __exit platform_dev_exit(void)
{
	platform_device_unregister(my_buttons_dev);
}
 
module_init(platform_dev_init);
module_exit(platform_dev_exit);
 
MODULE_AUTHOR("Y-Kee");
MODULE_LICENSE("GPL");

3.2 platform平台驱动

//platform driver
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
 
static int buttons_irq[6];
 
struct irq_des
{
	int *buttons_irq;
	char *name[6];
};
 
 
struct irq_des button_irqs = { 
	.buttons_irq = buttons_irq,
	.name = {"KEY0", "KEY1", "KEY2", "KEY3", "KEY4", "KEY5"},
};
 
static volatile int key_values;
 
 
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
 
 
static volatile int ev_press = 0;
 
 
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
  	int i;
	for(i=0; i<6; i++){
		if(irq == buttons_irq[i]){
			key_values = i;
			ev_press = 1;
			wake_up_interruptible(&button_waitq);	
		}
	}
	
    return IRQ_RETVAL(IRQ_HANDLED);
}
static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
    int i;
    int err = 0;
    
    for (i = 0; i < 6; i++) {
        err = request_irq(button_irqs.buttons_irq[i], buttons_interrupt, IRQ_TYPE_EDGE_BOTH, 
                          button_irqs.name[i], (void *)&button_irqs.buttons_irq[i]);
        if (err)
            break;
    }
    if (err) {
        i--;
        for (; i >= 0; i--) {
	    if (button_irqs.buttons_irq[i] < 0) {
		continue;
	    }
	    disable_irq(button_irqs.buttons_irq[i]);
            free_irq(button_irqs.buttons_irq[i], (void *)&button_irqs.buttons_irq[i]);
        }
        return -EBUSY;
    }
    
    return 0;
}
static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
{
    int i;
    
    for (i = 0; i < 6; i++) {
	free_irq(button_irqs.buttons_irq[i], (void *)&button_irqs.buttons_irq[i]);
    }
    return 0;
}
static int s3c24xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
    unsigned long err;
    if (!ev_press) {
	if (filp->f_flags & O_NONBLOCK)
	    return -EAGAIN;
	else
	    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 : min(sizeof(key_values), count);
}
 
static unsigned int s3c24xx_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    =   s3c24xx_buttons_open,
    .release =   s3c24xx_buttons_close, 
    .read    =   s3c24xx_buttons_read,
    .poll    =   s3c24xx_buttons_poll,
};
 
static struct miscdevice misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "my_buttons",
	.fops = &dev_fops,
};
 
 
static int my_plat_probe(struct platform_device *dev)
{
	int ret,i;
	struct resource *plat_resource;
	struct platform_device *pdev = dev;
	
	printk("my platform dirver find my platfrom device.\n");
 
	for(i=0; i<6; i++){
		plat_resource = platform_get_resource(pdev,IORESOURCE_IRQ,i);
		if(plat_resource == NULL)
			return -ENOENT;	
		buttons_irq[i] = plat_resource->start;
	}
 
	ret = misc_register(&misc);
	if(ret)
		return ret;
 
	
	return 0;
}
 
static int my_plat_remove(struct platform_device *dev)
{
	printk("my platfrom device has removed.\n");
	misc_deregister(&misc);
	return 0;
}
 
struct platform_driver my_buttons_drv = { 
	.probe = my_plat_probe,
	.remove = my_plat_remove,
	.driver = { 
		.owner = THIS_MODULE,
		.name = "my_buttons",
	},
};
 
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("Y-Kee");
MODULE_LICENSE("GPL");

4. 测试代码

/*
 *      Buttons Example for Matrix V
 *
 *      Copyright (C) 2004 capbily - friendly-arm
 *	[email protected]
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
int main(void)
{
	int buttons_fd;
	int key_value;
 
	buttons_fd = open("/dev/buttons", 0);
	if (buttons_fd < 0) {
		perror("open device buttons");
		exit(1);
	}
	for (;;) {
		fd_set rds;
		int ret;
		FD_ZERO(&rds);
		FD_SET(buttons_fd, &rds);
		ret = select(buttons_fd + 1, &rds, NULL, NULL, NULL);
		if (ret < 0) {
			perror("select");
			exit(1);
		}
		if (ret == 0) {
			printf("Timeout.\n");
		} else if (FD_ISSET(buttons_fd, &rds)) {
			int ret = read(buttons_fd, &key_value, sizeof key_value);
			if (ret != sizeof key_value) {
				if (errno != EAGAIN)
					perror("read buttons\n");
				continue;
			} else {
				printf("buttons_value: %d\n", key_value+1);
			}
				
		}
	}
	close(buttons_fd);
	return 0;
}

4.1 测试结果

Linux设备驱动之platform按键驱动_第1张图片

运行测试程序后按下第二个键,中断上打印了多次按键的键值,产生原因是因为按键抖动。导致按一下按键,产生多次中断。

你可能感兴趣的:(Linux,嵌入式,C/C++)