platform设备驱动

简介: platform设备驱动

前言

编写字符设备驱动基本上都要实现以下内容:

1.实现入口函数xxx_init()和卸载函数xxx_exit()

2.申请设备号register_chrdev(与内核有关)

3.注册字符设备驱动cdev_alloc cdev_init cdev_add(与内核有关)

4.利用udev/mdev机制创建设备文件(节点),

class_create,device_create(与内核有关)

5.硬件部分初始化

io资源映射ioremap,内核提供gpio库函数(与硬件相关)

注册中断(与硬件相关)

初始化等待队列(与内核有关)

初始化定时器(与内核有关)

6.构建file_operation结构(与内核相关)

实现操作硬件方法xxx_open,xxx_read,xxx_write…(与硬件有关)

如果我们编写不同的字符设备驱动,要实现上述内容,基本上就是硬件相关部分不一样,其他部分基本上去都是“体力活”,为了减少体力活,我们可以将代码重用,就是“体力活”部分重复用,每次实现不同设备字符设备驱动实现硬件部分就行,这就是驱动的分离与分层思想,实现这一思想,最常用的就是platform框架。这是一个虚拟的总线。


一、驱动和设备的匹配

platform的match 函数,此函数很重要,单词 match 的意思就是“匹配、相配”,因此此函数就是完成设备和驱动之间匹配的,总线就是使用 match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。 match 函数有两个参数: dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。

static int platform_match(struct device *dev, struct device_driver *drv)
{
  struct platform_device *pdev = to_platform_device(dev);
  struct platform_driver *pdrv = to_platform_driver(drv);
  /* Attempt an OF style match first */
  if (of_driver_match_device(dev, drv))
    return 1;
  /* Then try ACPI style match */
  if (acpi_driver_match_device(dev, drv))
    return 1;
  /* Then try to match against the id table */
  if (pdrv->id_table)
    return platform_match_id(pdrv->id_table, pdev) != NULL;
  /* fall-back to driver name match */
  return (strcmp(pdev->name, drv->name) == 0);
}

第一种匹配方式, OF 类型的匹配,也就是设备树采用的匹配方式,of_driver_match_device 函数定义在文件 include/linux/of_device.h 中。 device_driver 结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行。

第二种匹配方式, ACPI 匹配方式。

第三种匹配方式, id_table 匹配,每个 platform_driver 结构体有一个 id_table成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型。

第四种匹配方式,如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。

二、platform调用关系

platform_device_register
platform_device_add
  device_add
    bus_add_device // 放入链表
    bus_probe_device // probe 枚举设备,即找到匹配的(dev, drv)
      device_initial_probe
        __device_attach
          bus_for_each_drv(...,__device_attach_driver,...)
            __device_attach_driver
              driver_match_device(drv, dev) // 是否匹配
              driver_probe_device // 调用 drv 的 probe
platform_driver_register
__platform_driver_register
  driver_register
    bus_add_driver // 放入链表
      driver_attach(drv)
        bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
          __driver_attach
            driver_match_device(drv, dev) // 是否匹配
            driver_probe_device // 调用 drv 的 probe

三、platform相关结构体

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

在编写驱动时,我们要实现probe函数和remove函数。probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行。driver 成员,为 device_driver 结构体变量, Linux 内核里面大量使用到了面向对象的思维, device_driver 相当于基类,提供了最基础的驱动框架。 plaform_driver 继承了这个基类,然后在此基础上又添加了一些特有的成员变量。id_table 表,也就是我们上一小节讲解 platform 总线匹配驱动和设备的时候采用的第三种方法,id_table 是个表(也就是数组),每个元素的类型为 platform_device_id,platform_device_id 结构体内容如下:

struct platform_device_id {
  char name[PLATFORM_NAME_SIZE];
  kernel_ulong_t driver_data;
};

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;
  const struct acpi_device_id *acpi_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;
};

of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都为 of_device_id 结构体类型,内容如下:

