Linux驱动之platform设备驱动

目录

前言

一、Linux驱动的分离与分层

二、开发环境

三、驱动程序编写

3.2 platform 驱动模块程序

3.3 测试app程序

四、运行测试

4.1 编译

4.2 运行测试


前言

        前面几章编写的设备驱动都非常的简单,都是对 IO进行最简单的读写操作。像 I2C、SPI、 LCD 等这些复杂外设的驱动就不能这么去写了, Linux 系统要考虑到驱动的可重用性,因此提出了驱动的分离与分层这样的软件思路,在这个思路下诞生了我们将来最常打交道的platform 设备驱动,也叫做平台设备驱动。本章我们就来学习一下 Linux 下的驱动分离与分层,以及 platform 框架下的设备驱动该如何编写。

一、Linux驱动的分离与分层

Linux驱动的分离与分层在这就不再进行详细讲解了,如果还不了解的,可以去查看之前的文章《Linux驱动之软件架构思想》,链接如下:

Linux驱动之软件架构思想

二、开发环境

  • CPU:IMX6ULL

  • 内核版本:Linux-5.19

三、驱动程序编写

        本次我们需要编写一个驱动模块和一个设备模块,其中驱动模块是 platform 驱动程序,设备模块是 platform 的设备信息。当这两个模块都加载成功以后就会匹配成功,然后 platform驱动模块中的 probe 函数就会执行, probe 函数中就是传统的字符设备驱动那一套。

3.1 platform 设备模块程序

        在 leddevice.c 中输入如下所示内容:

/************************************************************
 * Copyright © toto Co., Ltd. 1998-2029. All rights reserved.
 * Description: 
 * Version: 1.0
 * Autor: toto
 * Date: Do not edit
 * LastEditors: Seven
 * LastEditTime: Do not edit
*************************************************************/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/irq.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>

#define LEDDEV_CNT  1               /* 设备号数量 */
#define LEDDEV_NAME "platform_led"  /* 设备名字 */
#define LED_ON      1
#define LED_OFF     0

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *VM_CCM_CCGR1;
static void __iomem *VM_SW_MUX_GPIO1_IO03;
static void __iomem *VM_SW_PAD_GPIO1_IO03;
static void __iomem *VM_GPIO1_DR;
static void __iomem *VM_GPIO1_GDIR;

/* led_dev 设备结构体 */
struct led_dev {
    dev_t devid;            /* 设备号 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    int major;              /* 主设备号 */
};

struct led_dev leddev; /* led 设备 */

/*
 * @Brief   led 打开、关闭接口
 * @Param   sta:1打开,0关闭
 * @Note    NOne
 * @RetVal  NOne
 */
void led_switch(u8 sta)
{
    u32 val = 0;

    if (sta == LED_ON) {
        val = readl(VM_GPIO1_DR);
        val &= ~(1 << 3);
        writel(val, VM_GPIO1_DR);
    } else if (sta == LED_OFF) {
        val |= (1 << 3);
        writel(val, VM_GPIO1_DR);
    }
}

/*
 * @Brief   打开设备
 * @Param   inode:传递给驱动的inode
 * @Param   filp:设备文件
 * @Note    NOne
 * @RetVal  NOne
 */
static int led_open(struct inode *inode, struct file *filp)
{
    /* 设置私有数据 */
    filp->private_data = &leddev;

    return 0;
}

/*
 * @Brief   向设备写数据
 * @Param   filp:设备文件
 * @Param   buf:要写入设备的数据
 * @Param   cnt:要写入的数据长度
 * @Param   offt:相对于文件首地址的偏移
 * @Note    NOne
 * @RetVal  写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf,
                        size_t cnt, loff_t *offt)
{
    int ret;
    unsigned char databuf[1];
    unsigned char ledstat;

    ret = copy_from_user(databuf, buf, cnt);
    if (ret < 0) {
        return -EFAULT;
    }

    ledstat = databuf[0];
    led_switch(ledstat);

    return 0;
}

/* 设备操作函数 */
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open  = led_open,
    .write = led_write, 
};

/*
 * @Brief   platform 驱动的probe函数,当驱动与设备
            匹配后此函数就会执行
 * @Param   platform 设备
 * @Note    NOne
 * @RetVal  0成功,其他值失败
 */
