浅谈平台总线驱动设计

Posted on 2015-02-17 19:02  Bumble_Bee  阅读(343)  评论(0编辑  收藏  举报

平台总线是linux2.6内核加入的一种虚拟总线,使用流程:

  1、定义设备

  2、注册设备

  3、定义驱动

  4、注册驱动

总线上的设备和驱动相互匹配由总线来完成。

一、定义设备

  平台设备描述结构:struct platform_device

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:设备名

    struct resource:设备资源(包括中断号、寄存器等)

    id:设备id

    num_resources:硬件占有资源的数量

  

struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    struct resource *parent, *sibling, *child;
};

  start和end的意义由flags来定,flags代表硬件资源的类型,linux规定了如下类型

/*
 * IO resources have these defined flags.
 */
#define IORESOURCE_BITS        0x000000ff    /* Bus-specific bits */

#define IORESOURCE_TYPE_BITS    0x00001f00    /* Resource type */
#define IORESOURCE_IO        0x00000100
#define IORESOURCE_MEM        0x00000200
#define IORESOURCE_IRQ        0x00000400
#define IORESOURCE_DMA        0x00000800
#define IORESOURCE_BUS        0x00001000

#define IORESOURCE_PREFETCH    0x00002000    /* No side effects */
#define IORESOURCE_READONLY    0x00004000
#define IORESOURCE_CACHEABLE    0x00008000
#define IORESOURCE_RANGELENGTH    0x00010000
#define IORESOURCE_SHADOWABLE    0x00020000

#define IORESOURCE_SIZEALIGN    0x00040000    /* size indicates alignment */
#define IORESOURCE_STARTALIGN    0x00080000    /* start field is alignment */

#define IORESOURCE_MEM_64    0x00100000
#define IORESOURCE_WINDOW    0x00200000    /* forwarded by bridge */
#define IORESOURCE_MUXED    0x00400000    /* Resource is software muxed */

#define IORESOURCE_EXCLUSIVE    0x08000000    /* Userland may not map this resource */
#define IORESOURCE_DISABLED    0x10000000
#define IORESOURCE_UNSET    0x20000000
#define IORESOURCE_AUTO        0x40000000
#define IORESOURCE_BUSY        0x80000000    /* Driver has marked this resource busy */

  例如当flags取IORESOURCE_MEM时,start、end代表硬件占有的首末内存地址;当flags取IORESOURCE_IRQ时,start、end则代表硬件占有的首末中断号。

二、注册平台设备

  函数原型:int platform_device_register(struct platform_device *pdev)

以mini2440键盘为例,定义并注册设备代码如下:

 

#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>

#define GPGCON 0x56000060
#define GPGDAT 0x56000064

struct resource key_resource[] = 
{
    [0] = {
        .start = GPGCON,
        .end = GPGCON + 8,
        .flags = IORESOURCE_MEM,
    },

    [1] = {
        .start = IRQ_EINT8,
        .end = IRQ_EINT19,
        .flags = IORESOURCE_IRQ,
    },
};

struct platform_device key_device = 
{
    .name = "my key",
    .id = 0,
    .num_resources = 2,        //硬件占有寄存器和中断号两个资源
    .resource = key_resource,
};

static int key_dev_init()
{
    /*注册平台设备*/
    platform_device_register(&key_device);
    return 0;
}

void key_dev_exit()
{
    /*注销平台设备*/
    platform_device_unregister(&key_device);
}

MODULE_LICENSE("GPL");
module_init(key_dev_init);
module_exit(key_dev_exit);

 

 

三、定义驱动

  平台驱动描述结构:struct platform_driver

 

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;
};

 

 

 

  重要成员:

    name:驱动名称  (仍采取设备名称和驱动名称来匹配,所以二者一定要一致)

    probe函数: 匹配成功后调用此函数

    remove函数:设备移除后执行此函数

四、注册驱动平台驱动

  函数原型:int platform_driver_register(struct platform_driver *drv)

 

 

将按键驱动程序由混杂设备驱动优化为平台总线驱动。之前的驱动程序都包含了硬件资源的信息,如GPGCON、GPGDAT寄存器的地址,各个按键的中断号等,移植性不高。优化过后,这些硬件信息都从匹配到的设备的platform_device结构中去取,从而将驱动和设备彻底分开了。从匹配到的设备中取硬件信息使用platform_get_resource函数

 

struct resource *platform_get_resource(struct platform_device *dev,
                       unsigned int type, unsigned int num)
{
    int i;

    for (i = 0; i < dev->num_resources; i++) {
        struct resource *r = &dev->resource[i];

        if (type == resource_type(r) && num-- == 0)
            return r;
    }
    return NULL;
}

 

该函数遍历设备资源后返回与type指定类型相同的资源。

优化后代码如下

key.h:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/platform_device.h>

/*存储键盘控制寄存器基地址*/
unsigned int *key_base;

/*定义工作项结构体*/
struct work_struct *work1;

/*定义定时器变量结构体*/
struct timer_list key_timer;

unsigned int key_num = 0;

/*定义等待队列*/
wait_queue_head_t key_queue;

