Bootstrap

linux驱动——平台总线驱动 platform_driver

目录

 

平台总线的意义:

平台总线定义:

平台总线的设备和驱动  platform_device  和 platform_driver

平台总线驱动的编写步骤:

1、定义设备和资源:

2、定义驱动和实现probe方法

3、匹配方法解析

4、接口函数(open read write ....)亮灯和灭灯 的实现

参考别人的笔记:

实验代码:

plat_ded_pdrv.c

plat_led_pdev.c

test.c

总结:



平台总线的意义:

平台总线定义:

系统自动创建平台总线,不需要我们自己创建,  了解即可

平台总线的设备和驱动  platform_device  和 platform_driver

以前我们定义 总线设备和驱动 的时候是 struc device   和 strcut device_driver

而平台总线的设备和驱动则有自己的定义:只不过也是继承了 struc device   和 strcut device_driver

platform_device  和 platform_driver

 

 

平台总线驱动的编写步骤:

1、定义设备和资源:

2、定义驱动和实现probe方法

static int led_pdrv_probe(struct platform_device * pdev)
{
	printk("--------------%s----------\n",__FUNCTION__);

	samsung_led = kmalloc(sizeof(struct led_dev), GFP_KERNEL);
	if(samsung_led==NULL)
	{
		printk("samsung_led kmalloc error\n");

		return -ENOMEM;
	}

	samsung_led->dev_major = register_chrdev(0, "led_drv", pled_fops); //动态申请
	samsung_led->cls = class_create(THIS_MODULE, "led_class") ;
	samsung_led->dev = device_create(samsung_led->cls, NULL, 
						MKDEV(samsung_led->dev_major, 0), NULL, "led0");
	//获取资源
	//参数1、从哪个pdev 获取资源
	//参数2、资源类型

	/*参数3、表示获取同种资源第几个 ,一定要注意是同种资源的排序(即先看同种资源,再看数组下标),
	而不是光看数组下标
	*/

	samsung_led->res = platform_get_resource(pdev,IORESOURCE_MEM, 0);

	samsung_led->reg_base = ioremap(samsung_led->res->start, 
						samsung_led->res->end-samsung_led->res->start+1);

	//配置寄存器输出,writel对某个寄存器写数据;

	writel((readl(samsung_led->reg_base)&~(3<<10))|(1<<10), samsung_led->reg_base);
	
	
	return 0;
}

3、匹配方法解析

//匹配算法:打开driver\base\platform.c文件,有个匹配函数
	方法一:用id_table进行匹配
	static  int   platform_match(struct   device  *dev,   struct   device_driver   *drv)
	 {
		 //由前面介绍platform_device相当于是从dev继承派生而来的,platform_driver相当于是从driver继承派生而来的,这里由结构体中的成员变量可以反推出整个结构体变量的首地址
		 struct   platform_device   *pdev  =   to_platform_device(dev);
		 struct   platform_driver   *pdrv  =   to_platform_driver(drv);
 
		 /*   match  against   the   id   table   first  */
		 if   (pdrv->id_table)//如果id_table不为空,则用id进行匹配,然后直接返回不再调用name匹配算法
			 return   platform_match_id(pdrv->id_table,   pdev)  !=   NULL;
		 //匹配方法二
		 /*   fall-back  to   driver   name  match   */
		 return   (strcmp(pdev->name,   drv->name)   ==   0);
	 }
 
	 static  const   struct   platform_device_id   *platform_match_id(
							 struct   platform_device_id   *id,
							 struct   platform_device   *pdev)
	 {
		 while   (id->name[0])   {        //id_table数组没有到尾
			 if   (strcmp(pdev->name,   id->name)   ==   0)   {     //本质上还是还是通过名字对应来匹配,但是匹配的名字被列在一个表中,这样一个驱动就可以匹配多个不同name的驱动了,只要设备的pdev->name在id_table数组列表的name中就可以匹配。
				 pdev->id_entry   =   id;   //和设备匹配后用来记录下此设备属于驱动idtable中的哪一个
				 return   id;
			 }
			 id++;      //指向id_table数组的下一个数组成员
		 }
		 return   NULL;
	 }
 
	 方法二:用设备名和驱动名进行匹配,这样就只能一个驱动匹配一个设备
 
 
		 源码举例1:
		 /drivers/mtd/nand/S3C2410.c
		 static   struct  platform_device_id   s3c24xx_driver_ids[]   =   {
			 {
				 .name                =   "s3c2410-nand",
					 .driver_data     =   TYPE_S3C2410,
			 },   {
				 .name                =   "s3c2440-nand",
					 .driver_data     =   TYPE_S3C2440,
			 },   {
				 .name                =   "s3c2412-nand",
					 .driver_data     =   TYPE_S3C2412,
				 },   {
					 .name                =   "s3c6400-nand",
						 .driver_data     =   TYPE_S3C2412,   /*   compatible   with  2412   */
				 },
				 {   }
	 };
 
	 static  struct   platform_driver   s3c24xx_nand_driver   =   {
		 .probe               =   s3c24xx_nand_probe,
		 .remove            =  s3c24xx_nand_remove,
		 .suspend  =   s3c24xx_nand_suspend,
		 .resume             =  s3c24xx_nand_resume,
		 .id_table  =   s3c24xx_driver_ids,//这个驱动支持的设备id表
		 .driver                =   {
			 .name       =  "s3c24xx-nand",
			 .owner     =   THIS_MODULE,
		 },
	 };

