linux平台设备驱动模型

linux平台设备介绍

linux2.6以上的设备驱动模型中,有三大实体:总线,设备和驱动。总线负责将设备和驱动绑定,在系统没注册一个设备的时候,会寻找与之匹配的驱动:相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配则由总线完成。
一个现实的linux设备和驱动通常都需要挂接在一个总线上例如PCIUSBI2CSPI接口的设备都是由对应的总线来管理,通过总线来操作设备。但对于Soc系统中集成的独立外设控制器,比如PCIUSBI2CSPI等总线。基于这一背景,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成员指针指向的函数
  • 如果只安装驱动
    则卸载模块时执行情况和普通模块执行状况一样,不会执行proberemove指针指向的函数

    总的结论:不管先安装设备模块还是先安装驱动模块,内核都正确的匹配进行绑定设备和驱动的绑定


内核为设备和驱动匹配的依据

根据驱动模块中的`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_MEMIORESOURCE_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结构变量必要的元素,一般在定义时就初始化了
    初始化的核心分析清楚设备占用的资源:分析硬件原理图
    定义设备占用的资源数组
    初始化设备结构的resourcenum_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 驱动固定写死4led,并且是连续的,只能从第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传下的数据进行设置。

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值