struct resource *res_irq;
struct resource *res_mem;

/*open函数接口*/
int key_open(struct inode *node, struct file *filp)
{
    return 0; 
}

/*read函数接口*/
ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
    wait_event(key_queue,key_num);

    copy_to_user(buf,&key_num,4);

    /*清空按键键值*/
    key_num = 0;

    return 4;
}

/* 函数映射关系表*/
struct file_operations key_fops = 
{
    .open = key_open,
    .read = key_read,
    //.unlocked_ioctl = key_ioctl,
};

/*字符设备描述结构*/
struct miscdevice key_miscdev = 
{
    .minor = 200,
    .name = "key",
    .fops = &key_fops,
};

驱动程序:

#include "key.h"

/********************
函数名:work1_func
参数:无
返回值:无
函数功能:实现工作项
结构体中的func成员
********************/
void work1_func()
{
    /*启动定时器*/
    mod_timer(&key_timer,jiffies + (HZ/10));
}

/************************
函数名:que_init
参数:无
返回值:无
函数功能:创建一个工作项
*************************/
int que_init()
{
    /*创建工作*/
    work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);

    INIT_WORK(work1, work1_func);
}

/************************
函数名:key_int
参数:无
返回值:0
函数功能:按键中断处理函数
*************************/
irqreturn_t key_int(int irq, void *dev_id)
{
    /*1、检测设备是否产生中断*/

    /*2、清除中断产生标志*/

    /*3、提交下半部分工作*/
    schedule_work(work1);

    return 0;
}

/************************
函数名:key_hw_init
参数:无
返回值:无
函数功能:初始化与按键相关
的寄存器
*************************/
void key_hw_init()
{
    unsigned int data; 
    data = readw(key_base);
    data &= ((3)|(3<<6)|(3<<10)|(3<<12)|(3<<14)|(3<<22));//~(0b11);
    data |= (2|(2<<6)|(2<<10)|(2<<12)|(2<<14)|(2<<22));//0b10;
    writew(data,key_base);
}

/************************
函数名:key_timer_func
参数:无
返回值:无
函数功能:定时器超时处理函
数,达到规定时间执行此函数
*************************/
void key_timer_func()
{
    unsigned int key_val,i;

    for(i = 0;i < 15;i++)
    {
        if((i == 0)||(i == 3)||(i == 5)||(i == 6)||(i == 7)||(i == 11))
        {
            key_val = readw(key_base + 1) & (1 << i);
            if(key_val == 0)
                key_num = i+1;
        }
    }
    wake_up(&key_queue);
}

static int __devinit key_probe(struct platform_device *pdev)
{
    int size,ret;

    /*注册设备*/
    ret = misc_register(&key_miscdev);
    
  /*从匹配到的设备中取出设备硬件信息*/
res_irq = platform_get_resource(pdev,IORESOURCE_IRQ,0); res_mem = platform_get_resource(pdev,IORESOURCE_MEM,0);
  /*注册中断*/ request_irq(res_irq
->start,key_int,IRQF_TRIGGER_FALLING ,"key",(void *) 0); request_irq(res_irq->start + 3,key_int,IRQF_TRIGGER_FALLING ,"key",(void *) 0); request_irq(res_irq->start+5,key_int,IRQF_TRIGGER_FALLING ,"key",(void *) 0); request_irq(res_irq->start+6,key_int,IRQF_TRIGGER_FALLING ,"key",(void *) 0); request_irq(res_irq->start+7,key_int,IRQF_TRIGGER_FALLING ,"key",(void *) 0); request_irq(res_irq->end,key_int,IRQF_TRIGGER_FALLING ,"key",(void *) 0); size = res_mem->end - res_mem->start + 1; key_base = ioremap(res_mem->start,size); /*硬件初始化*/ key_hw_init(); /*工作队列初始化*/ que_init(); /*初始化定时器*/ init_timer(&key_timer); key_timer.function = key_timer_func; /*注册定时器*/ add_timer(&key_timer); /*初始化等待队列*/ init_waitqueue_head(&key_queue); return ret; } static int key_remove(struct platform_device *pdev) { /*注销设备*/ misc_deregister(&key_miscdev); /*注销中断*/ free_irq(IRQ_EINT8, 0); } /*定义平台总线驱动*/ struct platform_driver key_driver = { .driver = { .name = "my key", }, .probe = key_probe, .remove = key_remove, }; static int button_init() { /*注册平台驱动*/ platform_driver_register(&key_driver); printk("key.ko is ready\n"); return 0; } static void button_exit() { /*注销平台驱动*/ platform_driver_unregister(&key_driver); } MODULE_LICENSE("GPL"); module_init(button_init); module_exit(button_exit);

这次的随笔内容大致这么多,这里我有一个小问题想不明白,就是platform_get_resource这个函数的第三个参数num意义何在?我查阅linux内核代码发现大多数用到这个函数的地方将num赋为0,表示不知道为什么。如果有知道的麻烦告诉我,感激不尽~

此代码适用mini2440开发板,不同型号开发板IO口和中断号不同。如果有疑问或建议,欢迎指出。