4、接口函数(open read write ....)亮灯和灭灯 的实现

 

ssize_t pled_write(struct file *filp, const char __user *buf, size_t count, loff_t *fops)
{
	int val;
	int ret;

	printk("--------------%s----------\n",__FUNCTION__);
	
	ret = copy_from_user(&val, buf, count);
	if(ret > 0)
	{
		printk("copy_from_user error\n");

		return -EFAULT;
	}
	
	if(val)   //灭灯
	{
		writel(readl(samsung_led->reg_base+4)|(1<<5), samsung_led->reg_base+4);  //一个寄存器4个字节 data寄存器为con寄存器+4
	}
	else  //亮灯
	{
		
		writel(readl(samsung_led->reg_base+4)&~(1<<5), samsung_led->reg_base+4);  //一个寄存器4个字节 data寄存器为con寄存器+4
	}


	return 0;
}

int pled_open(struct inode *inode, struct file *filp)
{
	printk("--------------%s----------\n",__FUNCTION__);

	return 0;

}

int pled_close (struct inode *ionde, struct file *filp)
{
	printk("--------------%s----------\n",__FUNCTION__);

	return 0;
}


static struct file_operations pled_fops = {

	.open = pled_open ,
	.release = pled_close ,
	.write = pled_write ,

};

 

参考别人的笔记:

struct   platform_device   {
	const   char   *   name;     //设备名,可以用来和驱动进行匹配
	int              id;            //作用?
	struct   device       dev;         //  platform_device相当于是从dev继承派生而来的
	u32            num_resources;         //资源数目
	struct   resource   *   resource;               //资源数组首地址
 
	struct   platform_device_id  *id_entry;            //和驱动匹配后用来记录下此设备属于驱动idtable中的哪一个
 
	/*   arch  specific   additions   */
	struct   pdev_archdata          archdata;
};
//平台驱动
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   (*resume)(struct   platform_device   *);
	struct   device_driver   driver;//匹配的设备的所有信息,platform_driver相当于是从driver继承派生而来的
	struct   platform_device_id   *id_table;      //所有可以匹配的设备的idtable
};
/**********************/转载请注明原文地址 http://blog.csdn.net/oyhb_1992/article/details/77477112
struct   device_driver   {
	const   char            *name;      //匹配的设备名
	struct   bus_type            *bus;
 
	struct   module               *owner;
	const   char            *mod_name;   /*   used   for   built-in   modules  */
 
	bool   suppress_bind_attrs;  /*   disables   bind/unbind  via   sysfs   */
 
	int   (*probe)  (struct   device   *dev);
	int   (*remove)  (struct   device   *dev);
	void   (*shutdown)  (struct   device   *dev);
	int   (*suspend)  (struct   device   *dev,  pm_message_t   state);
	int   (*resume)  (struct   device   *dev);
	const   struct  attribute_group   **groups;
 
