1、概述

(1)平台总线概述
  linux2.6内核引入的虚拟总线,主要是为了统一管理所有的设备和驱动,提高程序可移植性。这样我们在开发设备驱动的时候就只需要:
①定义平台设备:platform_device
②注册平台设备
③定义平台驱动:platform_driver
④注册平台驱动:

(2)平台设备
①平台设备struct platform_device概述:
struct platform_device{
  const char *name;
  int id; 设备编号
  struct device dev; 前面总线上谈到的设备
  u32 num_resource;资源的数量
  struct resource *resource;设备资源
}
struct resource {
  resource_size_t start;
  resource_size_t end;
  const char *name;
  unsigned long flags;资源的类型,例如是中断还是GPIO等
  struct resource *parent,*sibling,*child;
}
②平台设备的注册:
int platform_device_register(struct platform_device *pdev);

(3)平台驱动
①平台驱动的描述
struct platform_driver{
  int (*probe)(struct platform_device*); probe函数用于找设备
  int (*remove)(struct platform_device);
  .....
}
②平台驱动的注册
int platform_driver_register(struct platform_deiver *)

2、match函数实现匹配

(1)linux内核中已经定义了声明了platform_bus_type结构体
struct bus_type platform_bus_type={
  .name= "platform",
  .dev_attrs= platform_dev_attrs,
  .match= platform_match,
  .uevent=platform_uvent,
  ...
}

3、改写之前的按键驱动为平台总线驱动

(1)首先要明确平台驱动模型分为驱动和设备key_dev.c key_drv.c
(2)回顾之前的按键驱动程序,注册混杂设备,查看对应的中断号注册中断,初始化硬件引脚寄存器为中断功能,注意这个引脚寄存器GPFCON既能设置为中断,也能设置为IO内存资源。这里为什么要这么做呢?例如
#define GPFCON 0x56000050 点灯 按键中断都在这,也就是说这个“设备”有两个功能
static struct resource key_resource[] = {
  [0] = {
    .start = GPFCON,
    .end = GPFCON + 8;
    .flags = IORESOURCE_MEM,
  },
  [1] = {
    .start = IRQ_EINT0,
    .end = IRQ_EINT2,
    .flags = IORESOURCE_IRQ,
  },
};

其实平台设备的注册不需要做过多的解析,就是定义了一个变量,保存着平台驱动需要用到的硬件信息。把这些信息放在struct resource 结构体中,然后在平台驱动找到设备后会调用驱动的probe函数,并且这个函数的参数就是平台设备。至此,平台设备的作用就完成了,只起到一个保存硬件信息的作用。

//在平台设备中,如何注册一个设备搭配平台总线上。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
MODULE_LICENSE("GPL");
/*说明设备有几个资源,由于驱动程序一方面需要知道设备按键的中断号,另一方面还需要设置设备引脚为输出功能*/
#define GPFCON 0x56000050
static struct resource key_resource[] = {
    [0] = {
      .start = GPFCON,
      .end = GPFCON + 8;
      .flags = IORESOURCE_MEM,
    },
    [1] = {
      .start = IRQ_EINT0,
      .end = IRQ_EINT2,
      .flags = IORESOURCE_IRQ,
    },
};
//声明一个需要注册的设备
struct platform_device key_device = {
    .name          = "my-key",
    .id          = 0,
    .num_resources      = ARRAY_SIZE(key_resource),
    .resource      = key_resource,
};
static int button_init()
{
    platform_device_register(&key_device);
    return 0;
}
static void button_exit()
{
    platform_device_unregister(&key_device);
}
module_init(button_init);
module_exit(button_exit);

(3)平台驱动编写

平台驱动主要是申请注册一个平台驱动结构体,这个结构体的probe函数中再套用之前的按键驱动程序。也就说在总线平台驱动中,只有当probe函数识别了设备的时候才去注册混杂设备驱动,才去做硬件初始化等等工作。

#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/platform_device.h>

MODULE_LICENSE("GPL")
struct work_struct *work;
struct timer_list buttons_timer;
unsigned int key_num = 0;
wait_queue_head_t  key_q;
struct resource *res;
struct resource *res_irq;
unsigned int *key_base;

void work_func(struct work_struct *work)
{
    mod_timer(&buttons_timer, jiffies + (HZ /10));
}
void buttons_timer_function(unsigned long data)  
{
    unsigned int key_val;
    key_val = readw(key_base+1)&0x1; 
    if (key_val == 0)
       key_num = 4;
    key_val = readw(key_base+1)&0x4;
    if (key_val == 0)
        key_num = 3;
    wake_up(&key_q); 
} 
irqreturn_t key_int(int irq, void *dev_id)
{
    //1. 检测是否发生了按键中断
    //2. 清除已经发生的按键中断
    //3. 提交下半部
    schedule_work(work);
    return IRQ_HANDLED;
}
void key_hw_init()
{
    unsigned short data; 
    data = readw(key_base);
    data &= ~0b110011;
    data |= 0b100010;
    writew(data,key_base);
}
int key_open(struct inode *node,struct file *filp)
{
    return 0;
}
ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
{ 
    wait_event(key_q,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,
};
struct miscdevice key_miscdev = {
    .minor = 200,
    .name = "key",
    .fops = &key_fops,
};

//找到这个设备调用的函数,pdev就是设备程序中注册的设备
int key_probe(struct platform_device *pdev)
{
    int ret,size;
    
    /*注册设备*/
    ret = misc_register(&key_miscdev);
    if (ret !=0)
        printk("register fail!\n");
    /*注册中断处理程序,注意以前中断号是写死的,现在驱动和设备分开之后,注册zh中断的时候,需要先去设备程序中找到对应的资源*/
    res_irq =  platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    request_irq(res_irq->start,key_int,IRQF_TRIGGER_FALLING,"key",(void *)4);
    request_irq(res_irq->end,key_int,IRQF_TRIGGER_FALLING,"key",(void *)3);   
    //按键初始化,获取按键引脚的物理地址,设置按键引脚为中断
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    size = (res->end - res->start) + 1;
    key_base = ioremap(res->start, size);
    key_hw_init();
    //. 创建工作
    work = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    INIT_WORK(work, work_func);
    /* 初始化定时器 */  
    init_timer(&buttons_timer);   
    buttons_timer.function  = buttons_timer_function;  
    /* 向内核注册一个定时器 */  
    add_timer(&buttons_timer);  
    /*初始化等待队列*/
    init_waitqueue_head(&key_q);
    
    return 0;
}
int key_remove(struct platform_device *dev)
{
    free_irq(res_irq->start, (void *)4);
    free_irq(res_irq->end, (void *)3);
    iounmap(key_base);
    misc_deregister(&key_miscdev);
    return 0;
}
static struct platform_driver key_driver={
    .probe =key_probe,
    .remove = key_remove,
    .driver  = {
        .owner = THIS_MODULE,
        .name = "my-key", 
    }
}
static int button_init()
{
    return platform_driver_register(&key_driver);
}
static void button_exit()
{
    platform_driver_unregister(&key_driver);
}
module_init(button_init);
module_exit(button_exit);