linux平台设备介绍
linux2.6
以上的设备驱动模型中,有三大实体:总线,设备和驱动。总线负责将设备和驱动绑定,在系统没注册一个设备的时候,会寻找与之匹配的驱动:相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配则由总线完成。
一个现实的linux
设备和驱动通常都需要挂接在一个总线上例如PCI
、USB
、I2C
、SPI
接口的设备都是由对应的总线来管理,通过总线来操作设备。但对于Soc
系统中集成的独立外设控制器,比如PCI
、USB
、I2C
、SPI
等总线。基于这一背景,linux
发明了一种虚拟的总线,称为platform
总线,相应的设备称为platform_device
,而驱动称之为platform_driver
平台总线驱动模型的特点
- 平台模型采用了分层结构,把一个设备驱动分为了两个部分:平台设备(
platform_device
)和平台驱动(platform_driver
) - 平台设备将设备本身的资源(占用哪些中断号,内存资源,
IO
口等)注册进内核,可以由内核统一管理,驱动更加安全、可靠。
驱动层probe函数使用资源前必须先申请:
如果前面驱动对资源已经占用,则本驱动占用失败 - 统一了设备驱动模型,使智能电源管理更容易实现
- 从代码维护角度看,平台模型的可移植性,通用性更好
- 平台设备并不是一种新的设备,
linux
目前只有三种设备类型:字符设备、块设备、网络设备 - 平台设备是按总线进行划分的,所以平台设备不是一种新设备,也不是特定字符设备或块设备,它可以是三种设备之一
platform模型分层模型
- 分为两个部分:设备信息、驱动程序
- 如果设备正常工作,就必须把设备信息及驱动程序都注册到内核中
- 设备信息和驱动程序分别携程独立的
ko
文件(也可以写成一个ko
)
认识分层
- 先安装驱动,再安装设备
当安装设备模块的时候,内核会为当前安装的设备查找匹配的驱动程序,如果找到了,则绑定在一起,然后调用驱动中的platform_driver
结构中的probe
成员指针所指向的函数;当卸载已经匹配上的设备模块的时候,内核会调用驱动中的platform_driver
结构中remove
成员指针所指向的函数 - 先安装设备,再安装驱动
当安装驱动模块的时候,内核会为当前安装的驱动查找匹配的设备,如果找到了,则绑定在一起,然后调用驱动中的platform_driver
结构中的probe
成员指针所指向的函数;当卸载已经匹配上的设备模块的时候,内核会调用驱动中的platform_driver
结构中remove
成员指针所指向的函数 - 如果只安装设备
卸载时和普通的模块卸载一样,并且还会执行设备模块中的plaform_device.dev_release
成员指针指向的函数 - 如果只安装驱动
则卸载模块时执行情况和普通模块执行状况一样,不会执行probe
和remove
指针指向的函数
总的结论:不管先安装设备模块还是先安装驱动模块,内核都正确的匹配进行绑定设备和驱动的绑定
内核为设备和驱动匹配的依据
根据驱动模块中的`platform_driver.driver.name`和设备模块中的`platform_device.name` 字符串是否相同。 这只是平台设备驱动模型第一种匹配方式,当前内核还有其他两种匹配方法: 第一种:根据驱动代码`platform_driver.id_table.name`是否和设备代码中的`platform_device.name`相同 第二种:使用设备树方式进行匹配,驱动需要使用到`platform_driver.driver.of_device_id`实现,这个是高版本内核最新出现的一种匹配方式使用id_table方式进行匹配可以做到多个设备驱动一个驱动的效果
平台设备层编程
核心数据结构
- struct platform_device
路径:platform_device.h linux-3.5\include\linux
struct platform_device {
const char * name; //需要关注的
int id; //需要关注的
struct device dev; //需要关注的
u32 num_resources; //需要关注的
struct resource * resource; //需要关注的
const struct platform_device_id *id_entry;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
重要成员说明:
name:设备名,用来和驱动层代码做匹配,必须和驱动层中的platform_driver.driver.name
或者platform_driver.id_table.name
相同,这样安装设备和驱动模块才可以匹配上。
id:当设备和驱动是一对一关系时,一般设置成-1
。
dev:内嵌的设备模型结构2.6
版本把设备模型进行了统一,就使用该结构描述一切设备,是所有设备的基础
num_resources:标识设备占用了多少个资源,实际上用来描述,resource
指向的资源数组的元素数量。
resource:平台设备资源指针(资源表示物理设备在系统中占用了哪些内存,IO
口,中断号等)
- 讲解struct resource资源指针结构体
路径:ioport.h linux-3.5\include\linux
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
参数:
start:资源起始值
end:资源结束值
name:资源名字,随便起
flags:资源类型
parent
, sibling
, child
:驱动开发者不需要关注,内核使用到
start
end
值含义与flags
值有关,flags
用来描述资源类型,其可取值类型内*核定义如下:
#define IORESOURCE_IO 0x00000100 //资源是是IO口类型
#define IORESOURCE_MEM 0x00000200 //资源是物理内存
#define IORESOURCE_IRQ 0x00000400 //资源是中断编号
#define IORESOURCE_DMA 0x00000800 //资源师DMA
#define IORESOURCE_BUS 0x00001000 //资源是总线编号
常用的是IORESOURCE_MEM
和IORESOURCE_IRQ
补充:IORESOURCE_IO
不是指aem
芯片的GPIO
引脚而是指具有独立内存空间编址的GPIO
内存地址
类似上面图片这种情况左边的P0-P3
口占用的内存空间才属于IO
空间,IO
资源一般情况在x86
架构芯片上才会有
而ARM
芯片GPIO
口占用的空间是属于右边这部分空间,也就是4G
主存中的一部分,所以ARM
芯片的GPIO
占用的资源是内存资源,不是IO
资源
- 讲解struct device结构体
linuxn
内核使用这个结构体来描述一个设备信息,他是linux
设备模型的基础
路径:device.h \linux-3.5\include\linux
struct device {
void *platform_data; /* Platform specific data, device
core doesn't touch it */
//平台数据 任意类型
dev_t devt; /* dev_t, creates the sysfs "dev" *///设备号
···········//中间内容不关心
void (*release)(struct device *dev);//指向卸载模块时执行的函数指针
};
平台设备层API函数
platform_device_register
原型:
int platform_device_register(struct platform_device *pdev);
功能:向内核注册一个平台设备
参数:pdev
要注册的平台设备结构指针
返回值:0
成功 负数失败
platform_device_unregister
原型:
void platform_device_unregister(struct platform_device *pdev);
功能:从内核中注销一个平台设备
参数:pdev
要注销的平台设备结构指针
返回值:无
platform_add_devices
原型:
int platform_add_devices(struct platform_device **devs, int num)
功能:向内核一次注册多个平台设备
参数:devs
要注册的平台设备数组,num
需要注册平台设备的个数
返回值:0
成功 负数 失败
平台设备编写步骤
- 先是编写一个模块的模板代码
- 在模块文件中定义一个
struct platform_device
结构变量 - 初始化上一步定义的
struct platform_device
结构变量必要的元素,一般在定义时就初始化了
初始化的核心分析清楚设备占用的资源:分析硬件原理图
定义设备占用的资源数组
初始化设备结构的resource
,num_resource
根据需要决定是否需要传递平台数据:dev.platform_data
成员 - 在模块加载时调用
platform_device_register
函数注册已初始化好的机构变量 - 在模块卸载时使用
platform_device_unregister
函数注销注册的平台设备
编写代码
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/types.h>
#include<linux/interrupt.h>
#include<linux/platform_device.h>
//定义leds设备占用的资源
struct resource myleds_res[]={
[0]={
.start=0x110002e0,
.end=0x110002f7,
.flags=IORESOURCE_MEM,
.name="GPM4",
}
};
//定义一个平台设备变量
struct platform_device myleds_dev={
/*
//原理图上4个led GPM4^0-4,所以使用GPM4相关寄存器
//起始寄存器地址0x1100 02E0 结束寄存器地址0x1100 02F4
//但是最后一个寄存器占用4字节地址
//总的占用内存空间大小0x1100 02F4 + 4 - 1 -0x1100 02E0 +1=0x18
*/
.name ="myleds",
.id =-1,
.num_resources = ARRAY_SIZE(myleds_res),
.resource = myleds_res,
};
static int __init ldedrv_init(void)
{
platform_device_register(&myleds_dev);
printk("%s is call\r\n",__FUNCTION__);
return 0;
}
static void __exit leddrv_exit(void)
{
platform_device_unregister(&myleds_dev);
printk("%s is call\r\n",__FUNCTION__);
}
module_init(ldedrv_init);
module_exit(leddrv_exit);
MODULE_LICENSE("GPL");
平台驱动层编程
驱动层核心数据结构
路径:platform_device.h linux-3.5\include\linux
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;
const struct platform_device_id *id_table;
};
重要成员:
probe:指向探测函数的函数指针,当设备和驱动匹配时,其指向的函数会被自动执行
remove:指向设备移除函数的函数指针,当卸载已经匹配好的设备和驱动中的任意一个,都会执行该函数,简单理解:功能与probe
相反
比如:在probe
函数中动态申请了一块内存,则在remove
函数中必须释放所申请的内存,在probe
函数中注册一个杂项设备,则必须在remove
中注销一个杂项设备
以上两个必须实现
device_driver:设备结构体,这个是内核用来描述一个设备对应的驱动程序的抽象结构。
id_table:可以用来和设备层进行匹配,如果实现了这个成员,则不会使用.name
进行匹配
其他:suspend
resume
是关于设备电源管理方面的接口,这两个接口是相反的。
补充
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data
__attribute__((aligned(sizeof(kernel_ulong_t))));
};
当需要实现一个驱动可以实现多个设备时候可以使用这种方式实现
具体操作是定义一个数组,然后实现其中name
成员。所有在这个数组中的name
成员相同的时候,则匹配上,调用驱动程序中的probe
函数
补充device_driver结构
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 */
const struct of_device_id *of_match_table; //高版本内核平台设备可以使用设备树方式实现设备层,该成员就是当使用设备树方式实现时可以使用这个成员进行匹配
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;
};
`
一般驱动只需要实现name
成员即可
平台驱动层API函数
- platform_driver_register
路径:platform_device.h linux-3.5\include\linux
原型:int platform_driver_register(struct platform_driver *drv)
功能:向内核注册一个平台驱动。如果此时有匹配平台设备,会触发驱动结构中的probe
函数
参数:drv
要注册的平台驱动结构
返回值:0
注册成功,负数 注册失败 - platform_driver_unregister
路径:platform_device.h linux-3.5\include\linux
原型:void platform_driver_unregister(struct platform_driver *drv)
功能:从内核注销一个平台驱动。如果此时已有匹配平台设备,会触发驱动结构中的remove
函数
参数:drv
要注册的平台驱动结构
返回值:无
通常一个设备包含有很多资源例如:IO口、 中断编号、内存空间占用等等,下面几个是怎样方便的获取这些特定类型的资源API函数,可以方便的寻找指定类型的资源
- platform_get_resource
路径:platform_device.h linux-3.5\include\linux
原型:
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
功能:获取设备层平台设备结构中资源结构体指定的数组成员的结构,该函数在驱动层中使用
参数:
dev:平台设备结构体
type:资源结构体数组成员中资源类型
num:资源结构体数组中第几个type
类型的成员,同资源类型的第几个成员,下标与数组下标不相同
返回值:获取到的资源结构体数组中的指定成员结构
- platform_get_resource_byname
路径:platform_device.h linux-3.5\include\linux
原型:
struct resource *platform_get_resource_byname(struct platform_device *dev,
unsigned int type,
const char *name)
功能:通过名字获取设备结构中资源结构体中指定类型资源结构
参数:
dev:平台设备结构体
type:资源结构体数组成员中资源类型
name:资源结构体数组中名称为name的type类型的成员
返回值:获取到的资源结构体数组中的指定成员结构
- platform_get_irq
路径:platform_device.h linux-3.5\include\linux
原型:int platform_get_irq(struct platform_device *dev, unsigned int num)
功能:通过设备指针,获得指定编号中断的起始编号
参数:
dev:平台设备结构指针
num:中断资源编号,与数组小标不相同,
返回值:>0
中断资源中的其实编号;-ENXIO
:失败
函数具体还是调用的platform_get_resource函数
int platform_get_irq(struct platform_device *dev, unsigned int num)
{
struct resource *r = platform_get_resource(dev, IORESOURCE_IRQ, num);
return r ? r->start : -ENXIO;
}
- platform_get_irq_byname
路径:platform_device.h linux-3.5\include\linux
原型:int platform_get_irq_byname(struct platform_device *dev, const char *name)
功能:通过设备指针,获得指定名字中断的起始编号
参数:
dev:平台设备结构指针
name:中断资源名称,
返回值:>0
中断资源中的其实编号;-ENXIO
:失败
函数具体还是调用的platform_get_resource
函数
int platform_get_irq_byname(struct platform_device *dev, const char *name)
{
struct resource *r = platform_get_resource_byname(dev, IORESOURCE_IRQ,
name);
return r ? r->start : -ENXIO;
}
第三类API
- request_region
路径:ioport.h linux-3.5\include\linux
原型:
#define request_region(start,n,name) __request_region(&ioport_resource, (start), (n), (name), 0)
功能:向内核申请一段IO
端口空间(IORESOURCE_IO
类型)(x86
架构)
参数:
start:起始地址
n:连续大小
name:使用者名字,用于内核登记
返回值:非NULL
申请成功资源结构内存地址struct resource*
NULL:所申请的IO
资源已被占用申请失败
- request_mem_region
路径:ioport.h linux-3.5\include\linux
原型:
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0)
功能:向内核申请一段IO
内存(IORESOURCE_MEM
类型)(ARM
架构)
参数:
start:起始地址
n:连续大小
name:使用者名字,用于内核登记
返回值:非NULL
申请成功资源结构内存地址struct resource*
NULL:所申请的IO
资源已被占用申请失败
- release_region
路径:ioport.h linux-3.5\include\linux
原型:
#define release_region(start,n) __release_region(&ioport_resource, (start), (n))
功能:释放一段request_region申请的IO端口空间(IORESOURCE_IO类型)(x86架构)
参数:
start:起始地址
n:连续大小
返回值:无
- release_mem_region
路径:ioport.h linux-3.5\include\linux
原型:
#define release_mem_region(start,n) __release_region(&iomem_resource, (start), (n))
功能:释放一段release_mem_region
申请的IO
内存空间(IORESOURCE_MEM
类型)(ARM
架构)
参数:
start:起始地址
n:连续大小
返回值:无
特别注意
- 高版本中的相同功能宏,安全性较高,只是多了一个参数指定了设备指针中的device结构指针,不是平台设备结构指针而是平台设备结构中的device结构指针其余用法相同
#define devm_request_region(dev,start,n,name) \
__devm_request_region(dev, &ioport_resource, (start), (n), (name))
#define devm_request_mem_region(dev,start,n,name) \
__devm_request_region(dev, &iomem_resource, (start), (n), (name))
extern struct resource * __devm_request_region(struct device *dev,
struct resource *parent, resource_size_t start,
resource_size_t n, const char *name);
#define devm_release_region(dev, start, n) \
__devm_release_region(dev, &ioport_resource, (start), (n))
#define devm_release_mem_region(dev, start, n) \
__devm_release_region(dev, &iomem_resource, (start), (n))
驱动层编写
目标:把以前写的杂项led驱动修改为平台设备驱动模型
编程思想
- 先编写一个模块基本模板
- 定义一个平台驱动结构变量
- 初始化上一步定义的平台结构变量
- 在模块加载函数中用平台驱动注册函数注册上一步初始化好的平台驱动结构变量
- 在模块卸载函数调用平台驱动注销函数注销平台驱动
核心:
- 设备名:
.driver.name
:保持和设备层中相同即可 - id:如果一对一就赋值为-1
probe函数:
探测平台设备资源
申请使用平台设备资源
使用申请到的资源:内存资源-映射成虚拟地址使用,中断资源-注册中断
注册字符设备(杂项设备):以前应该怎么写还怎么写remove函数:功能与
probe
函数相反,所做的事情就是释放在probe
函数中占用的资源
整体代码
device代码
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/types.h>
#include<linux/interrupt.h>
#include<linux/platform_device.h>
//定义leds设备占用的资源
struct resource myleds_res[]={
[0]={
.start =0x110002e0,
.end =0x110002f7,
.flags =IORESOURCE_MEM,
.name ="GPM4",
}
};
void myleds_release(void)
{
}
//定义一个平台设备变量
struct platform_device myleds_dev={
//原理图上4个led GPM4^0-4,所以使用GPM4相关寄存器
//起始寄存器地址0x1100 02E0 结束寄存器地址0x1100 02F4
//但是最后一个寄存器占用4字节地址
//总的占用内存空间大小0x1100 02F4 + 4 - 1 -0x1100 02E0 +1=0x18
.name ="myleds",
.id =-1,
.num_resources = ARRAY_SIZE(myleds_res),
.resource = myleds_res,
.dev={
.release=myleds_release,
}
};
static int __init ldedrv_init(void)
{
platform_device_register(&myleds_dev);
printk("%s is call\r\n",__FUNCTION__);
return 0;
}
static void __exit leddrv_exit(void)
{
platform_device_unregister(&myleds_dev);
printk("%s is call\r\n",__FUNCTION__);
}
module_init(ldedrv_init);
module_exit(leddrv_exit);
MODULE_LICENSE("GPL");
driver代码
#include<linux/module.h>
#include<linux/init.h>
#include<linux/kernel.h>//添加头文件
#include<linux/ioctl.h>
#include<linux/types.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<asm/io.h>
#include<linux/platform_device.h>
#include<linux/miscdevice.h>
#include<asm/uaccess.h>
//定义资源指针
static struct resource *res;
unsigned long *base_addr=NULL;
#define GPM4CON (*(volatile unsigned long *)(base_addr+0))
#define GPM4DAT (*(volatile unsigned long *)(base_addr+1))
//打开函数
static int misc_open(struct inode *node, struct file *fp)
{
GPM4DAT &= ~0XF;
printk("all leds is light\r\n");
return 0;
}
//关闭函数
static int misc_close(struct inode *node, struct file *fp)
{
printk("this dev is close\r\n");
return 0;
}
//读函数
ssize_t misc_read(struct file *fp, char __user *buf, size_t size, loff_t *loff)
{
printk("this dev is read\r\n");
return 0;
}
//写函数
ssize_t misc_write(struct file *fp, const char __user *buf, size_t size, loff_t *loff)
{
printk("this dev is write\r\n");
return 0;
}
//操作函数
struct file_operations fops={
.owner=THIS_MODULE,
.open=misc_open,
.read=misc_read,
.write=misc_write,
.release=misc_close,
};
//杂项设备结构体
struct miscdevice mymisc={
.minor=255,
.name="my_leds",
.fops=&fops,
};
//探测函数
static int led_probe(struct platform_device *pdev)
{
int irq;
int size;
int ret;
//指向设备层中的 &my_leds[0]
res=platform_get_resource(pdev,IORESOURCE_MEM,0);
printk("res->name:%s\r\n",res->name);
printk("res->start:0x%x,res->end:0x%x",res->start,res->end);
//向内核申请资源
size=res->end - res->start + 1;
res= devm_request_mem_region(&pdev->dev,res->start, size, "myleds");
if(!res)
{
printk("devm_request_mem_region error\r\n");
//注册失败释放资源
size=res->end - res->start + 1;
//这里应特别注意第一个参数为平台设备结构中的device结构指针
devm_release_mem_region(&pdev->dev, res->start, size);
return -EBUSY;
}
printk("devm_request_mem_region ok\r\n");
//注册核心结构
if(misc_register(&mymisc))
{
printk("misc_register is insmod fail\r\n");
return -1;
}
printk("misc_register is success\r\n");
base_addr = ioremap(res->start,size);
GPM4CON &= ~0XFFFF;
GPM4CON |= 0x1111;
GPM4DAT |= 0XF;
return 0;
}
static int led_remove(struct platform_device *pdev)
{
int size;
int ret;
//注销核心结构
misc_deregister(&mymisc);
res=platform_get_resource(pdev,IORESOURCE_MEM,0);
size=res->end - res->start + 1;
devm_release_mem_region(&pdev->dev, res->start, size);
iounmap(base_addr);
}
//平台设备驱动结构
struct platform_driver led_driver={
.probe=led_probe,
.remove=led_remove,
.driver={
.owner=THIS_MODULE,
.name="myleds",
},
};
/* 2、编写驱动入口出口函数 */
static __init int leddrv_init(void)
{
/* 平台设备注册 */
platform_driver_register(&led_driver);
printk("leddrv_init...!!\n");
return 0;
}
static __exit void leddrv_exit(void)
{
platform_driver_unregister(&led_driver);
}
/* 1、指定驱动入口出口函数 */
module_init(leddrv_init);
module_exit(leddrv_exit);
MODULE_LICENSE("GPL");
app代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
unsigned char LED_STATUE,shi;
int ge;
int led_fp;
led_fp = open(argv[1],O_RDWR);
close(led_fp);
}
Makefile
KERN_DIR = /zhangchao/linux3.5/linux-3.5
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += platform_driver.o
obj-m += platform_device.o
开发板运行效果
[root@ZC/zhangchao]#insmod platform_device.ko
[ 1208.980000] ldedrv_init is call
[root@ZC/zhangchao]#insmod platform_driver.ko
[ 1222.025000] res->name:GPM4
[ 1222.025000] res->start:0x110002e0,res->end:0x110002f7devm_request_mem_region ok
[ 1222.035000] misc_register is success
[ 1222.040000] leddrv_init...!!
[root@ZC/zhangchao]#./app /dev/my_leds
[ 1232.870000] all leds is light
[ 1232.870000] this dev is close
对平台总线驱动模型的特点的理解
- 平台模型采用了分层结构,把一个设备驱动分为了两个部分:平台设备(
platform_device
)和平台驱动(platform_driver
) - 平台设备将设备本身的资源(占用哪些中断号,内存资源,
IO
口等)注册进内核,可以由内核统一管理,驱动更加安全、可靠。
驱动层probe函数使用资源前必须先申请:
res= devm_request_mem_region(&pdev->dev,res->start, size, "myleds");
或
res= request_mem_region(res->start, size, "myleds");
如果前面驱动对资源已经占用,则本驱动占用失败.如果一个资源申请后没有释放,则重复申请会失败,这样保证资源的安全性,系统安全性 - 统一了设备驱动模型,使智能电源管理更容易实现
如果你想你的设备可以休眠,可以实现下面两个成员结构
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;
const struct platform_device_id *id_table;
};
这是旧版本的电源管理结构,新版本的电源管理在.driver.pm
结构中 const struct dev_pm_ops *pm;
- 从代码维护角度看,平台模型的可移植性,通用性更好
一般只需要修改设备驱动模块中的设备信息和资源信息,驱动层不做修改
真正优秀的平台驱动代码可以做到,只要CPU
型号相同,一个驱动程序可以适应任何型号具体的开发板,对不同型号的开发板,只需要修改平台设备层的资源及平台数据即可。当然这里举的例子并不合适,将杂项设备的led
驱动修改为平台模型反而更复杂。
上面代码存在的问题:
1 驱动固定写死4
个led
,并且是连续的,只能从第0
个开始
2 如果要实现真正一个通用性效果就要对上面代码进行改进,使用平台数据进行改进
3 目标:实现一个平台驱动层代码可以任意数量的led
,任意IO
位置的效果 - 平台设备并不是一种新的设备,
linux
目前只有三种设备类型:字符设备、块设备、网络设备 - 平台设备是按总线进行划分的,所以平台设备不是一种新设备,也不是特定字符设备或块设备,它可以是三种设备之一
平台驱动代码优化
平台数据的使用
目标:只要同一组IO
口,平台驱动层代码可以适应任意数量,任意IO
口。对不同的情况只需要修改一下平台设备层代码即可
分析:要实现以上功能平台设备层必须传递以下两个信息给平台驱动层
1 平台设备层要传递的led
灯数量
2 每一个led
所在本组第几个IO
口上
其实我们知道,设备结构中的所有数据,在驱动中probe
函数都可以从本身的参数拿到,这样的话有没有我们可以自定义的数据可以进行数据传递呢?有!这个数据就在
struct platform_device
结构中struct device
结构成员中的void *platform_data
成员,由于是void*
类型可以传递任意类型的数据。
.platform_device.dev.platform_data
设计思想:
- 把每一个灯看组一个对象:必须信息是在哪个编号上。(如果不在一组上还需直到在哪一个组)
- 总共有几个灯
可以把上面内容设计一个结构体:然后在一个头文件中声明,设备模块和驱动模块都包含这个头文件。
在驱动模块中定义
公共头文件的内容
//定义单灯信息结构
struct led_info{
int pin_nr;//本组第几个IO口
};
//定义总结构
struct leds_data{
struct led_info *leds_info;
int leds_num;
};
设备模块中定义的内容
//定义开发板每个led信息
struct led_info myled_info[]={
[0]={
.pin_nr=0,
},
[1]={
.pin_nr=1,
},
[2]={
.pin_nr=2,
},
[3]={
.pin_nr=3,
},
};
//定义leds设备占用的资源
struct resource myleds_res[]={
[0]={
.start =0x110002e0,
.end =0x110002f7,
.flags =IORESOURCE_MEM,
.name ="GPM4",
}
};
struct platform_device myleds_dev={
//原理图上4个led GPM4^0-4,所以使用GPM4相关寄存器
//起始寄存器地址0x1100 02E0 结束寄存器地址0x1100 02F4
//但是最后一个寄存器占用4字节地址
//总的占用内存空间大小0x1100 02F4 + 4 - 1 -0x1100 02E0 +1=0x18
.name ="myleds",
.id =-1,
.num_resources = ARRAY_SIZE(myleds_res),
.resource = myleds_res,
.dev={
.release=myleds_release,
.platform_data=&myleds_data,
}
};
在驱动模块中获取传递下来的数据,根据数据初始化相应的寄存器
//探测函数
struct leds_data *pdata_leds;
static int led_probe(struct platform_device *pdev)
{
int irq;
int size;
int ret;
int i;
//定义资源指针
static struct resource *res;
//////////////////////////////////////////////////////////////////////
//取得平台设备传下来的数据结构
pdata_leds=(struct leds_data*)pdev->dev.platform_data;
base_addr = ioremap(res->start,size);
//根据传下来的数据进行初始化对应的IO
for(i=0;i<pdata_leds->leds_num,i++){
GPM4CON &= ~(0XF<<pdata_leds->leds_info[i].pin_nr);
GPM4CON |= (0X1<<pdata_leds->leds_info[i].pin_nr);
GPM4DAT |= (0X1<<pdata_leds->leds_info[i].pin_nr);
}
////////////////////////////////////////////////////////////////////
//指向设备层中的 &my_leds[0]
res=platform_get_resource(pdev,IORESOURCE_MEM,0);
printk("res->name:%s\r\n",res->name);
printk("res->start:0x%x,res->end:0x%x",res->start,res->end);
//向内核申请资源
size=res->end - res->start + 1;
res= devm_request_mem_region(&pdev->dev,res->start, size, "myleds");
if(!res)
{
printk("devm_request_mem_region error\r\n");
//注册失败释放资源
size=res->end - res->start + 1;
//这里应特别注意第一个参数为平台设备结构中的device结构指针
devm_release_mem_region(&pdev->dev, res->start, size);
return -EBUSY;
}
printk("devm_request_mem_region ok\r\n");
//注册核心结构
if(misc_register(&mymisc))
{
printk("misc_register is insmod fail\r\n");
return -1;
}
printk("misc_register is success\r\n");
return 0;
}
根据灯的IO
编号可以初始化的相应IO
的寄存器,如果灯在IO
口上不是连续的,只需要修改每个灯对应led_info.pin_nr
引脚值即可。例如以下
struct led_info myled_info[]={
[0]={
.pin_nr=0,
},
[1]={
.pin_nr=3,
},
[2]={
.pin_nr=5,
},
[3]={
.pin_nr=7,
},
};
获取指定灯的状态,或者设置指定灯的状态都可以通过platform_data
传下的数据进行设置。