	const   struct  dev_pm_ops   *pm;
 
	struct   driver_private   *p;
 
 
//匹配算法:打开driver\base\platform.c文件,有个匹配函数
	方法一:用id_table进行匹配
	static  int   platform_match(struct   device  *dev,   struct   device_driver   *drv)
	 {
		 //由前面介绍platform_device相当于是从dev继承派生而来的,platform_driver相当于是从driver继承派生而来的,这里由结构体中的成员变量可以反推出整个结构体变量的首地址
		 struct   platform_device   *pdev  =   to_platform_device(dev);
		 struct   platform_driver   *pdrv  =   to_platform_driver(drv);
 
		 /*   match  against   the   id   table   first  */
		 if   (pdrv->id_table)//如果id_table不为空,则用id进行匹配,然后直接返回不再调用name匹配算法
			 return   platform_match_id(pdrv->id_table,   pdev)  !=   NULL;
		 //匹配方法二
		 /*   fall-back  to   driver   name  match   */
		 return   (strcmp(pdev->name,   drv->name)   ==   0);
	 }
 
	 static  const   struct   platform_device_id   *platform_match_id(
							 struct   platform_device_id   *id,
							 struct   platform_device   *pdev)
	 {
		 while   (id->name[0])   {        //id_table数组没有到尾
			 if   (strcmp(pdev->name,   id->name)   ==   0)   {     //本质上还是还是通过名字对应来匹配,但是匹配的名字被列在一个表中,这样一个驱动就可以匹配多个不同name的驱动了,只要设备的pdev->name在id_table数组列表的name中就可以匹配。
				 pdev->id_entry   =   id;   //和设备匹配后用来记录下此设备属于驱动idtable中的哪一个
				 return   id;
			 }
			 id++;      //指向id_table数组的下一个数组成员
		 }
		 return   NULL;
	 }
 
	 方法二:用设备名和驱动名进行匹配,这样就只能一个驱动匹配一个设备
 
 
		 源码举例1:
		 /drivers/mtd/nand/S3C2410.c
		 static   struct  platform_device_id   s3c24xx_driver_ids[]   =   {
			 {
				 .name                =   "s3c2410-nand",
					 .driver_data     =   TYPE_S3C2410,
			 },   {
				 .name                =   "s3c2440-nand",
					 .driver_data     =   TYPE_S3C2440,
			 },   {
				 .name                =   "s3c2412-nand",
					 .driver_data     =   TYPE_S3C2412,
				 },   {
					 .name                =   "s3c6400-nand",
						 .driver_data     =   TYPE_S3C2412,   /*   compatible   with  2412   */
				 },
				 {   }
	 };
 
