一、摘要:
linux内核的驱动的实现是分成驱动、总线、设备模型,采用面向对象的编程思维。为了减低成本、功耗,大多数嵌入式微处理器的SOC上面已经集成i2c、spi等总线,但是并不是所有设备都符合这些总线。所以,linux内核为了维护驱动是高内聚、低耦合并且符合驱动、总线、设备模型,而开发出一个platform虚拟总线。
二、原理:
1、明确硬件的基本信息或者资源,分配并初始化struct platform_device *plat_dev变量。
2、根据struct platform_device *plat_dev变量传递过来的硬件资源进行初始化处理(映射、申请、根据具体功能配置相应的寄存器),明确获取信号的流程以及提交信号到用户空间的过程。根据前面的步骤初始化struct platform_driver *plat_drv变量,最后把plat_drv变量注册进内核。
3、plat_dev和plat_drv的关联,platform总线会维护着该总线的驱动和设备这两条双向链表,当有platform_device变量注册是,会遍历驱动链表的驱动与之匹配(具体的匹配函数是platform总线的match指向的匹配函数)。相反,如果注册platform_driver变量,就会遍历设备链表的每一个设备与该注册的驱动进行匹配。每当匹配成功后,会进行调用platform_driver变量的probe指向的探测函数。如果匹配失败,就会先加入链表,等待合适的设备/驱动注册后再匹配。
三、源码:
1、注册platform_device的源码:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
/**********指定需要用到的GPIO的基地址************/
#define GPIO0 0xfffff700
#define GPIO1 0xfffff701
/**********自定义的结构体,为了保存硬件信息************/
struct _key_info{
char *name;
unsigned long gpio_base;
unsigned long gpio_num;
};
/**********指明需要用到的硬件资源************/
struct resource res[] = {
[0] = {
.start = 10,
.end = 10,
.flags = IORESOURCE_IRQ,
},
[1] = {
.start = 12,
.end = 12,
.flags = IORESOURCE_IRQ,
}
};
/**********初始化需要的硬件信息************/
struct _key_info key_info[] = {
[0] = {
.gpio_base = GPIO0,
.gpio_num = 10,
.name = "GPIO0-B2",
},
[1] = {
.gpio_base = GPIO1,
.gpio_num = 12,
.name = "GPIO1-B4"
},
};
/**********定义、初始化platform_device变量************/
struct platform_device key_dev = {
//该设备名最好和驱动的名字一致,这是设备和驱动匹配是否成功的最后保障。
.name = "rk3399_key_demo",
.resource = res,
.num_resources = 2,
.dev = {
//该指针可以指向传递给驱动的私有数据,所以用此来指定保存其它硬件信息的自定义结构体变量
.platform_data = key_info,
}
};
int __init rk3399_key_dev_init(void)
{
//加载设备模块时,注册platform_device变量
platform_device_register(&key_dev);
return 0;
}
void __exit rk3399_key_dev_exit(void)
{
//卸载模块时,注销platform_device变量
platform_device_unregister(&key_dev);
}
module_init(rk3399_key_dev_init);
module_exit(rk3399_key_dev_exit);
MODULE_LICENSE("GPL");
注:硬件信息,需要根据自已的硬件环境来定,上述的硬件信息和rk3399的平台信息不对应,主要是想提供一个框架。
2、注册platform_driver变量:
/*
*步骤:
*1、使用字符设备来创建一个设备文件,可以使用户空间通过该设备文件读取键值。
*2、使用到中断,当硬件有数据的时候,会产生中断通知CPU。避免了CPU不断轮询键值,提高CPU工作效率。
*3、使用定时器进行消抖,每当一个中断到来的时候,设置定时器超时处理函数延迟10ms执行。
*4、使用等待队列使进程没有新的键值读取的时候,进入睡眠状态,让出CPU。
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/timer.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
/***********自定义需要的结构体和定义所需的变量*************/
#define DRV_NAME "rk3399_key_demo"
//struct input_dev *in_dev = NULL;
struct timer_list dev_time;
struct cdev *c_dev;
struct class *dev_class;
struct device *dev_device;
wait_queue_head_t wait_que;
dev_t dev_num;
int dev_minor = 0;
int dev_major = 0;
int key_value;
int key_control_flag = 0;
struct _key_info{
char *name;
unsigned long gpio_base;
unsigned long gpio_num;
};
struct _gpio_reg{
void *gpiocon;
void *gpiopub;
void *gpiodat;
}gpio_reg[2];
/***********下面的四个函数是进程对设备的具体实现**************/
int rk3399_key_open(struct inode *inode, struct file *filp)
{
return 0;
}
int rk3399_key_release(struct inode *inode, struct file *filp)
{
return 0;
}
ssize_t rk3399_key_read(struct file *filp, char __user *buf, size_t count, loff_t *loff)
{
int r_size = count;
if(r_size > sizeof(int)){
r_size = sizeof(int);
}
/*******当条件不满足/为0时,进入睡眠等待*********/
wait_event_interruptible(wait_que, key_control_flag);
/*******条件满足时,要更改条件状态,以防重复读取某个值*********/
key_control_flag = 0;
/*********把键值上报给进程***********/
copy_to_user(buf, &key_value, r_size);
return r_size;
}
ssize_t rk3399_key_write(struct file *filp, const char __user *buf, size_t count, loff_t *loff)
{
return count;
}
/************对gpio进行映射,并设置*******************/
void gpio_key_init(unsigned long addr, unsigned long gpio_num, int num)
{
/*********gpio addr ioremap*********/
gpio_reg[num].gpiocon = ioremap(addr, 24); //rk3399 64位/8字节,需要映射3个寄存器
gpio_reg[num].gpiopub = gpio_reg[num].gpiocon + 8;
gpio_reg[num].gpiodat = gpio_reg[num].gpiocon + 16;
/**********gpio set****************/
/*下面可以通过控制register的值来设置,gpiocon为输入设置寄存器,gpiopub为上/下拉设置*/
//...
}
/*************定时器超时处理函数*****************/
void rk3399_key_time_func(unsigned long data)
{
struct _key_info *pdata = (struct _key_info *)data;
unsigned long pintdata;
switch(pdata->gpio_num){
case 10:
pintdata = *(unsigned long *)(gpio_reg[0].gpiodat);
if(pintdata == 0){ //按下
key_value = 0x01;
}else{//松开
key_value = 0x02;
}
break;
case 12:
pintdata = *(unsigned long *)(gpio_reg[1].gpiodat);
if(pintdata == 0){ //按下
key_value = 0x03;
}else{//松开
key_value = 0x04;
}
break;
default :
return;
}
/****在唤醒等待队列前,设置条件为真******/
key_control_flag = 1;
/*******唤醒在等待队列上睡眠的某一个进程********/
wake_up_interruptible(&wait_que);
}
/*******中断处理函数*********/
irqreturn_t dev_irq_isr(int irq_num,void *data)
{
/*******每发生一个中断,都把定时器延迟10ms********/
dev_time.data = (unsigned long)data;
mod_timer(&dev_time, jiffies + 1 * HZ / 100);
return IRQ_HANDLED;
}
/********文件操作函数集合**********/
struct file_operations fop = {
.owner = THIS_MODULE,
.open = rk3399_key_open,
.write = rk3399_key_write,
.read = rk3399_key_read,
.release = rk3399_key_release,
};
int rk3399_key_probe(struct platform_device *dev)
{
int ret = -1, i;
struct resource *res = dev->resource;
struct _key_info *pdata = dev->dev.platform_data;
/*********根据不同情况,可以动态或者静态申请设备号**************/
if(!dev_major){
alloc_chrdev_region(&dev_num, dev_minor, 1, DRV_NAME);
dev_major = MAJOR(dev_num);
dev_minor = MINOR(dev_num);
}else{
dev_num = MKDEV(dev_major, dev_minor);
ret = register_chrdev_region(dev_num, 1, DRV_NAME);
if(ret < 0){
printk("register chrdev region fail.\n");
return ret;
}
}
/*************申请字符设备的空间、初始化以及向内核添加该字符设备**************/
c_dev = cdev_alloc();
if(IS_ERR(c_dev)){
printk("cdev_alloc fail: %ld,\n",PTR_ERR(c_dev));
goto cdev_err;
}
cdev_init(c_dev, &fop);
cdev_add(c_dev, dev_num, 1);
/*************为该模块申请一个类***************/
dev_class = class_create(THIS_MODULE, DRV_NAME);
if(IS_ERR(dev_class)){
printk("class_create fail.\n");
goto class_create_fail;
}
/*************在该类上,根据设备号申请设备***************/
dev_device = device_create(dev_class, NULL, dev_num, NULL, DRV_NAME);
if(IS_ERR(dev_device)){
printk("device_create fail.\n");
goto device_create_fail;
}
/*************初始化等待队列头***************/
init_waitqueue_head(&wait_que);
/*************初始化定时器,并且指定定时器超时处理函数***************/
init_timer(&dev_time);
dev_time.function = rk3399_key_time_func;
/*************gpio的初始化,以及中断的申请***************/
for(i = 0; i < dev->num_resources; i++){
gpio_key_init(pdata[i].gpio_base, pdata[i].gpio_num, i);
ret = request_irq(res[i].start, dev_irq_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
pdata[i].name, &pdata[i]);
if(ret < 0){
printk("request_irq fail.\n");
goto request_irq_fail;
}
}
return 0;
request_irq_fail:
del_timer(&dev_time);
for(--i; i >= 0; i--){
iounmap(gpio_reg[i].gpiocon);
free_irq(res[i].start, &pdata[i]);
}
device_destroy(dev_class, dev_num);
device_create_fail:
class_destroy(dev_class);
class_create_fail:
cdev_del(c_dev);
cdev_err:
unregister_chrdev_region(dev_num, 1);
return ret;
}
/*********卸载模块时,需要释放资源************/
int rk3399_key_remove(struct platform_device *dev)
{
struct resource *res = dev->resource;
struct _key_info *pdata = dev->dev.platform_data;
int i;
del_timer(&dev_time);
for(i = 0; i < dev->num_resources; i++){
iounmap(gpio_reg[i].gpiocon);
free_irq(res[i].start, &pdata[i]);
}
device_destroy(dev_class, dev_num);
class_destroy(dev_class);
cdev_del(c_dev);
unregister_chrdev_region(dev_num, 1);
return 0;
}
struct platform_driver rk3399_key_drv = {
.probe = rk3399_key_probe,
.remove = rk3399_key_remove,
.driver = {
.owner = THIS_MODULE,
.name = DRV_NAME,
}
};
int __init rk3399_key_drv_init(void)
{
platform_driver_register(&rk3399_key_drv);
return 0;
}
void __exit rk3399_key_drv_exit(void)
{
platform_driver_unregister(&rk3399_key_drv);
}
module_init(rk3399_key_drv_init);
module_exit(rk3399_key_drv_exit);
MODULE_LICENSE("GPL");