struct of_device_id
{
  char  name[32];
  char  type[32];
  char  compatible[128];
  const void *data;
};

compatible 非常重要,因为对于设备树而言,就是通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。

在编写 platform 驱动的时候,首先定义一个 platform_driver 结构体变量,然后实现结构体中的各个成员变量,重点是实现匹配方法以及 probe 函数。当驱动和设备匹配成功以后 probe函数就会执行,具体的驱动程序在 probe 函数里面编写,比如字符设备驱动等等。

四、常用函数

int platform_driver_register (struct platform_driver *driver)

driver:要注册的 platform 驱动。

返回值: 负数,失败; 0,成功。

void platform_driver_unregister(struct platform_driver *drv)

drv:要卸载的 platform 驱动。

返回值: 无。

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

dev:平台设备。描述设备信息的,有设备树描述。

type: 资源类型。下文详细讲解。

num: 资源索引。同类型资源进行重新编号后的下标编号,

struct resource *platform_get_resource_byname(struct platform_device *dev,
unsigned int type,
const char *name)

dev:平台设备。描述设备信息的,有设备树描述。

type: 资源类型。下文详细讲解。

num: 资源索引。同类型资源进行重新编号后的下标编号,

附录

device.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/* 
 * 寄存器地址定义
 */
#define CCM_CCGR1_BASE        (0X020C406C)  
#define SW_MUX_GPIO1_IO03_BASE    (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE    (0X020E02F4)
#define GPIO1_DR_BASE       (0X0209C000)
#define GPIO1_GDIR_BASE       (0X0209C004)
#define REGISTER_LENGTH       4
/* @description   : 释放flatform设备模块的时候此函数会执行 
 * @param - dev   : 要释放的设备 
 * @return      : 无
 */
static void led_release(struct device *dev)
{
  printk("led device released!\r\n"); 
}
/*  
 * 设备资源信息,也就是LED0所使用的所有寄存器
 */
static struct resource led_resources[] = {
  [0] = {
    .start  = CCM_CCGR1_BASE,
    .end  = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
    .flags  = IORESOURCE_MEM,
  },  
  [1] = {
    .start  = SW_MUX_GPIO1_IO03_BASE,
    .end  = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
    .flags  = IORESOURCE_MEM,
  },
  [2] = {
    .start  = SW_PAD_GPIO1_IO03_BASE,
    .end  = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
    .flags  = IORESOURCE_MEM,
  },
  [3] = {
    .start  = GPIO1_DR_BASE,
    .end  = (GPIO1_DR_BASE + REGISTER_LENGTH - 1),
    .flags  = IORESOURCE_MEM,
  },
  [4] = {
    .start  = GPIO1_GDIR_BASE,
    .end  = (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),
    .flags  = IORESOURCE_MEM,
  },
};
/*
 * platform设备结构体 
 */
static struct platform_device leddevice = {
  .name = "imx6ul-led",
  .id = -1,
  .dev = {
    .release = &led_release,
  },
  .num_resources = ARRAY_SIZE(led_resources),
  .resource = led_resources,
};
/*
 * @description : 设备模块加载 
 * @param     : 无
 * @return    : 无
 */
static int __init leddevice_init(void)
{
  return platform_device_register(&leddevice);
}
/*
 * @description : 设备模块注销
 * @param     : 无
 * @return    : 无
 */
static void __exit leddevice_exit(void)
{
  platform_device_unregister(&leddevice);
}
module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");

driver.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LEDDEV_CNT    1     /* 设备号长度  */
#define LEDDEV_NAME   "platled" /* 设备名字   */
#define LEDOFF      0
#define LEDON       1
/* 寄存器名 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/* leddev设备结构体 */
struct leddev_dev{
  dev_t devid;      /* 设备号  */
  struct cdev cdev;   /* cdev   */
  struct class *class;  /* 类    */
  struct device *device;  /* 设备   */
  int major;        /* 主设备号 */    
};
struct leddev_dev leddev;   /* led设备 */
/*
 * @description   : LED打开/关闭
 * @param - sta   : LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return      : 无
 */