	 static  struct   platform_driver   s3c24xx_nand_driver   =   {
		 .probe               =   s3c24xx_nand_probe,
		 .remove            =  s3c24xx_nand_remove,
		 .suspend  =   s3c24xx_nand_suspend,
		 .resume             =  s3c24xx_nand_resume,
		 .id_table  =   s3c24xx_driver_ids,//这个驱动支持的设备id表
		 .driver                =   {
			 .name       =  "s3c24xx-nand",
			 .owner     =   THIS_MODULE,
		 },
	 };



static struct platform_device_id s3c_rtc_driver_ids[] = {
	{
		.name		= "s3c2410-rtc",
			.driver_data	= TYPE_S3C2410,
	}, {
		.name		= "s3c2416-rtc",
			.driver_data	= TYPE_S3C2416,
	}, {
		.name		= "s3c2443-rtc",
			.driver_data	= TYPE_S3C2443,
		}, {
			.name		= "s3c64xx-rtc",
				.driver_data	= TYPE_S3C64XX,
		},
		{ }
};
 
static struct platform_device_id s3c_rtc_driver_ids[] = {
	{
		.name  = "s3c2410-rtc",
			.driver_data = TYPE_S3C2410,
	}, {
		.name  = "s3c2416-rtc",
			.driver_data = TYPE_S3C2416,
	}, {
		.name  = "s3c2443-rtc",
			.driver_data = TYPE_S3C2443,
		}, {
			.name  = "s3c64xx-rtc",
				.driver_data = TYPE_S3C64XX,
		},
		{ }
}; 
 
上面就是platform_device_id,可以看到这个驱动支持的设备不只一种,包括2410、2416等等。而Platform_driver定义如下:
 
 
 
static struct platform_driver s3c_rtc_driver = {
	.probe  = s3c_rtc_probe,
	.remove  = __devexit_p(s3c_rtc_remove),
	.suspend = s3c_rtc_suspend,
	.resume  = s3c_rtc_resume,
	.id_table = s3c_rtc_driver_ids,
	.driver  = {
 		.name = "s3c-rtc",
      		.owner = THIS_MODULE,
		.of_match_table = s3c_rtc_dt_match,
	},
};
 
2410的platfrom_device定义如下:
 
 
 
struct platform_device s3c_device_rtc = {
	.name  = "s3c2410-rtc",
	.id  = -1,
	.num_resources = ARRAY_SIZE(s3c_rtc_resource),
	.resource = s3c_rtc_resource,
};
 
 
通过id_table这种方式有什么好处呢,如果只是简单的比较驱动里的.driver.name字段和设备里的.name是否相同,那么一个驱动只能支持特定的一个设备,而如果通过id_table的方式呢,一个驱动.id_table可以和多个驱动的设备里的.name匹配是否相同,支持很多个设备,而它们只是name字段不同而已。

实验代码:

plat_ded_pdrv.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/fs.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>


//probe方法实现步骤:
/*
1、注册设备号,
2、创建设备节点
3、初始化硬件
4、实现各种io接口

*/

struct led_dev{
	int dev_major;   //主设备号
	struct class *cls;   //设备类
	struct class_device *dev;   //设备节点
	struct resource *res;  //获取到的资源
	void *reg_base;   //物理地址映射后的虚拟地址
};

struct led_dev *samsung_led;

ssize_t pled_write(struct file *filp, const char __user *buf, size_t count, loff_t *fops)
{
	int val;
	int ret;

	printk("--------------%s----------\n",__FUNCTION__);
	
	ret = copy_from_user(&val, buf, count);
	if(ret > 0)
	{
		printk("copy_from_user error\n");

		return -EFAULT;
	}
	
	if(val)   //灭灯
	{
		writel(readl(samsung_led->reg_base+4)|(1<<5), samsung_led->reg_base+4);  //一个寄存器4个字节 data寄存器为con寄存器+4
	}
	else  //亮灯
	{
		
		writel(readl(samsung_led->reg_base+4)&~(1<<5), samsung_led->reg_base+4);  //一个寄存器4个字节 data寄存器为con寄存器+4
	}


	return 0;
}

int pled_open(struct inode *inode, struct file *filp)
{
	printk("--------------%s----------\n",__FUNCTION__);

	return 0;

}

int pled_close (struct inode *ionde, struct file *filp)
{
	printk("--------------%s----------\n",__FUNCTION__);

	return 0;
}


static struct file_operations pled_fops = {

