之前学习了linux设备驱动模型,为加深理解,将之前的按键驱动程序改写成用linux设备模型的方法来实现
有了设备驱动模型,驱动的架构统一简洁,而且真正要深入学习驱动程序的,这个驱动模型的架构是一定要熟悉的,这期间参考了
http://blog.youkuaiyun.com/wulong117/article/details/7376509
这位仁兄的代码,在此表示感谢。
先简单回顾一下设备驱动模型,几个要素,总线,设备,驱动和类,这里的类我用的是input_dev,也就是input输入子系统
这里我的按键注册为平台设备platform_device
驱动注册为平台驱动platform_driver
他们都挂在总线platform上
这个platform总线是linux系统的一个虚拟总线,用于实现SOC片上外设的挂接,很方便,类似的还有很多其他的总线,比如iic,这些通过/sys目录可以查看
platform_device代码很简单,就是申明出设备使用的硬件资源,比如io地址,中断号,然后模块加载时注册设备
paltform_driver就是驱动程序啦,他从与之匹配的platform_device那里获得硬件资源的信息,然后进行相关操作,这里跟之前的驱动程序干的事差不多,详见代码注释
而这个匹配的工作就是由platform总线来做,platform总线已经再内核中实现好了,开机就已经加载好,就不用我们操心了
我们分别将platform_device和platform_driver加载到总线上,总线会跟据他们里面的名字进行匹配,匹配上了就执行platform_driver中的probe函数,这个函数就开始从
platform_device获取资源信息,然后各种初始化。
总体来看,比如我最后编译好的模块,设备模块是key_dev.ko,驱动模块是key_drv.ko,分别insmod这两个模块,顺序无所谓,反正只要总线上由设备或者驱动注册,总线都会进行扫描匹配工作的,insmod完了,如果名称一样他们就匹配上了,驱动程序就正常工作了。
这里我的按键操作,沿用了上一篇中的input子系统输入的方法,操作简便。
下面是代码。
先看设备模块
#include<linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/mm.h>
#include<linux/sched.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<asm/io.h>
#include<asm/system.h>
#include<asm/uaccess.h>
#include<mach/regs-gpio.h>
#include<linux/interrupt.h>
#include<linux/irq.h>
#include<linux/slab.h>
#include<linux/sched.h>
#include<linux/wait.h>
#include<mach/gpio-fns.h>
#include<linux/ioport.h>
#include<linux/platform_device.h>
//GPF的端口地址
#define GPF_PA (0x56000050)
//平台设备
struct platform_device *key_dev;
//定义设备资源,这里两项,io地址资源和中断号资源
struct resource key_resource[]={
[0]={
.start=GPF_PA,
.end=GPF_PA+8,
.flags=IORESOURCE_MEM,
},
[1]={
.start=IRQ_EINT0,
.end=IRQ_EINT0,
.flags=IORESOURCE_IRQ,
},
};
static int __init s3c2440_key_init(void)
{
int ret;
//注册平台设备,第一个参数为设备名,这是与平台驱动driver进行匹配的依据,-1是id,自动分配,不care
key_dev=platform_device_alloc("platform_key",-1);
//为平台设备添加资源
platform_device_add_resources(key_dev,key_resource,2);
//添加设备
ret=platform_device_add(key_dev);
if(ret)
platform_device_put(key_dev);
printk(KERN_NOTICE "platform device added\n");
return ret;
}
static void __exit s3c2440_key_exit(void)
{
platform_device_unregister(key_dev);
}
MODULE_AUTHOR("weicz");
MODULE_LICENSE("Dual BSD/GPL");
module_init(s3c2440_key_init);
module_exit(s3c2440_key_exit);
再看驱动模块
#include<linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/mm.h>
#include<linux/sched.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<asm/io.h>
#include<asm/system.h>
#include<asm/uaccess.h>
#include<mach/regs-gpio.h>
#include<linux/interrupt.h>
#include<linux/irq.h>
#include<linux/slab.h>
#include<linux/sched.h>
#include<linux/wait.h>
#include<mach/gpio-fns.h>
#include<linux/ioport.h>
#include<linux/platform_device.h>
#include<asm/bitops.h>
#include<linux/input.h>
#define DEVICE_NAME "plat_key_driver"
//#define GPFCON (0x56000050)
//#define GPFDAT (0x56000054)
//#define GPFCFG (0x0002)
//地址映射后的io虚拟地址,这里用到了GPIOF,只用GPFCON和GPFDAT两个寄存器
//GPFCON 16bit GPFDAT 8bit
void __iomem *gpfcon;
void __iomem *gpfdat;
//定义输入设备,使用输入子系统
struct input_dev *key_input_dev;
//定义中断资源,这里只做了一个按键,这个结构显得有点多余,为的方便扩展
struct irq_des
{
int key_irq;
char *name;
};
struct irq_des key_irqs={
.name="KEY0",
};
//中断处理函数
static irqreturn_t s3c2440_eint_key(int irq,void *dev_id)
{
int value;
//读出端口状态,并且只关心做后一个bit,对应我的按键key0
value=ioread8(gpfdat);
value=value & 0x01;
//讲述据上报给输入子系统
input_report_key(key_input_dev,KEY_0,value);
//printk(KERN_NOTICE "value=%d\n",value);
input_sync(key_input_dev);
return IRQ_HANDLED;
}
//打开函数,主要工作就是申请中断
static int s3c2440_key_open(struct input_dev *dev)
{
set_irq_type(key_irqs.key_irq,IRQF_TRIGGER_RISING);
if(request_irq(key_irqs.key_irq,s3c2440_eint_key,IRQF_SAMPLE_RANDOM,key_irqs.name,NULL))
return -1;
printk(KERN_NOTICE "key_open\n");
return 0;
}
static void s3c2440_key_release(struct input_dev *dev)
{
disable_irq_nosync(key_irqs.key_irq);
free_irq(key_irqs.key_irq,NULL);
}
//probe函数,这是driver的核心内容
//当总线匹配到driver和device时,调用driver的probe函数
//这里主要做的工作就是从对应的device的resource中获得device的硬件资源
//比如io地址,做地址映射,同时为对应的按键申请输入子系统的相关数据结构
static int key_probe(struct platform_device *dev)
{
int ret=0;
int value;
struct resource *plat_resource;
struct platform_device *pdev=dev;
struct input_dev *input_dev;
printk(KERN_NOTICE "platform driver match platform device\n");
//get resource[0]
//num代表的不是resource数组的数组号
//而是具有相同资源类型的序号(从0开始的序号)
//在本例中,因为IORESOURCE_IRQ类型的资源只有一个
//故platform_get_irq()函数的第二个参数是0,而不是resource数组的序号1
//获取io地址资源
plat_resource=platform_get_resource(pdev,IORESOURCE_MEM,0);
//申请iomem资源
request_mem_region(plat_resource->start,resource_size(plat_resource),"platform key io_res");
//地址映射,每个地址4个字节
//GPFCON
gpfcon=ioremap(plat_resource->start,0x00000004);
//GPFDAT
gpfdat=ioremap(plat_resource->start+4,0x00000004);
//由于我的板子网卡用到了GPF的管脚,为防止配置中破坏其他管教配置
//先读出原有配置,再对按键对应的GPF0进行单独配置,也就是最低两位配置位10,代表中断模式,具体的参见手册
value=ioread16(gpfcon);
value=(value | (0x1<<1))&(0xfffe);
//写回配置
iowrite16(value,gpfcon);
//获取中断号资源
plat_resource=platform_get_resource(pdev,IORESOURCE_IRQ,0);
//向全局的数据结构中填写中断号,这个值再open函数调用时进行中断申请
key_irqs.key_irq=plat_resource->start;
//申请input子系统资源
input_dev=input_allocate_device();
key_input_dev=input_dev;
//input子系统的按键配置
set_bit(EV_KEY,key_input_dev->evbit);
set_bit(KEY_0,key_input_dev->keybit);
key_input_dev->name="mykeys";
key_input_dev->dev.init_name="input_key";
key_input_dev->open=s3c2440_key_open;
key_input_dev->close=s3c2440_key_release;
ret=input_register_device(key_input_dev);
if(ret)
return -1;
printk(KERN_NOTICE "registered\n");
return ret;
}
static int key_remove(struct platform_device *dev)
{
printk(KERN_NOTICE "platform device removed\n");
input_unregister_device(key_input_dev);
return 0;
}
struct platform_driver key_drv={
.probe=key_probe,
.remove=key_remove,
.driver={
.owner=THIS_MODULE,
//这个名字要和对应的device中的名字一致,这是匹配dirver和device的依据
.name="platform_key",
},
};
static int __init s3c2440_key_init(void)
{
int ret;
//注册驱动程序
ret=platform_driver_register(&key_drv);
return ret;
}
static void __exit s3c2440_key_exit(void)
{
platform_driver_unregister(&key_drv);
}
MODULE_AUTHOR("weicz");
MODULE_LICENSE("Dual BSD/GPL");
module_init(s3c2440_key_init);
module_exit(s3c2440_key_exit);
测试代码就是上一篇按键input子系统的代码,不用改动,makefile也一样,改个名字就行
看看/sys/bus/platform/devices和/sys/bus/platform/driver目录下都会多出来platform_key
/sys/class/input目录下会有input_key