Linux字符驱动开发-GPIO设备驱动模板(基于设备树及GPIO子系统)

一、.在设备树根节点添加节点信息

    led{
                compatible="myled";
                status="okay";
                default-state="on";

        led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
        };

led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;表示电平为高时,点亮LED。
二、基于gpio子系统编写LED驱动
在linux下一切皆文件,因此我们编写的驱动也是以文件形式挂载在linux的根文件系统中的,
驱动的编写最基础的操作函数包括:设备打开、设备释放、设备读、设备写
这里以正点原子官方的驱动代码为例:

点击查看代码
/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 文件名    : gpioled.c
 作者      : 邓涛
 版本      : V1.0
 描述      : ZYNQ LED驱动文件。
 其他      : 无
 论坛      : www.openedv.com
 日志      : 初版V1.0 2019/1/30 邓涛创建
 ***************************************************************/

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>

#define GPIOLED_CNT		1			/* 设备号个数 */
#define GPIOLED_NAME	"gpioled"		/* 名字 */

/* dtsled设备结构体 */
struct gpioled_dev {
    dev_t devid;			/* 设备号 */
    struct cdev cdev;		/* cdev */
    struct class *class;		/* 类 */
    struct device *device;	/* 设备 */
    int major;				/* 主设备号 */
    int minor;			/* 次设备号 */
    struct device_node *nd;	/* 设备节点 */
    int led_gpio;			/* LED所使用的GPIO编号 */
};

static struct gpioled_dev gpioled;  /* led设备 */

/*
 * @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 = &gpioled;  /* 设置私有数据 */
    return 0;
}

/*
 * @description			: 从设备读取数据 
 * @param – filp			: 要打开的设备文件(文件描述符)
 * @param – buf			: 返回给用户空间的数据缓冲区
 * @param – cnt			: 要读取的数据长度
 * @param – offt			: 相对于文件首地址的偏移
 * @return				: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t led_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 led_write(struct file *filp, const char __user *buf,
            size_t cnt, loff_t *offt)
{
    int ret;
    char kern_buf[1];

    ret = copy_from_user(kern_buf, buf, cnt);   // 得到应用层传递过来的数据
    if(0 > ret) {
        printk(KERN_ERR "kernel write failed!\r\n");
        return -EFAULT;
    }

    if (0 == kern_buf[0])
        gpio_set_value(gpioled.led_gpio, 0); // 如果传递过来的数据是0则关闭led
    else if (1 == kern_buf[0])
        gpio_set_value(gpioled.led_gpio, 1); // 如果传递过来的数据是1则点亮led

    return 0;
}

/*
 * @description			: 关闭/释放设备
 * @param – filp			: 要关闭的设备文件(文件描述符)
 * @return				: 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 设备操作函数 */
static struct file_operations gpioled_fops = {
    .owner   = THIS_MODULE,
    .open    = led_open,
    .read    = led_read,
    .write   = led_write,
    .release = led_release,
};

static int __init led_init(void)
{
    const char *str;
    int ret;

    /* 1.获取led设备节点 */
    gpioled.nd = of_find_node_by_path("/led");
    if(NULL == gpioled.nd) {
        printk(KERN_ERR "gpioled: Failed to get /led node\n");
        return -EINVAL;
    }

    /* 2.读取status属性 */
    ret = of_property_read_string(gpioled.nd, "status", &str);
    if(!ret) {
        if (strcmp(str, "okay"))
            return -EINVAL;
    }

    /* 2、获取compatible属性值并进行匹配 */
    ret = of_property_read_string(gpioled.nd, "compatible", &str);
    if(0 > ret) {
        printk(KERN_ERR "gpioled: Failed to get compatible property\n");
        return ret;
    }

    if (strcmp(str, "alientek,led")) {
        printk(KERN_ERR "gpioled: Compatible match failed\n");
        return -EINVAL;
    }

    printk(KERN_INFO "gpioled: device matching successful!\r\n");

    /* 4.获取设备树中的led-gpio属性,得到LED所使用的GPIO编号 */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    if(!gpio_is_valid(gpioled.led_gpio)) {
        printk(KERN_ERR "gpioled: Failed to get led-gpio\n");
        return -EINVAL;
    }

    printk(KERN_INFO "gpioled: led-gpio num = %d\r\n", gpioled.led_gpio);

    /* 5.向gpio子系统申请使用GPIO */
    ret = gpio_request(gpioled.led_gpio, "LED-GPIO");
    if (ret) {
        printk(KERN_ERR "gpioled: Failed to request led-gpio\n");
        return ret;
    }

    /* 6.将led gpio管脚设置为输出模式 */
    gpio_direction_output(gpioled.led_gpio, 0);

    /* 7.初始化LED的默认状态 */
    ret = of_property_read_string(gpioled.nd, "default-state", &str);
    if(!ret) {
        if (!strcmp(str, "on"))
            gpio_set_value(gpioled.led_gpio, 1);
        else
            gpio_set_value(gpioled.led_gpio, 0);
    } else
        gpio_set_value(gpioled.led_gpio, 0);

    /* 8.注册字符设备驱动 */
     /* 创建设备号 */
    if (gpioled.major) {
        gpioled.devid = MKDEV(gpioled.major, 0);
        ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
        if (ret)
            goto out1;
    } else {
        ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
        if (ret)
            goto out1;

        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }

    printk("gpioled: major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);

     /* 初始化cdev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);

     /* 添加一个cdev */
    ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    if (ret)
        goto out2;

     /* 创建类 */
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class)) {
        ret = PTR_ERR(gpioled.class);
        goto out3;
    }

     /* 创建设备 */
    gpioled.device = device_create(gpioled.class, NULL,
                gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device)) {
        ret = PTR_ERR(gpioled.device);
        goto out4;
    }

    return 0;