	.open = pled_open ,
	.release = pled_close ,
	.write = pled_write ,

};

static int led_pdrv_probe(struct platform_device * pdev)
{
	printk("--------------%s----------\n",__FUNCTION__);

	samsung_led = kmalloc(sizeof(struct led_dev), GFP_KERNEL);
	if(samsung_led==NULL)
	{
		printk("samsung_led kmalloc error\n");

		return -ENOMEM;
	}

	samsung_led->dev_major = register_chrdev(0, "led_drv", &pled_fops); //动态申请
	
	samsung_led->cls = class_create(THIS_MODULE, "led_class") ;

	samsung_led->dev = class_device_create(samsung_led->cls, NULL, 
						MKDEV(samsung_led->dev_major, 0), NULL, "led0");

	//获取资源
	//参数1、从哪个pdev 获取资源
	//参数2、资源类型

	/*参数3、表示获取同种资源第几个 ,一定要注意是同种资源的排序(即先看同种资源,再看数组下标),
	而不是光看数组下标
	*/

	samsung_led->res = platform_get_resource(pdev,IORESOURCE_MEM, 0);

	samsung_led->reg_base = ioremap(samsung_led->res->start, 
						samsung_led->res->end-samsung_led->res->start+1);

	if(samsung_led->reg_base == NULL)
	{
		printk("ioremap error \n");

		return -ENOMEM;

	}

	
	//配置寄存器输出,writel对某个寄存器写数据;

	writel((readl(samsung_led->reg_base)&~(3<<10))|(1<<10), samsung_led->reg_base);
	
	
	return 0;
}

static int led_pdrv_remove(struct platform_device * pdev)
{
	printk("--------------%s----------\n",__FUNCTION__);

	
	iounmap(samsung_led->reg_base);    //释放io资源
	class_device_destroy(samsung_led->cls, MKDEV(samsung_led->dev_major, 0));
    class_destroy(samsung_led->cls);
	unregister_chrdev(samsung_led->dev_major, "led_drv");
	kfree(samsung_led);
	
	return 0;
}

//const struct platform_device_id *id_table  老版本的没有;

//构建一个platform_driver对象

struct platform_driver led_pdrv = {
	.probe = led_pdrv_probe ,    //匹配成功调用
	.remove = led_pdrv_remove ,
	.driver = {
			.name = "myled" ,   //可以用来作匹配,也可以用id匹配  ,
								//在driver目录下会生成myled驱动
				
	}
	
};

static int __init plat_led_drv_init(void)
{
	int ret;

	printk("--------------%s----------\n",__FUNCTION__);

	ret = platform_driver_register(&led_pdrv);
	if(ret < 0)
	{
		printk("driver register error!\n");
		return ret;
	
	}
	
	return 0;

}

static void __exit plat_led_drv_exit(void)
{
	printk("--------------%s----------\n",__FUNCTION__);

	platform_driver_unregister(&led_pdrv);


}


module_init(plat_led_drv_init);
module_exit(plat_led_drv_exit);
MODULE_LICENSE("GPL");



plat_led_pdev.c

#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>



//led GPF 4 5 6


struct resource led_res[]=
{
	[0] = {
		.start = 0x56000050 ,               //这个是起始地址 GPFCON
		.end =  0x56000050+8-1 , 		    //GPFUP终止地址      0x56000058-1 
		.flags = IORESOURCE_MEM ,		  //说明是什么类型的资源 这里是IO内存资源
	},

	[1] = {

		.start = 5 ,             //start和end是一样的,中断号为5
		.end =   5 , 		    
		.flags =  IORESOURCE_IRQ ,              //中断资源
	},
};


struct platform_device led_pdev = {

	.name = "myled" ,  //名称和driver里面的保持一致
	.id = -1 ,
	.num_resources = ARRAY_SIZE(led_res),  //资源数量
	.resource = led_res ,
};



static int __init plat_led_dev_init(void)
{
	int ret;

	printk("--------------%s----------\n",__FUNCTION__);

	ret = platform_device_register(&led_pdev);
	if(ret < 0)
	{
		printk("platform_device_register error!\n");
		return ret;
	
	}
	
	return 0;

}

static void __exit plat_led_dev_exit(void)
{
	printk("--------------%s----------\n",__FUNCTION__);

	platform_device_unregister(&led_pdev);


}

module_init(plat_led_dev_init);
module_exit(plat_led_dev_exit);
MODULE_LICENSE("GPL");




test.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>


int main(int argc, char *argv[])
{

	int fd;
	int on;

	fd = open("/dev/led0", O_RDWR);

	if(fd < 0)
	{
		printf("open error\n");
		exit(1);
	}
	

	//控制灯的亮灭
	
	while(1)
	{
		on = 0 ;  //亮灯
		write(fd, &on, 4);
		sleep(1);

		on = 1;  //灭灯
		write(fd,&on,4);
		sleep(1);
	
	}



	close(fd);

	return 0 ;

}



总结:

容易出错的地方是:创建设备节点用错函数,device_create  应该用 class_device_create

调式方法:多加几个printk函数打印

 

 

 

 

 

 

 

 

 

;