• <Linux开发>驱动开发 -之-基于pinctrl/gpio子系统的beep驱动


    Linux开发>驱动开发 -之-基于pinctrl/gpio子系统的beep驱动

    交叉编译环境搭建:
    <Linux开发> linux开发工具-之-交叉编译环境搭建

    uboot移植可参考以下:
    <Linux开发> -之-系统移植 uboot移植过程详细记录(第一部分)
    <Linux开发> -之-系统移植 uboot移植过程详细记录(第二部分)
    <Linux开发> -之-系统移植 uboot移植过程详细记录(第三部分)(uboot移植完结)

    Linux内核及设备树移植可参考以下:
    <Linux开发>系统移植 -之- linux内核移植过程详细记录(第一部分)
    <Linux开发>系统移植 -之- linux内核移植过程详细记录(第二部分完结)

    Linux文件系统构建移植参考以下:
    <Linux开发>系统移植 -之- linux构建BusyBox根文件系统及移植过程详细记录
    <Linux开发>系统移植 -之-使用buildroot构建BusyBox根文件系统

    Linux驱动开发参考以下:
    <Linux开发>驱动开发 -之-pinctrl子系统
    <Linux开发>驱动开发 -之-gpio子系统
    <Linux开发>驱动开发 -之-基于pinctrl/gpio子系统的LED驱动

    以下实验基于上述uboot和kernel系统移植,以及buildroot构建的BusyBox根文件系统。

    一、前言

    本文主要讲解基于pinctrl子系统和gpio子系统的前提下,编写beep驱动,并编写测试app测试beep的控制。

    二、新增beep设备节点

    参考-<Linux开发>驱动开发 -之-基于pinctrl/gpio子系统的LED驱动的讲解,笔者使用的开发板上beep连接的是SNVS_TAMPER1这个io引脚,如下图:
    在这里插入图片描述

    在这里插入图片描述

    可以在设备树中添加如下内容;
    pinctrl节点添加如下:

    路径:arch/arm/boot/dts/imx6ull-water-emmc.dts
    pinctrl_beep: beepgrp {
    			fsl,pins =01
    				MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10B0
    			>;
    		};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    可以看到pinctrl_beep节点是在iomuxc节点下的。

    beep设备节点添加如下:

    路径:arch/arm/boot/dts/imx6ull-water-emmc.dts
    beep{
    		#address-cells = <1>;
    		#size-cells = <1>;
    		compatible = "water-beep";
    		pinctrl-names = "default";
    		pinctrl-0 = <&pinctrl_beep>;
    		beep-gpio= <&gpio5 1 GPIO_ACTIVE_HIGH>;
    		status = "okay";
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    注意各属性命名,后续beep驱动中会使用对应的属性名。。。。。

    新增beep 节点和gpioled节点一样都是在/节点下的。

    添加完上述两个节点后,重新编译设备树“make dtbs”,然后用新生成的dtb文件启动kernel,在设备的/sys/firmware/devicetree/base/目录下有如下:
    在这里插入图片描述
    在这里插入图片描述

    新增beep节点完成,后面编写驱动和app来测试验证这个beep设备。

    三、编写驱动

    3.1 编写led驱动 模块挂载

    编译单独的ko文件,然后动态挂载设备。
    使用vscode新建一个工程;
    (1)新建目录beep
    在这里插入图片描述
    (2)使用vscode打开beep目录
    在这里插入图片描述
    (3)新建beep.c
    在这里插入图片描述
    (4) 在beep.c中输入驱动代码

    /***************************************************************
    Copyright © OneFu Co., Ltd. 2018-2023. All rights reserved.
    文件名 : beep.c
    作者 : water
    版本 : V1.0
    描述 : 采用 pinctrl 和 gpio 子系统驱动 beep蜂鸣器。
    其他 : 无
    日志 : 初版 V1.0 2023/05/28 water创建
    ***************************************************************/
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define BEEP_CNT 1              /* 设备号个数 */
    #define BEEP_NAME "beep"        /* 名字 */
    #define BEEPOFF 0                /* 关beep */
    #define BEEPON 1                 /* 开beeo */
    
    /* beep 设备结构体 */
    struct beep_dev{
        dev_t devid;            /* 设备号 */
        struct cdev cdev;       /* cdev */
        struct class *class;    /* 类 */
        struct device *device;  /* 设备 */
        int major;              /* 主设备号 */
        int minor;              /* 次设备号 */
        struct device_node *nd; /* 设备节点 */
        int beep_gpio;           /* beep 所使用的 GPIO 编号 */
    };
     
    struct beep_dev beep; /* beep 设备 */
    
    /*
    * @description : 打开设备
    * @param – inode : 传递给驱动的 inode
    * @param – filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
    * 一般在 open 的时候将 private_data 指向设备结构体。
    * @return : 0 成功;其他 失败
    */
    static int beep_open(struct inode *inode, struct file *filp)
    {
        filp->private_data = &beep; /* 设置私有数据 */
        return 0;
    }
    
    /*
    * @description : 从设备读取数据
    * @param – filp : 要打开的设备文件(文件描述符)
    * @param - buf : 返回给用户空间的数据缓冲区
    * @param - cnt : 要读取的数据长度
    * @param – offt : 相对于文件首地址的偏移
    * @return : 读取的字节数,如果为负值,表示读取失败
    */
    static ssize_t beep_read(struct file *filp, char __user *buf,
                            size_t cnt, loff_t *offt)
    {
        return 0;
    }
     
    /*
    * @description : 向设备写数据
    * @param - filp : 设备文件,表示打开的文件描述符
    * @param - buf : 要写给设备写入的数据
    * @param - cnt : 要写入的数据长度
    * @param – offt : 相对于文件首地址的偏移
    * @return : 写入的字节数,如果为负值,表示写入失败
    */
    static ssize_t beep_write(struct file *filp, const char __user *buf,
                                size_t cnt, loff_t *offt)
    {
        int retvalue;
        unsigned char databuf[1];
        unsigned char beepstat;
        struct beep_dev *dev = filp->private_data;
    
        retvalue = copy_from_user(databuf, buf, cnt);
        if(retvalue < 0) {
            printk("kernel write failed!\r\n");
            return -EFAULT;
        }
    
        beepstat = databuf[0]; /* 获取状态值 */
    
        if(beepstat == BEEPON) { 
            gpio_set_value(dev->beep_gpio, 0); /* 打开 beep */
        } else if(beepstat == BEEPOFF) {
            gpio_set_value(dev->beep_gpio, 1); /* 关闭 beep */
        }
        return 0;
    }
    
    /*
    * @description : 关闭/释放设备
    * @param – filp : 要关闭的设备文件(文件描述符)
    * @return : 0 成功;其他 失败
    */
    static int beep_release(struct inode *inode, struct file *filp)
    {
        return 0;
    }
    
    /* 设备操作函数 */
    static struct file_operations beep_fops = {
        .owner = THIS_MODULE,
        .open = beep_open,
        .read = beep_read,
        .write = beep_write,
        .release = beep_release,
    };
    
    /*
    * @description : 驱动入口函数
    * @param : 无
    * @return : 无
    */
    static int __init water_beep_init(void)
    {
        int ret = 0;
    
        /* 设置 beep 所使用的 GPIO */
        /* 1、获取设备节点: beep */
        beep.nd = of_find_node_by_path("/beep");
        if(beep.nd == NULL) {
            printk("beep node cant not found!\r\n");
            return -EINVAL;
        } else {
            printk("beep node has been found!\r\n");
        }
    
        /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
        beep.beep_gpio = of_get_named_gpio(beep.nd, "beep-gpio", 0);
        if(beep.beep_gpio < 0) {
            printk("can't get beep-gpio");
            return -EINVAL;
        }
        printk("beep num = %d\r\n", beep.beep_gpio);
    
        /* 3、设置 GPIO5_IO01 为输出,并且输出高电平,默认关闭 beep */
        ret = gpio_direction_output(beep.beep_gpio, 1);
        if(ret < 0) {
            printk("can't set gpio!\r\n");
        }
    
        /* 注册字符设备驱动 */
        /* 1、创建设备号 */
        if (beep.major) { /* 定义了设备号 */
            beep.devid = MKDEV(beep.major, 0);
            register_chrdev_region(beep.devid, BEEP_CNT,
                                        BEEP_NAME);
        } else { /* 没有定义设备号 */
            alloc_chrdev_region(&beep.devid, 0, BEEP_CNT, 
                                        BEEP_NAME); /* 申请设备号 */
            beep.major = MAJOR(beep.devid); /* 获取分配号的主设备号 */
            beep.minor = MINOR(beep.devid); /* 获取分配号的次设备号 */
        }
        printk("beep major=%d,minor=%d\r\n",beep.major,
                                        beep.minor); 
    
        /* 2、初始化 cdev */
        beep.cdev.owner = THIS_MODULE;
        cdev_init(&beep.cdev, &beep_fops);
    
        /* 3、添加一个 cdev */
        cdev_add(&beep.cdev, beep.devid, BEEP_CNT);
    
        /* 4、创建类 */
        beep.class = class_create(THIS_MODULE, BEEP_NAME);
        if (IS_ERR(beep.class)) {
        return PTR_ERR(beep.class);
        }
    
        /* 5、创建设备 */
        beep.device = device_create(beep.class, NULL,
                                beep.devid, NULL, BEEP_NAME);
        if (IS_ERR(beep.device)) {
            return PTR_ERR(beep.device);
        }
        return 0;
    }
    
    /*
    * @description : 驱动出口函数
    * @param : 无
    * @return : 无
    */
    static void __exit water_beep_exit(void)
    {
        /* 注销字符设备驱动 */
        cdev_del(&beep.cdev); /* 删除 cdev */
        unregister_chrdev_region(beep.devid, BEEP_CNT); /* 注销 */
    
        device_destroy(beep.class, beep.devid);
        class_destroy(beep.class);
    }
    
    module_init(water_beep_init);
    module_exit(water_beep_exit);
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("water");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212

    (5) 配置linux头文件
    同时按下ctrl+shitf+P,显示如下:
    在这里插入图片描述
    选择“c/c++:编辑配置(JSON)”,添加linux的头文件,注意是绝对路径,内容如下:

     "/home/water/imax/NXP/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga/include",
                    "/home/water/imax/NXP/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include",
                    "/home/water/imax/NXP/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include/generated/"
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    (6) 编写Makefile

    ARCH=arm 
    CROSS_COMPILE=arm-linux-gnueabihf-
    
    KERNELDIR := /home/water/imax/NXP/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga/
    CURRENT_PATH := $(shell pwd)
    obj-m := beep.o
    
    KBUILD_CFLAGS += -fno-pie
    
    build: kernel_modules
    
    kernel_modules:
    	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
    clean:
    	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述
    (7) 编译

    make
    
    • 1

    编译成功如下:
    在这里插入图片描述
    在这里插入图片描述

    出现一下错误,,新增:

    ARCH=arm 
    CROSS_COMPILE=arm-linux-gnueabihf-
    KBUILD_CFLAGS += -fno-pie
    
    • 1
    • 2
    • 3

    在这里插入图片描述 出现以下错误,需将kernel内核顶级目录的Makefile下修改下面句子:
    在这里插入图片描述
    路径:/home/water/imax/NXP/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga/Makefile.
    读者更具自己的kernel路径修改…
    原:

    ARCH		?= $(SUBARCH)
    CROSS_COMPILE	?= $(CONFIG_CROSS_COMPILE:"%"=%)
    
    • 1
    • 2

    改:

     ARCH		?= arm
    CROSS_COMPILE	?= arm-linux-gnueabihf-
    
    • 1
    • 2

    (8) 放到设备中挂载
    在文件系统中新建文件夹,如下:

    mkdir -p /home/water/imax/nfs/buildrootfs/lib/modules/4.1.15+
    
    • 1

    将前面编译的ko驱动文件放到4.1.15+目录

    cp beep.ko  /home/water/imax/nfs/buildrootfs/lib/modules/4.1.15+
    
    • 1

    在这里插入图片描述

    使用depmod命令提示下列错误:
    在这里插入图片描述
    原因:作者使用buildroot构建busybox时没有是能depmod,
    解决方法:重新buildroot构建busybox使能depmod.
    buildroot构建参考:<Linux开发>系统移植 -之-使用buildroot构建BusyBox根文件系统
    进入buildroot目录下,运行:
    sudo make busybox-menuconfig 在这里插入图片描述
    在这里插入图片描述
    重新编译:

    sudo make FORCE_UNSAFE_CONFIGURE=1
    
    • 1

    替换原根文件系统:(记得先保留/root/water_soft/,否则会被删掉 之前开发的内容)

    sudo rm -r buildrootfs
    mkdir  buildrootfs
    cd buildrootfs
    cp ../../NXP/buildroot/buildroot-2020.05/output/images/rootfs.tar  .
    tar -vxf rootfs.tar
    sudo rm rootfs.tar
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    重新运行depmod
    在这里插入图片描述
    又发生错误,我们新建这个/lib/modules/4.1.15+目录即可:

    mkdir -p  /libmodules/4.1.15+
    
    • 1

    在这里插入图片描述
    新曾的beep.ko文件需要放到/lib/modules/4.1.15+目录下,然后在/lib/modules/4.1.15+下在运行:

    depmod
    
    • 1

    运行之后在同级目录下的 modules.dep文件会有文件记录,如下:

    # cat modules.dep 
    beep.ko:
    
    • 1
    • 2

    挂载驱动:

    modprobe beep.ko 
    
    • 1

    在这里插入图片描述
    上述就是动态挂载的过程,正常挂载beep设备,并获取了对应的设备号等信息。

    3.2 beep驱动并入内核一起编译

    由于kernel没有专门针对beep的驱动,那么我们讲究将beep设备放到led的驱动目录下吧。
    新建文件:drivers/leds/beep-water.c
    在beep-water.c输入以下内容:

    驱动内容与3.1节的驱动内容保持一致。
    
    • 1

    3.3 添加makefile编译项

    在drivers/leds/leds-water.c同级目录下找到Makefile文件,添加一下内容:

    路径:drivers/leds/Makefile
    obj-$(CONFIG_BEEP_WATER)			+= beep-water.o
    
    • 1
    • 2

    在这里插入图片描述

    3.4 添加menuconfig选项

    在3.3节中,我们把驱动添加进Kernel了,那怎么样才能编译这个驱动呢?我们添加的Makefile内容中有CONFIG_BEEP_WATER 这个宏,只要这个宏为true那么就会编译beep-water.o所对于的beep-water.c文件了。
    在Kconfig文件末尾添加如下内容:

    路径:drivers/leds/Kconfig
    config BEEP_WATER
    	tristate "beep support for water imax6ull board"
    	help
    	  This option enables support for the imax water beep.
    
    • 1
    • 2
    • 3
    • 4
    • 5

    添加完上述内容保存后,就可以通过“make menuconfig”来配置使用这个led驱动了。

    3.5 使用menuconfig配置驱动

    在kernel跟目录运行“make menuconfig”后,如下步骤配置;
    (1) 选择 “Device Drivers —>”
    在这里插入图片描述
    (2) 选择 “LED Support —>”
    在这里插入图片描述
    (3) 选择 “beep Support for water imax6ull board —>”
    在这里插入图片描述

    选中之后按“Y”,在行前的尖括号里会出现*,表示编译进内核;然后用键盘左右键选择"Save",保存配置;保存的路径使用默认的路径,如下,然后OK即可。
    在这里插入图片描述
    配置完驱动编译,重新编译kernel,然后使用新的kernel启动。

    四、编写测试beep的app

    在ubuntu下新建目录:/home/water/imax/soft/beep,并新建文件beep_water_app.c,然后输入一下内容:

    #include "stdio.h"
    #include "unistd.h"
    #include "sys/types.h"
    #include "sys/stat.h"
    #include "fcntl.h"
    #include "stdlib.h"
    #include "string.h"
    
    /*************************************************************** 
     * Copyright © onefu Co., Ltd. 2019-2023. All rights reserved. 
     * 文件名 : beep_water_app.c 
     * 作者 : water 
     * 版本 : V1.0 
     * 描述 : beep 驱测试APP。 
     * 其他 : 使用方法:./beep_water_app /dev/beep <1>|<2>
     *                argv[2] 0:关闭beep
     *                argv[2] 1:打开beep
     * 日志 : 初版V1.0 2023/05/28 water创建 
     * ***************************************************************/ 
    
    #define BEEPOFF 0
    #define BEEPON  1
    
    /* 
    * @description : main主程序 
    * @param - argc : argv数组元素个数 
    * @param - argv : 具体参数 
    * @return : 0 成功;其他 失败 
    */ 
    int main(int argc, char *argv[])
    {
        int fd, retvalue;               //fd: 文件描述符 用以对文件操作    retvalue:存放函数操作后的返回值
        char *filename;                 //filename:文件名,有主函数参数传入赋值
        unsigned char databuf[1];       //定义的buf,用来读写数据用 
    
        if(argc != 3){                  //判断主函数传入的函数的参数的个数
            printf("Error Usage!\r\n");
            return -1;
        }
    
        filename = argv[1];              //获取第1个参数,存放的是文件的路径(即要操作的设备文件路径)
        
        fd = open(filename,O_RDWR);                         /*打开驱动文件*/
        if(fd < 0){
            printf("Can't open file %s\r\n",filename);      /*打开失败,输出提示*/
            return -1;
        }
    
        databuf[0] = atoi(argv[2]);                         /* 要执行的操作:打开或关闭 */
    
        retvalue = write(fd, databuf, sizeof(databuf));     /*向设备驱动写入数据*/
        if(retvalue < 0){
            printf("BEEP Control Failed!\r\n",filename);     /*写入错误输出提示*/
        }
    
        retvalue = close(fd);                               /*关闭文件*/
        if(retvalue < 0){
            printf("Can't close file %s\r\n",filename);     /*关闭错误输出提示*/
            return -1;
        }
    
        return 0;
    }
    //编译指令: arm-linux-gnueabihf-gcc  beep_water_app.c  -o  beep_water_app
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    编译app:

     arm-linux-gnueabihf-gcc  beep_water_app.c  -o  beep_water_app
    
    • 1

    在这里插入图片描述

    五、运行测试

    将编译生成的 beep_water_app 执行文件拷贝到文件系统的/root/water_soft/beep/目录下:
    在这里插入图片描述

    在Securecrt终端运行如下命令:

     cd /root/water_soft/beep/
    ./beep_water_app  /dev/beep  1
    
    • 1
    • 2

    在这里插入图片描述

    运行之后,即可观看开发板beep状态为开。
    如果是./beep_water_app /dev/beep 0则beep为关。
    作者实际现象是正常开关的,读者可以自行测试自己的开发板。

    六、总结

    我们结合pinctrl子系统和gpio子系统,完成了beep的驱动开发,开发beep蜂鸣器的驱动,其原理与led是一样的,都是通过控制gpio引脚输出高低电平来控制设备的。

    先通过控制简单的led、beep等设备,熟悉掌握pinctrl子系统和gpio子系统,后面我们在深入开发比较复杂的驱动。

    一步一脚印,法路自然成。

  • 相关阅读:
    极智Paper | 单级特征检测网络 YOLOF
    介绍一款数据准实时复制(CDC)中间件 `Debezium`
    NIM游戏,模板题
    “论大数据处理架构及其应用”写作框架,软考高级,系统架构设计师
    【学习】一致性哈希与哈希环
    Python实现print输出至日志文件
    xgboost early_stop_rounds是如何生效的?
    6.1 Hive学习总结
    stm32HAL_GPIO输入
    1.QML Hello world
  • 原文地址:https://blog.csdn.net/qq_39257814/article/details/130893740