static int led_probe(struct platform_device *dev)
{
    int i = 0;
    int ressize[5];
    u32 val = 0;
    struct  resource *ledsource[5];
  
    printk(KERN_INFO "led driver and device has matched\n");

    for (i = 0; i < 5; i++) {
        ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
        if (!ledsource[i]) {
            dev_err(&dev->dev, "NO MEM\n");
            return -ENXIO;
        }
        ressize[i] = resource_size(ledsource[i]);
    }

    /* 初始化 led */
    /* 寄存器映射 */
    VM_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
    VM_SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
    VM_SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
    VM_GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
    VM_GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);

    val = readl(VM_CCM_CCGR1);
    val |= (1 << 3);
    writel(val, VM_CCM_CCGR1);

    /* 设置GPIO1_IO03 复用功能 */
    writel(5, VM_SW_MUX_GPIO1_IO03);
    writel(0x10B0, VM_SW_PAD_GPIO1_IO03);

    /* 默认关闭 led */
    val = readl(VM_GPIO1_GDIR);
    val |= (1 << 3);
    writel(val, VM_GPIO1_GDIR);

    /* 注册字符设备驱动 */
    /* 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;
}

/*
 * @Brief   移除 platform 驱动函数
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static int led_remove(struct platform_device *dev)
{
    iounmap(VM_CCM_CCGR1);
    iounmap(VM_SW_MUX_GPIO1_IO03);
    iounmap(VM_SW_PAD_GPIO1_IO03);
    iounmap(VM_GPIO1_DR);
    iounmap(VM_GPIO1_GDIR);

    cdev_del(&leddev.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 = "imx6ull-led", /* 驱动名字,用于匹配设备 */
    },
    .probe = led_probe,
    .remove = led_remove,
};

/*
 * @Brief   驱动模块加载函数
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static int __init leddriver_init(void)
{
    return platform_driver_register(&led_driver);
}

/*
 * @Brief   驱动模块卸载函数
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("toto");
  • 第 56~82 行, led_resources 数组,也就是设备资源,描述了 LED 所要使用到的寄存器信息,也就是 IORESOURCE_MEM 资源。

  • 第 88~96, platform 设备结构体变量 leddevice,这里要注意 name 字段为“imx6ul-led”,所以稍后编写 platform 驱动中的 name 字段也要为“imx6ul-led”,否则设备和驱动匹配失败。

  • 第 103~106 行,设备模块加载函数,在此函数里面通过 platform_device_register 向 Linux 内核注册 leddevice 这个 platform 设备。

  • 第 113~116 行,设备模块卸载函数,在此函数里面通过 platform_device_unregister 从 Linux内核中删除掉 leddevice 这个 platform 设备。

3.2 platform 驱动模块程序

        在 leddriver.c 里面输入如下内容:

/***********************************************************
 * Copyright © toto Co., Ltd. 1998-2029. All rights reserved.
 * Description: 
 * Version: 1.0
 * Autor: toto
 * Date: Do not edit
 * LastEditors: Seven
 * LastEditTime: Do not edit
***********************************************************/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/irq.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>

#define CCM_CCGR1_REGBASE           (0x020C406C)
#define SW_MUX_GPIO1_IO03_REGBASE   (0x020E0068)
#define SW_PAD_GPIO1_IO03_REGBASE   (0x020E02F4)
#define GPIO1_DR_REGBASE            (0x0209C000)
#define GPIO1_GDIR_REGBASE          (0x0209C004)
#define REGISTER_LENGTH             4

/*
 * @Brief   释放platform设备模块的函数
 * @Param   dev:要释放的设备
 * @Note    NOne
 * @RetVal  NOne
 */
static void led_release(struct device *dev)
{
    printk("led device released\n");
}

