【IMX6ULL学习笔记】十八、Platform 驱动框架

一、platform 驱动

Linux 提出了 platform 这个虚拟总线,相应的就有 platform_driver 和 platform_device。
platform 驱动使用 platform_driver 结构体表示,此结构体定义在文件
include/linux/platform_device.h 中,内容如下:

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

第 2 行:probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行,一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么 probe 就需要自行实现。
第 7 行:driver 成员,为 device_driver 结构体变量,Linux 内核里面大量使用到了面向对象的思维,device_driver 相当于基类,提供了最基础的驱动框架。plaform_driver 继承了这个基类,然后在此基础上又添加了一些特有的成员变量。
第 8 行: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 结构体定义在 include/linux/device.h,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;
};

第 10 行:of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都为 of_device_id 结构体类型,此结构体定义在文件 include/linux/mod_devicetable.h 中,内容如下:

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

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

在编写 platform 驱动的时候,首先定义一个 platform_driver 结构体变量,然后实现结构体中的各个成员变量,重点是实现匹配方法以及 probe 函数。当驱动和设备匹配成功以后 probe 函数就会执行,具体的驱动程序在 probe 函数里面编写,比如字符设备驱动等等。
当我们定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里调用 platform_driver_register 函数向 Linux 内核注册一个 platform 驱动,platform_driver_register 函数原型如下所示:

int platform_driver_register (struct platform_driver *driver)

函数参数和返回值含义如下:

driver:要注册的 platform 驱动。
返回值:负数,失败;0,成功。

还需要在驱动卸载函数中通过 platform_driver_unregister 函数卸载 platform 驱动,platform_driver_unregister 函数原型如下:

void platform_driver_unregister(struct platform_driver *drv)

函数参数和返回值含义如下:

drv:要卸载的 platform 驱动。
返回值:无。

二、platform 设备

1、无设备树

platform_device 这个结构体表示 platform 设备,如果内核支持设备树的话就不要再使用 platform_device 来描述设备了,因为改用设备树去描述了。你如果
一定要用 platform_device 来描述设备信息的话也是可以的。
platform_device 结构体定义在文件include/linux/platform_device.h 中,结构体内容如下:

struct platform_device {
    const char *name;
    int id;
    bool id_auto;
    struct device dev;
    u32 num_resources;
    struct resource *resource;
    
    const struct platform_device_id *id_entry;
    char *driver_override; /* Driver name to force a match */
    
    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;
    
    /* arch specific additions */
    struct pdev_archdata archdata;
};

第 2 行:name 表示设备名字,要和所使用的 platform 驱动的 name 字段相同,否则的话设备就无法匹配到对应的驱动。比如对应的 platform 驱动的 name 字段为“xxx-gpio”,那么此 name 字段也要设置为“xxx-gpio”。
第 6 行:num_resources 表示资源数量,一般为第7行 resource 资源的大小。
第 7 行:resource 表示资源,也就是设备信息,比如外设寄存器等。Linux 内核使用 resource 结构体表示资源,resource 结构体内容如下:

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 表示资源类型,可选的资源类型都定义在了文件 include/linux/ioport.h 里面,如下所示:

#define IORESOURCE_BITS  0x000000ff     /* Bus-specific bits */
#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO    0x00000100     /* PCI/ISA I/O ports */
#define IORESOURCE_MEM   0x00000200
#define IORESOURCE_REG   0x00000300     /* Register offsets */
#define IORESOURCE_IRQ   0x00000400
#define IORESOURCE_DMA   0x00000800
#define IORESOURCE_BUS   0x00001000
......
/* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */
#define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */

在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中,此函数原型如下所示:

int platform_device_register(struct platform_device *pdev)

函数参数和返回值含义如下:

pdev:要注册的 platform 设备。
返回值:负数,失败;0,成功。

如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform 设备,platform_device_unregister 函数原型如下:

void platform_device_unregister(struct platform_device *pdev)

函数参数和返回值含义如下:

pdev:要注销的 platform 设备。
返回值:无。

platform 设备信息框架如下所示:

/* 寄存器地址定义*/
#define PERIPH1_REGISTER_BASE (0X20000000) /* 外设 1 寄存器首地址 */
#define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设 2 寄存器首地址 */
#define REGISTER_LENGTH 4

