在驱动里面编写设备信息,没有实现设备和驱动的分离,一旦硬件变化,就必须修改驱动。
我们现在把LED驱动分解成平台设备和平台驱动
设备里面存放硬件信息,如果硬件有变化,只需要修改设备
驱动从设备中去获取硬件信息,硬件变化,驱动不用改变。
代码:向平台总线注册4个设备
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#define FSLED_MAJOR 256
static void fsdev_release(struct device *dev)
{
}
//硬件信息用resource来表示,DEFINE_RES_MEM将后面的信息包装成一个struct resource
static struct resource led2_resource[]={
[0]=DEFINE_RES_MEM(0x11000c40,4), //外设寄存器的地址,4个字节
// [1]=DEFINE_RES_MEM(0x1100c44,4),
}
static struct resource led2_resource[]={
[0]=DEFINE_RES_MEM(0x11000c20,4),
}
static struct resource led2_resource[]={
[0]=DEFINE_RES_MEM(0x114001e0,4),
}
static struct resource led2_resource[]={
[0]=DEFINE_RES_MEM(0x114001e0,4),
}
//寄存器的第几位
unsigned int led2pin = 7;
unsigned int led3pin = 0;
unsigned int led4pin = 4;
unsigned int led5pin = 5;
//开发板有4个LED,我们把每个灯都做成一个设备
//内核用struct platform_device的对象来表示一个平台设备,有4个LED灯,
struct platform_device fsled2={
.name = "fsled",
.id=2,
.num_resources=ARRAY_SIZE(led2_resources), //资源个数
.release=led2_release,
.dev={
//移除设备时调用
.release=fsdev_release,
.platform_data=&led2pin,
}
};
struct platform_device fsled3={
.name = "fsled",
.id=3,
.num_resources=ARRAY_SIZE(led3_resources), //资源个数
.release=led3_release,
.dev={
//移除设备时调用
.release=fsdev_release,
.platform_data=&led3pin,
}
};
struct platform_device fsled4={
.name = "fsled",
.id=4,
.num_resources=ARRAY_SIZE(led4_resources), //资源个数
.release=led4_release,
.dev={
//移除设备时调用
.release=fsdev_release,
.platform_data=&led4pin,
}
};
struct platform_device fsled5={
.name = "fsled",
.id=5,
.num_resources=ARRAY_SIZE(led5_resources), //资源个数
.release=led5_release,
.dev={
//移除设备时调用
.release=fsdev_release,
.platform_data=&led5pin,
}
};
//注册设备
static int __init fsdev_init(void)
{
//向平台总线注册4个LED灯
platform_device_register(&fsled2);
platform_device_register(&fsled3);
platform_device_register(&fsled4);
platform_device_register(&fsled5);
/*
//使用先把4个LED包装称数组
static struct platform_device *fsled_device[]={
&fsled2,
&fsled3,
&fsled4,
&fsled5,
};
//使用该函数一次性添加4个设备
return platform_add_devices(fsled_devices,ARRAY_SIZE(fsled_devices));
*/
}
//注销设备
static void __exit fsdev_exit(void)
{
platform_device_unregister(&fsled2);
platform_device_unregister(&fsled3);
platform_device_unregister(&fsled4);
platform_device_unregister(&fsled5);
}
module_init(fsdev_init);
module_init(fsdev_exit);
MODULE_LICENSE("GPL");
ARRAY_SIZE函数
设备树:内核启动后,会扫描设备树,生成各种设备
代码:平台驱动
//定义一个字符设备,内嵌一个cdev
struct fsled_dev
{
//根据设备的具体情况,我们增加结构体成员
unsigned int __iomem *con; //存放映射后的地址
unsigned int __iomem *dat;
unsigned int pin;
struct cdev cdev;
}
static int fsled_open(struct inode*inode,struct file*filp)
{
struct fsled_dev *fsled=container_of(inode->cdev,struct fsled_dev,cdev);
filp->private_data=fsled;
return 0;
}
static int fsled_release(struct inode*inode,struct file*filp)
{
}
struct file_operation fsled_ops=
{
.owner = THIS_MODULE,
.open = fsled_open,
.release=fsled_release,
.unlocked_ioctl=fs_ioctl,
};
//当设备和驱动匹配成功时,自动调用该函数,参数是一个指向匹配设备的指针
static int fsled_probe(struct platform_device *pdev)
{
//1.申请注册设备号
int ret;
dev_t dev; //本例中有4设备,会被调用4次
struct fsled_dev *fsled; //定义一个字符设备
//硬件信息用struct resource来表示
struct resource *res;
//获取设备中的管脚信息,这是非标准的硬件信息
unsigned int pin = *(unsigned int)(pdev->dev.platform_data)
dev=MKDEV(FSLED_MAJOR,pdev->id); //设备结构体中有id
ret=register_chrdev_region(dev,1,"fsled");
if(ret)
{
goto reg_err;
fsled = kzalloc(sizeof(struct fsled_dev),GFP_KERNEL);
if(!fsled)
{
ret = -ENOMEM;
goto mem_err;
}
cdev_init(&fsled->cdev,&fsled_ops);
fsled->cdev.owner=THIS_MODULE;
ret=cdev_add(&fsled->cdev,dev,1);
//下面开始从pdev设备中获取硬件信息,res为获取的硬件地址
res = platform_get_resource(pdev,IORESOURCE_MEM,0);
if(!res)
{
ret = -ENOENT;
goto res_err;
}
//物理--->虚拟 0x11000c40 4
fsled->con=ioremap(res->start,resource_size(res));
if(!fsled->con)
{
ret = -EBUSY;
goto map_err;
}
//另一个寄存器的地址
fsled->dat=fsled->con+1;
fsled->pin = pin;
/*************硬件信息已经获取完毕******************/
/******fsled->con,fsled->dat,fsled-pin*************/
//接下来实施控制LED灯让GPIO输出功能,熄灭LED
write((readl(fsled->con)&~(0xf<<4*fsled->pin))|(0x1<<4*fsled->pin),fsled->con);
write(readl(fsled->dat)&~(0x1<<fsled->pin),fsled->dat);
//为了以后方便的得到fsled这个指针,我们把它存放在pdev中一个成员里,方便以后获取。
platform_set_drvdata(pdev,fsled);
return 0;
res_err:
cdev_del(&fsled->cdev);
add_err:
kfree(fsled);
mem_err:
unregister_chrdev_region(dev,1);
reg_err:
return ret;
}
}
//参数是指向被移除的设备的指针
static int fsled_remove(struct platform_device *pdev)
{
//本函数也是被调用4次
//从pdev中恢复我们之前存入的fsled
struct fsled_dev* fsled = platform_get_drvdata(pdev);
ioumap(fsled->con); //解除映射
cdev_del(&fsled->cdev); //删除设备
kfree(fsled);//释放资源
unregister_chrdev_region(cdev,1);
return 0;
}
//内核中用platform_driver的对象来表示平台驱动
struct platform_driver fsled_drv={
.driver={
//名字要注意,要和设备匹配
.name="fsled",
.owner=THIS_MODULE,
},
//当匹配成功,会自动调用probe函数
.probe=fsled_probe,
.remove=fsled_remove,
};
//平台设备的加载与卸载函数通常只注册和注销驱动
//设备号,设备等放到probe函数中去做。
//用该函数替换之前的加载,卸载函数
module_platform_driver(fsled_drv);
/*
static int __init fsled_init(void)
{
//注册平台驱动
platform_driver_register(&fsled_drv);
return 0;
}
static void__exit fsled_exit(void)
{
//注销平台驱动
platform_driver_unregister(&fsled);
}
module_init(fsled_init);
module_exit(fsled_exit);
*/
MODULE_LICENSE("GPL");
由于加载函数、卸载函数基本上做固定的事情、
总线要根据名字匹配设备和驱动
平台驱动的加载和卸载函数通常只注册和注销平台驱动
以前的字符设备驱动程序的加载函数要做申请设备号,添加cdev都放在了probe中,相应的注销设备号,cdev_del放到remove中去。
kmalloc和kzalloc都是内核中用来动态分配内存的函数,kzalloc会将分配的内存清0,这两个函数分配内存时,一般都使用GFP_KERNEL的方式;释放空间kfree();
硬件信息需要从设备中获取。
标准硬件信息用硬件资源来表示,platform_get_resource来获得信息
struct resource结构体
struct resource
{
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent,*sibing,*chind;
}