首页 > 其他分享 >【IMX6ULL学习笔记】十八、Platform 驱动框架

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

时间:2023-02-18 23:11:23浏览次数:43  
标签:struct platform driver 笔记 Platform device IMX6ULL include 设备

一、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");

标签:struct,platform,driver,笔记,Platform,device,IMX6ULL,include,设备
From: https://www.cnblogs.com/KuDianWanJia/p/17133108.html

相关文章

  • 【IMX6ULL学习笔记】十七、总线驱动框架-Platform、IIC、SPI等
    一、总线Linux总线驱动模型主要可以分为三个部分:总线、设备、驱动。Linux中的总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离。Linux内核在启动时会向......
  • Android笔记--添加联系人
    添加联系人(将联系人信息添加到手机的通讯录里面)方式一:使用ContentResolver方法写入对象,每次一个字段新创建一个需要加入通讯录的对象(我这里写的比较简单,并不是通讯录的......
  • PHP学习笔记(观隅反三)
    前言上一篇文章PHP学习笔记(才贯二酉)为大家介绍了PHP中的流程控制语句,这篇文章接着学习PHP中的数组。数组(一)为什么要创建数组这里就不赘述了,在PHP中,有三种类型的数组:​​......
  • 【IMX6ULL学习笔记】十五、设备树DTS操作函数
    一、设备树常用OF操作函数设备树描述了设备的详细信息,这些信息包括数字类型的、字符串类型的、数组类型的,我们在编写驱动的时候需要获取到这些信息。Linux内核给我们提......
  • 【IMX6ULL学习笔记】十六、设备树下LED驱动
    一、修改设备树文件在根节点“/”下创建一个名为“alphaled”的子节点,打开imx6ull-alientek-emmc.dts文件,在根节点“/”最后面输入如下所示内容:alphaled{#address-ce......
  • 笔记20230218
    <!DOCTYPEhtml><!--savedfromurl=(0060)http://demo.cssmoban.com/cssthemes5/cpts_1121_brc/index.html--><html><head><metahttp-equiv="Content-Type"content="t......
  • Linux操作系统课程笔记
    1.配置安装源什么是安装源?安装源类似于一个软件仓库,绝大部分系统软件和工具都可以从软件仓库中自动下载和安装。Ubuntu安装后默认的官方源是美国的服务器,因此访问速度很......
  • 【IMX6ULL学习笔记】十四、Linux调用设备树
    一、设备树在系统中的体现Linux内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/device/tree目录下根据节点名字创建不同文件夹,如图所示:上图就......
  • 【量化读书笔记】【打开量化投资的黑箱】CH.05. 交易成本模型
    交易是有成本的,除非有足够的理由,否则便不应该进行交易。交易的原因增加盈利的期望值降低亏损的期望值对交易成本的估计过低,会导致交易过于频繁,损失扩大。过高......
  • 阅读笔记————架构漫谈
    今天阅读了架构漫谈这一博客对于架构的理解有更进一步 通过阅读架构漫谈这几篇文章,我对架构这个名词有了初步的认识。架构,顾名思义,就是结构,正如文中所介绍的那样原始社......