/* 资源 */
static struct resource xxx_resources[] = {
    [0] = {
        .start = PERIPH1_REGISTER_BASE,
        .end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = PERIPH2_REGISTER_BASE,
        .end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
};

/* platform 设备结构体 */
static struct platform_device xxxdevice = {
    .name = "xxx-gpio",
    .id = -1,
    .num_resources = ARRAY_SIZE(xxx_resources),
    .resource = xxx_resources,
};

/* 设备模块加载 */
static int __init xxxdevice_init(void)
{
    return platform_device_register(&xxxdevice);
}

/* 设备模块注销 */
static void __exit xxx_resourcesdevice_exit(void)
{
    platform_device_unregister(&xxxdevice);
}

module_init(xxxdevice_init);
module_exit(xxxdevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

驱动程序框架如下:

#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");
MODULE_AUTHOR("zuozhongkai");

2、有设备树

使用设备树的时候,设备的描述被放到了设备树中,因此 platform_device 就
不需要我们去编写了,我们只需要实现 platform_driver 即可。在编写基于设备树的 platform 驱动的时候我们需要注意一下几点:

1、在设备树中创建设备节点

在设备树中创建设备节点来描述设备信息,重点是要设置好 compatible 属性的值,因为 platform 总线需要通过设备节点的 compatible 属性值来匹配驱动,编写如下所示的设备节点来描述实验要用到的 LED 这个设备:

gpioled {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "atkalpha-gpioled";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_led>;
    led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
    status = "okay";
};

2、编写 platform 驱动的时候要注意兼容属性

在使用设备树的时候 platform 驱动会通过 of_match_table 来保存兼容性值,也就是表明此驱动兼容哪些设备。所以,of_match_table 将会尤为重要,比如本例程的 platform 驱动中 platform_driver 就可以按照如下所示设置:

static const struct of_device_id leds_of_match[] = {
    { .compatible = "atkalpha-gpioled" }, /* 兼容属性 */
    { /* Sentinel */ }
};

MODULE_DEVICE_TABLE(of, leds_of_match);

static struct platform_driver leds_platform_driver = {
    .driver = {
        .name = "imx6ul-led",
        .of_match_table = leds_of_match,
    },
    .probe = leds_probe,
    .remove = leds_remove,
};

第 1~4 行:of_device_id 表,也就是驱动的兼容表,是一个数组,每个数组元素为 of_device_id 类型。每个数组元素都是一个兼容属性,表示兼容的设备,一个驱动可以跟多个设备匹配。这里仅匹配了一个设备,就是 gpioled 这个设备。
第 2 行:compatible 值为“atkalpha-gpioled”,驱动中的 compatible 属性和设备中的 compatible 属性(设备树中定义)相匹配,因此驱动中对应的 probe 函数就会执行。注意第 3 行是一个空元素,在编写 of_device_id 的时候最后一个
元素一定要为空!
第 6 行:用MODULE_DEVICE_TABLE声明一下 leds_of_match 这个设备匹配表。
第 11 行:设置 platform_driver 中的 of_match_table 匹配表为上面创建的 leds_of_match,至此就设置好了 platform 驱动的匹配表了。

3、编写 platform 驱动

新建名为 leddriver.c 的驱动文件,在 leddriver.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 "dtsplatled" /* 设备名字 */
#define LEDOFF 0
#define LEDON 1

/* leddev 设备结构体 */
struct leddev_dev{
    dev_t devid;    /* 设备号 */
    int major;      /* 主设备号 */
    struct cdev cdev;      /* cdev */
    struct class *class;   /* 类 */
    struct device *device; /* 设备 */
    struct device_node *node;  /* LED 设备节点 */
    int led0;                  /* LED 灯 GPIO 标号 */
};

struct leddev_dev leddev; /* led 设备 */

/*
 * @description : LED 打开/关闭
 * @param - sta : LEDON(0) 打开 LED,LEDOFF(1) 关闭 LED
 * @return : 无
 */
void led0_switch(u8 sta)
{
    if (sta == LEDON )
        gpio_set_value(leddev.led0, 0);
    else if (sta == LEDOFF)
        gpio_set_value(leddev.led0, 1);
}

/*
 * @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[2];
    unsigned char ledstat;

    retvalue = copy_from_user(databuf, buf, cnt);
    if(retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0];
    if (ledstat == LEDON) {
        led0_switch(LEDON);
    } else if (ledstat == LEDOFF) {
        led0_switch(LEDOFF);
    }
    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)
{
    printk("led driver and device was matched!\r\n");
    /* 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_init(&leddev.cdev, &led_fops);
    cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);

    /* 3、创建类 */
    leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
    if (IS_ERR(leddev.class)) {
        return PTR_ERR(leddev.class);
    }

    /* 4、创建设备 */
    leddev.device = device_create(leddev.class, NULL, leddev.devid,
    NULL, LEDDEV_NAME);
    if (IS_ERR(leddev.device)) {
        return PTR_ERR(leddev.device);
    }

    /* 5、初始化 IO */
    leddev.node = of_find_node_by_path("/gpioled");
    if (leddev.node == NULL){
        printk("gpioled node nost find!\r\n");
        return -EINVAL;
    }

    leddev.led0 = of_get_named_gpio(leddev.node, "led-gpio", 0);
    if (leddev.led0 < 0) {
        printk("can't get led-gpio\r\n");
        return -EINVAL;
    }

    gpio_request(leddev.led0, "led0");
    gpio_direction_output(leddev.led0, 1); /*设置为输出,默认高电平 */
    return 0;
}

/*
 * @description : remove 函数,移除 platform 驱动的时候此函数会执行
 * @param - dev : platform 设备
 * @return : 0,成功;其他负值,失败
 */
static int led_remove(struct platform_device *dev)
{
    gpio_set_value(leddev.led0, 1); /* 卸载驱动的时候关闭 LED */

    cdev_del(&leddev.cdev); /* 删除 cdev */
    unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
    device_destroy(leddev.class, leddev.devid);
    class_destroy(leddev.class);
}

/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
    { .compatible = "atkalpha-gpioled" },
    { /* Sentinel */ }
};

/* platform 驱动结构体 */
static struct platform_driver led_driver = {
    .driver = {
        .name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */
        .of_match_table = led_of_match, /* 设备树匹配表 */
    },
    .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");
MODULE_AUTHOR("KODO");
posted @   酷电玩家  阅读(149)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 提示词工程——AI应用必不可少的技术
· 地球OL攻略 —— 某应届生求职总结
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界
点击右上角即可分享
微信分享提示