目录
平台总线的设备和驱动 platform_device 和 platform_driver
4、接口函数(open read write ....)亮灯和灭灯 的实现
平台总线的意义:
平台总线定义:
系统自动创建平台总线,不需要我们自己创建, 了解即可
平台总线的设备和驱动 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函数打印