out4:
    class_destroy(gpioled.class);

out3:
    cdev_del(&gpioled.cdev);

out2:
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);

out1:
    gpio_free(gpioled.led_gpio);

    return ret;
}

static void __exit led_exit(void)
{
    /* 注销设备 */
    device_destroy(gpioled.class, gpioled.devid);

    /* 注销类 */
    class_destroy(gpioled.class);

    /* 删除cdev */
    cdev_del(&gpioled.cdev);

    /* 注销设备号 */
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);

    /* 释放GPIO */
    gpio_free(gpioled.led_gpio);
}

/* 驱动模块入口和出口函数注册 */
module_init(led_init);
module_exit(led_exit);

MODULE_AUTHOR("DengTao <773904075@qq.com>");
MODULE_DESCRIPTION("Alientek ZYNQ GPIO LED Driver");
MODULE_LICENSE("GPL");

三、编译驱动代码,生成驱动文件
对于ZYNQ,可以借助petalinux工具新建驱动:
petalinux-create -t modules --name MY_LED //建立一个名为MY_LED的驱动,
随后在工程的project-spec/meta-user/recipes-modules的目录下可以找到该驱动目录,将上述的驱动代码copy至驱动目录下files/xxx.c
最后使用petalinux-build即可。
对于一般的linux系统,需要撰写makefile,用linux的源码编译该驱动;

KERN_DIR :=/home/zy/workspace/kernel-driver/linux-xlnx-xlnx_rebase_v5.4_2020.2

obj-m :=gpioled.o

all:
	make -C $(KERN_DIR) M=`pwd` modules
clean:
	make -C $(KERN_DIR) M=`pwd` clean

使用make命令对驱动源文件进行编译,生成.ko驱动文件。
四、编写驱动测试APP
这一步主要用于测试我们开发的驱动是否能够实现预定的功能,实现用户空间与内核空间的数据交换。
使用$CC ledApp .c -o ledApp 命令编译APP。

点击查看代码
/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 文件名                 : led-app.c
 作者                   : 邓涛
 版本                   : V1.0
 描述                   : LED驱测试APP。
 其他                   : 无
 使用方法               : ./ledApp /dev/led 0 关闭LED
                           ./ledApp /dev/led 1 打开LED       
 论坛                   : www.openedv.com
 日志                   : 初版V1.0 2019/1/30 邓涛创建
 ***************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

/*
 * @description         : main主程序
 * @param - argc        : argv数组元素个数
 * @param - argv        : 具体参数
 * @return              : 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
    int fd, ret;
    unsigned char buf[1];

    if(3 != argc) {
        printf("Usage:\n"
               "\t./ledApp /dev/led 1          @ close LED\n"
               "\t./ledApp /dev/led 0          @ open LED\n"
               );
        return -1;
    }

    /* 打开设备 */
    fd = open(argv[1], O_RDWR);
    if(0 > fd) {
        printf("file %s open failed!\r\n", argv[1]);
        return -1;
    }

    /* 将字符串转换为int型数据 */
    buf[0] = atoi(argv[2]);

    /* 向驱动写入数据 */
    ret = write(fd, buf, sizeof(buf));
    if(0 > ret){
        printf("LED Control Failed!\r\n");
        close(fd);
        return -1;
    }

    /* 关闭设备 */
    close(fd);
    return 0;
}

五、挂载驱动
需要在linux中挂载我们编写的驱动,这里可以参考我的另一篇博文《Petalinux ARM设置自启动、自动加载驱动、自动挂载SD卡》
https://www.cnblogs.com/dy-stairmed/p/18650187

posted @   羊的第七章  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体
点击右上角即可分享
微信分享提示