void led0_switch(u8 sta)
{
  u32 val = 0;
  if(sta == LEDON){
    val = readl(GPIO1_DR);
    val &= ~(1 << 3); 
    writel(val, GPIO1_DR);
  }else if(sta == LEDOFF){
    val = readl(GPIO1_DR);
    val|= (1 << 3); 
    writel(val, GPIO1_DR);
  } 
}
/*
 * @description   : 打开设备
 * @param - inode   : 传递给驱动的inode
 * @param - filp  : 设备文件,file结构体有个叫做private_data的成员变量
 *            一般在open的时候将private_data指向设备结构体。
 * @return      : 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
  filp->private_data = &leddev; /* 设置私有数据  */
  return 0;
}
/*
 * @description   : 向设备写数据 
 * @param - filp  : 设备文件,表示打开的文件描述符
 * @param - buf   : 要写给设备写入的数据
 * @param - cnt   : 要写入的数据长度
 * @param - offt  : 相对于文件首地址的偏移
 * @return      : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
  int retvalue;
  unsigned char databuf[1];
  unsigned char ledstat;
  retvalue = copy_from_user(databuf, buf, cnt);
  if(retvalue < 0) {
    return -EFAULT;
  }
  ledstat = databuf[0];   /* 获取状态值 */
  if(ledstat == LEDON) {
    led0_switch(LEDON);   /* 打开LED灯 */
  }else if(ledstat == LEDOFF) {
    led0_switch(LEDOFF);  /* 关闭LED灯 */
  }
  return 0;
}
/* 设备操作函数 */
static struct file_operations led_fops = {
  .owner = THIS_MODULE,
  .open = led_open,
  .write = led_write,
};
/*
 * @description   : flatform驱动的probe函数,当驱动与
 *            设备匹配以后此函数就会执行
 * @param - dev   : platform设备
 * @return      : 0,成功;其他负值,失败
 */
static int led_probe(struct platform_device *dev)
{ 
  int i = 0;
  int ressize[5];
  u32 val = 0;
  struct resource *ledsource[5];
  printk("led driver and device has matched!\r\n");
  /* 1、获取资源 */
  for (i = 0; i < 5; i++) {
    ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); /* 依次MEM类型资源 */
    if (!ledsource[i]) {
      dev_err(&dev->dev, "No MEM resource for always on\n");
      return -ENXIO;
    }
    ressize[i] = resource_size(ledsource[i]); 
  } 
  /* 2、初始化LED */
  /* 寄存器地址映射 */
  IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
  SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
    SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
  GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
  GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);
  val = readl(IMX6U_CCM_CCGR1);
  val &= ~(3 << 26);        /* 清除以前的设置 */
  val |= (3 << 26);       /* 设置新值 */
  writel(val, IMX6U_CCM_CCGR1);
  /* 设置GPIO1_IO03复用功能,将其复用为GPIO1_IO03 */
  writel(5, SW_MUX_GPIO1_IO03);
  writel(0x10B0, SW_PAD_GPIO1_IO03);
  /* 设置GPIO1_IO03为输出功能 */
  val = readl(GPIO1_GDIR);
  val &= ~(1 << 3);     /* 清除以前的设置 */
  val |= (1 << 3);      /* 设置为输出 */
  writel(val, GPIO1_GDIR);
  /* 默认关闭LED1 */
  val = readl(GPIO1_DR);
  val |= (1 << 3) ; 
  writel(val, GPIO1_DR);
  /* 注册字符设备驱动 */
  /*1、创建设备号 */
  if (leddev.major) {   /*  定义了设备号 */
    leddev.devid = MKDEV(leddev.major, 0);
    register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
  } else {            /* 没有定义设备号 */
    alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME); /* 申请设备号 */
    leddev.major = MAJOR(leddev.devid); /* 获取分配号的主设备号 */
  }
  /* 2、初始化cdev */
  leddev.cdev.owner = THIS_MODULE;
  cdev_init(&leddev.cdev, &led_fops);
  /* 3、添加一个cdev */
  cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
  /* 4、创建类 */
  leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
  if (IS_ERR(leddev.class)) {
    return PTR_ERR(leddev.class);
  }
  /* 5、创建设备 */
  leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
  if (IS_ERR(leddev.device)) {
    return PTR_ERR(leddev.device);
  }
  return 0;
}
/*
 * @description   : platform驱动的remove函数,移除platform驱动的时候此函数会执行
 * @param - dev   : platform设备
 * @return      : 0,成功;其他负值,失败
 */
