第一、 首先进入\arch\arm\plat-s3c24xx\devs.c文件中,在最后一行定义设备
/* my_led device
author : jiang chuan yu
*/
static struct resource s3c_my_led_resource[] = {
[0] = {
.start = S3C24XX_PA_GPIO,
.end = S3C24XX_PA_GPIO + S3C24XX_SZ_GPIO - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_EINT0,
.end = IRQ_EINT0,
.flags = IORESOURCE_IRQ,
},
[2] = {
.start = IRQ_EINT2,
.end = IRQ_EINT2,
.flags = IORESOURCE_IRQ,
},
[3] = {
.start = IRQ_EINT3,
.end = IRQ_EINT3,
.flags = IORESOURCE_IRQ,
},
[4] = {
.start = IRQ_EINT4,
.end = IRQ_EINT4,
.flags = IORESOURCE_IRQ,
}
};
struct platform_device s3c_device_my_led= {
.name = "s3c24xx-my-led",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_adc_resource),
.resource = s3c_my_led_resource,
};
EXPORT_SYMBOL(s3c_device_my_led);
第二、 进入\arch\arm\mach-s3c2440\Mach-smdk2440.c文件
在static struct platform_device *结构体指针数组smdk2440_devices的最后一行添加如下蓝色代码
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
&s3c_device_rtc,
&s3c_device_usbgadget,
&s3c_device_my_led,
};
当系统启动时会调用smdk2440_machine_init函数,而在此函数中调用了platform_add_devices函数 platform_add_devices,语句为:
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
第一个参数就是上面的数组,第二个参数是数组的长度(ARRAY_SIZE是求一个数组长度的宏定义,详情请查看内核源码)。在platform_add_devices函数中就是对smdk2440_devices当中所有的设备进行注册,内核源码如下:
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) {
ret = platform_device_register(devs[i]);
if (ret) {
while (--i >= 0)
platform_device_unregister(devs[i]);
break;
}
}
return ret;
}
第四、 进入\arch\arm\plat-s3c\include\plat\devs.h中的相应位置添加如下的代码,这样才能被\arch\arm\mach-s3c2440\Mach-smdk2440.c(有用此宏代码#include <plat/devs.h>)文件使用
extern struct platform_device s3c_device_my_led;
上述操作之后就会在平台总线上注册了设备,设备的名字为s3c24xx-my-led,怎么知道你的平台设备是否在总线呐?运行开发板,通过终端进入/sys/bus/platform/devices目录,此目录中包括了注册在platform总线上的所有的设备,当然sys/bus/platform/derivers目录中包括的所有的注册到platform总线上的驱动。如果想查看某一总线上是否注册了某个设备或者驱动,你就可以到/sys/bus/目录中的相应目录来查看。
我编译内核后将内核下载到开发板上运行在/sys/bus/platform/devices目录中就有了s3c24xx-my-led的设备如图所示:
好了现在我们来编写与次设备对应的驱动吧!
第一、要建立一个struct platform_driver的变量,变量名自定。我在驱动源码中是这样定义的,如下
static struct platform_driver s3c2440mixled_driver = {
.probe = s3c2440my_led_probe,
.remove = s3c2440my_led_remove,
.driver = {
.name = "s3c24xx-my-led",
.owner = THIS_MODULE,//
},
};
该结构体当中的成员有很多在此用到了3个,其中driver函数一定要有,当然probe也应该有。定义了变量肯定要对其中的变量初始化,所以在s3c2440mixled_driver的前面一定要有s3c2440my_led_probe和 s3c2440my_led_remove定义或者声明,对于其中的成员变量driver就直接使用静态数据,所以前面就不需要在定义了struct device_driver 结构体或者结构体指针了。
第二、既然设备驱动已经建立好了,那么就要向platform设备总线注册。注册platform总线设备驱动需要用函数platform_driver_register。在我的驱动源码中是这样实现的。如下:
static int __init my_led_init(void)
{
int iret = 0;
//注册平台设备驱动
iret = platform_driver_register(&s3c2440mixled_driver);
if(iret < 0 )
{
printk("platform driver register error!\n");
return -1;
}
#if DEBUG
printk("load my my_led module !\n\r");
#endif //end DEBUG
return 0;
}
向platform总线注册要在模块初始化函数当中执行。这样加入模块的时候就会向platform总线注册,在代码中在加上构建模块的一些代码后,一个最简单的platform驱动模型就基本建成了,加入模块后就会将驱动挂接到platform总线上,平台设备驱动中的s3c2440my_led_probe探测函数就可以执行了。
当然应用程序要访问还要差一步呀,
1. 在程序中还要做(不有顺序)
2. 建立一个设备,
3. 添加一些文件设备操作函数,
4. 在向内核注册该设备,
5. 根据设备号创建结点(当然可以在终端用mknod来创建,对于设备号也可自动产生或者指定设备号,但是如果是自动产生你要把设备中的主设备号和次设备号打印出来才能使用mknod创建对应与设备号的结点。在自动产生的时候最好使用自动创建结点。)在我的源码中是自动创建设备号,根据设备号自动创建设备结点。
需要注意的是:
我们在单独写没有涉及到字符设备驱动的时候,是把设备的注册和向内核添加设备都写在了模块初始化函数当中,在写平台设备驱动时我们是把他们写在探测函数当中,
而在模块初始化当中只实现了平台设备驱动向总线的注册。因为驱动向内核注册成功后会寻找遍历该总线上的设备,若有与该设备驱动名字或者设备号相同的设备就说明匹配成功,然后会执行平台设备驱动的探测函数。这样一个流程下来就可以运行设备注册和添加到内核的程序代码了。
先把我的源程序复制如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#include <linux/io.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <mach/map.h>
#define ON 1
#define OFF 0
#define DEBUG 0N
#define DEVICE_NAME "my_led"
struct cdev my_led_cdev;
int my_led_open(struct inode *inode, struct file *file);
int my_led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
int my_led_close(struct inode *inode, struct file *file);
struct file_operations my_led_fops =
{
.open = my_led_open,
.ioctl = my_led_ioctl,
.release = my_led_close,
};
/**************调用该函数自动创建设备结点****************start自动创建结点代码*****************/
static struct class *class_for_node; //define mixled class
int auto_create_node(dev_t dev_no, char *dev_name)
{
struct device * dev_tmp=NULL;//用于对返回值进行检测
// register device class
class_for_node = class_create(THIS_MODULE, dev_name);//dev/
if(IS_ERR(class_for_node))
{
printk(" create class faild!\n");
return -1;
}
//create device name node
dev_tmp = device_create(class_for_node, NULL,dev_no , NULL, dev_name);//dev/
if(dev_tmp == NULL)
{
printk("create class device error!\n");
return -1;
}
return 0;
}
/***********************end 自动创建结点代码***************************************/
int my_led_open(struct inode *inode, struct file *file)
{
unsigned int value = 0 ;
value |= (1<<10)|(1<<12)|(1<<16)|(1<<20);
writel(value,S3C2410_GPBCON);
value = 0;
writel(value,S3C2410_GPBUP);
return 0;
}
#define MY_LED_ON 1
#define MY_LED_OFF 0
#define LED1 1
#define LED2 2
#define LED3 3
#define LED4 4
int my_led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
//writel(0,S3C2410_GPBDAT);
unsigned int value = 0;
value = readl(S3C2410_GPBDAT);
switch(arg)
{
case LED1 :
if(MY_LED_ON == cmd)
{
writel(value&(~(1<<5)) ,S3C2410_GPBDAT);
}
else if(MY_LED_OFF == cmd)
{
writel((value|(1<<5)),S3C2410_GPBDAT);
}
break;
case LED2 :
if(MY_LED_ON == cmd)
{
writel(value&(~(1<<6)),S3C2410_GPBDAT);
}
else if(MY_LED_OFF == cmd)
{
writel((value|(1<<6)),S3C2410_GPBDAT);
}
break;
case LED3 :
if(MY_LED_ON == cmd)
{
writel(value&(~(1<<8)),S3C2410_GPBDAT);
}
else if(MY_LED_OFF == cmd)
{
writel((value|(1<<8)),S3C2410_GPBDAT);
}
break;
case LED4 :
if(MY_LED_ON == cmd)
{
writel(value&(~(1<<10)),S3C2410_GPBDAT);
}
else if(MY_LED_OFF == cmd)
{
writel((value|(1<<10)),S3C2410_GPBDAT);
}
break;
default :
writel(value&(~((1<<5)|(1<<6)|(1<<8)|(1<<10))),S3C2410_GPBDAT);
break;
}
return 0;
}
int my_led_close(struct inode *inode, struct file *file)
{
printk("close the led dev!\n\r");
return 0;
}
dev_t my_led_no = 0;
static int __init s3c2440my_led_probe(struct platform_device *pdev)
{
unsigned int iret = 0;
//my_led_no = MKDEV(MAIN_DEV_NUM, MINOR_DEV_NUM);//内核源码的定义为#define MKDEV(ma,mi) ((ma)<<8 | (mi))
//register_chrdev_region(my_led_no,1,DEVICE_NAME);
iret = alloc_chrdev_region(&my_led_no, 0,1, DEVICE_NAME);//申请设备号,并注册设备
if(iret < 0)
{
printk("alloc devno error\n");
return -ENOENT;
}
printk("entr into probe function !\n\r");
printk("maior = %d\n",my_led_no>>8);
cdev_init(&my_led_cdev,&my_led_fops);//也可以对my_led_cdev中的ops变量直接赋值,此函数就是对cdev中变量进行初始化
my_led_cdev.owner = THIS_MODULE;//((struct module *)0)
cdev_add(&my_led_cdev, my_led_no, 1);//第二个的类型是dev_t类型(即unsigned long型)存储主设备和次设备号
auto_create_node(my_led_no,DEVICE_NAME);//掉用此函数可以自动创建结点
return 0;
}
static int s3c2440my_led_remove(struct platform_device *pdev)
{
printk("call the function of platform driver \n\r");
return 0;
}
/*define platform driver *///建立平台设备驱动
static struct platform_driver s3c2440mixled_driver = {
.probe = s3c2440my_led_probe,
.remove = s3c2440my_led_remove,
.driver = {
.name = "s3c24xx-my-led",
.owner = THIS_MODULE,//
},
};
static int __init my_led_init(void)
{
int iret = 0;
//注册平台设备驱动
iret = platform_driver_register(&s3c2440mixled_driver);
if(iret < 0 )
{
printk("platform driver register error!\n");
return -1;
}
#if DEBUG
printk("load my my_led module !\n\r");
#endif //end DEBUG
return 0;
}
static void __exit my_led_exit(void)
{
cdev_del(&my_led_cdev);
unregister_chrdev_region(my_led_no, 1);
printk("unload my my_led module !\n\r");
}
module_init(my_led_init);
module_exit(my_led_exit);
MODULE_LICENSE("GPL");
以上就是我的简单的平台设备源码了。
现在来分析一下调用向platform总线注册平台设备驱动函数如何就执行探测函数
设计平台设备驱动,在模块初始化函数中执行平台注册函数 platform_driver_register,但是内核执行该代码后会匹配平台总线上的设备,如匹配成功还会执行平台设备驱动结构体当中的probe指针指向的函数。但是为什么呐?
调用platform_driver_register函数,函数中会对platform_driver当中的函数指针进行赋值(对于赋值需要满足的条件,请参看系统源码),并调用driver_register函数,然后进入driver_register函数当中。通过执行driver_find函数查看以此文件命名的驱动是否已经存在。若没有找到就说明没有注册过该驱动,那么接着会执行bus_add_driver函数,看到函数名字我们就知道的该函数的功能就是在总线上添加设备。我们就进入 bus_add_driver函数,如果总线上没有实现drivers_autoprobe函数,那么就会执行driver_attach(drv);函数driver_attach函数中只有一个函数bus_for_each_dev执行,此函数的原型为bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);(__driver_attach是回调用的函数)其中个参数是总线,第三个参数是平台设备驱动,第四个参数是最重要的。进入bus_for_each_dev函数,列出代码
while ((dev = next_device(&i)) && !error)
error = fn(dev, data);
此代码是遍历所有总线上的设备找到与该平台总线设备驱动匹配的平台设备,。在代码中的fn,其实就是bus_for_each_dev函数的第四个参数__driver_attach。现在我们进入该函数中看一下。
if (!driver_match_device(drv, dev))
return 0;
if (dev->parent) /* Needed for USB */
down(&dev->parent->sem);
down(&dev->sem);
if (!dev->driver)
driver_probe_device(drv, dev);
up(&dev->sem);
if (dev->parent)
up(&dev->parent->sem);
上面就是__driver_attach 函数中的代码了
driver_match_device(drv, dev)此语句就是产看平台设备和平台设备驱动是否匹配。若不匹配就会直接返回,一直循环下去。当平台设备和平台设备驱动匹配时就不会返回,下面会执行一个driver_probe_device(drv, dev);(dev->driver,设备和驱动还没连接所以dev->driver==0,所以会执行),
好了,我们就进入driver_probe_device(drv, dev);吧!此函数中只有really_probe(dev, drv);是核心的语句,还等什么,进入看看吧!
此函数中有如如下的代码:
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
drv->probe的值就是我们驱动当中的探测函数的地址,在前面已经被赋值了。被赋值为驱动当中的探测函数。
到此探测函数就会执行了,看起来是很复杂的。要耐心看呀!!!
先把调用关系列出一下,方便以后阅读