/*
*设备资源信息,LED使用的所有寄存器
*/
static struct resource led_resource[] = {
    [0] = {
        .start = CCM_CCGR1_REGBASE,
        .end   = (CCM_CCGR1_REGBASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = SW_MUX_GPIO1_IO03_REGBASE,
        .end   = (SW_MUX_GPIO1_IO03_REGBASE + REGISTER_LENGTH -1),
        .flags = IORESOURCE_MEM,
    },
    [2] = {
        .start = SW_PAD_GPIO1_IO03_REGBASE,
        .end   = (SW_PAD_GPIO1_IO03_REGBASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
    [3] = {
        .start = GPIO1_DR_REGBASE,
        .end   = (GPIO1_DR_REGBASE + REGISTER_LENGTH - 1),
        .flags = IORESOURCE_MEM,
    },
    [4] = {
        .start = GPIO1_GDIR_REGBASE,
        .end   = (GPIO1_GDIR_REGBASE + REGISTER_LENGTH -1),
        .flags = IORESOURCE_MEM,
    },
};

/*
 * platform设备结构体
 */
static struct platform_device leddev = {
    .name = "imx6ull-led",
    .id = -1,
    .dev = {
        .release = led_release,
    },
    .num_resources = ARRAY_SIZE(led_resource),
    .resource = led_resource,
};

/*
 * @Brief   设备模块加载
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static int __init leddev_init(void)
{
    return platform_device_register(&leddev);
}

/*
 * @Brief   设备模块注销
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static void __exit leddev_exit(void)
{
    platform_device_unregister(&leddev);
}

module_init(leddev_init);
module_exit(leddev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("toto");
  • 第 34~122 行,传统的字符设备驱动。

  • 第 130~206 行, probe 函数,当设备和驱动匹配以后此函数就会执行,当匹配成功以后会在终端上输出“led driver and device has matched!”这样语句。在 probe 函数里面初始化 LED、注册字符设备驱动。也就是将原来在驱动加载函数里面做的工作全部放到 probe 函数里面完成。

  • 第 213~226 行, remobe 函数,当卸载 platform 驱动的时候此函数就会执行。在此函数里面释放内存、注销字符设备等。也就是将原来驱动卸载函数里面的工作全部都放到 remove 函数中完成。

  • 第 229~235 行, platform_driver 驱动结构体,注意 name 字段为"imx6ul-led",和我们在leddevice.c 文件里面设置的设备 name 字段一致。

  • 第 242~245 行,驱动模块加载函数,在此函数里面通过 platform_driver_register 向 Linux 内核注册 led_driver 驱动。

  • 第 252~255 行,驱动模块卸载函数,在此函数里面通过 platform_driver_unregister 从 Linux内核卸载 led_driver 驱动。

3.3 测试app程序

        在 platform_app.c 里面输入如下内容:

/********************************************
 * Copyright © toto Co., Ltd. 1998-2029. All rights reserved.
 * Description: 
 * Version: 1.0
 * Autor: toto
 * Date: Do not edit
 * LastEditors: Seven
 * LastEditTime: Do not edit
********************************************/
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

#define LEDON   1
#define LEDOFF  0 

/*
 * @Brief
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
int main(int argc, char *argv[])
{
    int fd, retval;
    char *filename;
    unsigned char databuf[1];

    if (argc != 3) {
        printf("Error argc par cnt\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if (fd < 0) {
        printf("file %s open failed\n", filename);
        return -1;
    }

    databuf[0] = atoi(argv[2]);
    retval = write(fd, databuf, sizeof(databuf));
    if (retval < 0) {
        printf("led control failed\n");
        close(fd);
        return -1;
    }

    close(fd);

    return 0;
}

四、运行测试

4.1 编译

1.编译驱动程序 编写 Makefile 文件,Makefile 内容如下所示:

KERNELDIR := /home/toto/workspace/linux/linux-5.19
CURRENT_PATH := $(shell pwd)
obj-m := leddriver.o
obj-m += leddevice.o

build: kernel_modules

kernel_modules:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译命令:

make -j8

编译成功以后就会生成一个名为“leddevice.ko leddriver.ko”的驱动模块文件。

2. 编译测试app 编译命令:

arm-linux-gnueabihf-gcc platform_app.c -o platform_app

编译成功以后就会生成 platform_app 这个应用程序。

4.2 运行测试

        开发板上电,将leddevice.ko、leddriver.ko 和 key_app 这三个文件拷贝到 /lib/modules/5.19.0-g794a2f7be62d-dirty/ 目录中,输入如下命令加载 leddevice.ko 设备模块和 leddriver.ko 这个驱动模块:

insmod leddevice.ko 
insmod leddriver.ko

        根文件系统中/sys/bus/platform/目录下保存着当前板子 platform 总线下的设备和驱动,其中devices 子目录为 platform 设备, drivers 子目录为 plartofm 驱动。查看/sys/bus/platform/devices/目录,看看我们的设备是否存在,我们在 leddevice.c 中设置 leddevice(platform_device 类型)的name 字段为“imx6ull-led”,也就是设备名字为 imx6ull-led,因此肯定在/sys/bus/platform/devices/目录下存在一个名字“imx6ull-led”的文件,否则说明我们的设备模块加载失败,结果如下所示:

        同理,查看/sys/bus/platform/drivers/目录,看一下驱动是否存在,我们在 leddriver.c 中设置led_driver (platform_driver 类型)的 name 字段为“imx6ull-led”,因此会在/sys/bus/platform/drivers/目录下存在名为“imx6ull-led”这个文件,结果如下所示:

        驱动模块和设备模块加载成功以后 platform 总线就会进行匹配,当驱动和设备匹配成功以后就会输出如下所示一行语句:

/lib/modules/5.19.0-g794a2f7be62d-dirty # insmod leddevice.ko
/lib/modules/5.19.0-g794a2f7be62d-dirty # insmod leddriver.ko
[   45.657342] led driver and device has matched

驱动和设备匹配成功以后就可以测试 LED 灯驱动了,输入如下命令打开 LED 灯:

./platform_app /dev/platform_led 1

在输入如下命令关闭 LED 灯:

./platform_app /dev/platform_led 0

卸载驱动命令如下:

rmmod leddevice.ko
rmmod leddriver.ko

        关于更多嵌入式C语言、FreeRTOS、RT-Thread、Linux应用编程、linux驱动等相关知识,关注公众号【嵌入式Linux知识共享】,后续精彩内容及时收看了解。 

猜你喜欢

转载自blog.csdn.net/Wang_XB_3434/article/details/132524810
今日推荐