static int led_remove(struct platform_device *dev)
{
  iounmap(IMX6U_CCM_CCGR1);
  iounmap(SW_MUX_GPIO1_IO03);
  iounmap(SW_PAD_GPIO1_IO03);
  iounmap(GPIO1_DR);
  iounmap(GPIO1_GDIR);
  cdev_del(&leddev.cdev);/*  删除cdev */
  unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */
  device_destroy(leddev.class, leddev.devid);
  class_destroy(leddev.class);
  return 0;
}
/* platform驱动结构体 */
static struct platform_driver led_driver = {
  .driver   = {
    .name = "imx6ul-led",     /* 驱动名字,用于和设备匹配 */
  },
  .probe    = led_probe,
  .remove   = led_remove,
};
/*
 * @description : 驱动模块加载函数
 * @param     : 无
 * @return    : 无
 */
static int __init leddriver_init(void)
{
  return platform_driver_register(&led_driver);
}
/*
 * @description : 驱动模块卸载函数
 * @param     : 无
 * @return    : 无
 */
static void __exit leddriver_exit(void)
{
  platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");


目录
相关文章
|
7月前
|
Linux 程序员
Linux系统中驱动之设备树的platform驱动实现
Linux系统中驱动之设备树的platform驱动实现
213 0
Linux 平台设备platform
Linux 平台设备platform
|
编解码 Linux API
Linux ALSA驱动之Platform源码分析(wm8350.c)
Linux ALSA驱动之Platform源码分析(wm8350.c)
|
JavaScript 物联网 芯片
AliOS Things 硬件抽象层(HAL)对接系列2 — SPI driver porting
HAL层(Hardware abstraction layer) 的目的是为了屏蔽底层不同芯片平台的差异,从而使驱动层上面的软件不会随芯片平台而改变。AliOS Things定义了全面的HAL抽象层,这个系列主要介绍AliOS ThingsHAL层与不同芯片平台对接的poring要点,并举例说明。
2968 0
|
JavaScript 物联网 芯片
AliOS Things 硬件抽象层(HAL)对接系列3 — I2C driver porting
HAL层(Hardware abstraction layer) 的目的是为了屏蔽底层不同芯片平台的差异,从而使驱动层上面的软件不会随芯片平台而改变。AliOS Things定义了全面的HAL抽象层,这个系列主要介绍AliOS ThingsHAL层与不同芯片平台对接的poring要点,并举例说明。
3336 0
|
物联网 中间件
AliOS Things 使用HAL库的USB_DEVICE MSC调用SPI W25Q128
AliOS Things的USB_DEVICE MSC的SPI W25Q128实现
1141 0
|
JavaScript 物联网 芯片
AliOS Things 硬件抽象层(HAL)对接系列1 — GPIO driver porting
HAL层(Hardware abstraction layer 硬件抽象层) 的目的是为了屏蔽底层不同芯片平台的差异,从而使驱动层上面的软件不会随芯片平台而改变。AliOS Things定义了全面的HAL抽象层,这个系列主要介绍AliOS ThingsHAL层与不同芯片平台对接的poring要点,并举例说明。
3635 0