i.MX8QM GPIO 驱动编写(LED)

关键字 :NXPimx8qmGPIOLED
一、概述
i.MX8QM NXP 开发板有 8 组 GPIO 每组 GPIO 32 bit,本文使用 i.MX8QM NXP 开发板的 GPIO1, 在 i.MX8QM 上实现 GPIO 控制配置,在应用层通过 APP 控制 LED 的亮灭。本次控制
LVDS 的 LED 指示灯,使用 platform 框架,注册字符设备,platform 框架用来匹配设备树,驱动可以通过 of 函数获取设备树参数,字符设备给用户层提供函数接口,可以用 open 函数打开设备,用 read 和 write 函数读写数据,与驱动内核进行联系,从而控制 GPIO 电平变化,软件框架图如下所示:

LVDS 的 led 灯连接脚为 LVDS0_BL_PWM,连接 i.mx8qm 的 GPIO1_04 引脚,如下图所示:




图片来源:取自 
 NXP 官网 imx8qm 原理图

 
二、GPIO 驱动编写

1. 模块入口 / 出口

如下代码, module_init(gpio_train_init) 指示驱动入口函数为 gpio_train_init h函数。module_exit (gpio_train_exit) 指示驱动出口函数为 gpio_train_exit 函数。__init  是告知编译器,将变量或函数放在一个特殊的区域,这个区域定义在 vmlinux.lds 中。__init 将函数放在代码段的一个子段“.init.text”(初始化代码段)中,_exit 标记的函数只有对模块才起作用,是指明函数是放在代码段的“.exit.text”中。

在入口函数中使用 platform_driver_register 进行 platform 驱动注册,在出口函数中使用  platform_driver_unregister 函数进行 platform 驱动注销。MODULE_LICENSE 声明模块许可证,通常使用 GPL 即可。

static int __init gpio_train_init(void)

{

    return platform_driver_register(&gpio_driver);

}

static void __exit gpio_train_exit(void)

{

    platform_driver_unregister(&gpio_driver);

}

module_init(gpio_train_init);

module_exit(gpio_train_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Andrew");

 

2. Platform 驱动框架

结构体定义如下代码所示。.driver 配置了驱动名称和 compatible 内容,.of_math_table 是模块匹配表,其赋值为 of_device_id 类型数组变量,数组 of_device_id 最后一行为空,.compatible 表示匹配属性,当设备树节点的 compatible 内容属性与此驱动匹配表内容一样时,表示驱动可以使用此设备节点的定义属性,并调用 probe 函数进一步初始化驱动。.probe 表示设备驱动于设备树成功匹配后的调用函数,.remove 表示设备卸载时的调用函数。

static const struct of_device_id gpiotrain_of_mach[] =

{

    {.compatible = "gpio-trainled"},

    {},

};

static struct platform_driver gpio_driver ={

    .driver = {

        .name = "gpio-train",

        .of_match_table = gpiotrain_of_mach,

    },

    .probe = gpio_probe,

    .remove = gpio_remove,

};

 

2.2 probe 函数

驱动 probe 函数为 gpio_probe,此函数包含字符设备初始化(1)和 gpio 的初始化(2),字符设备和 GPIO 框架下文分析,具体代码如下所示:

static int gpio_probe(struct platform_device *dev)

{

    // int ret;

    // struct pwm_state state;

    // /*创建设备号*/

    if(gpiodev.major)                                           (1)

    {

        gpiodev.devid = MKDEV(gpiodev.major,0);

        register_chrdev_region(gpiodev.devid, 1 , "gpio_train");

    }

    else

    {

        alloc_chrdev_region(&gpiodev.devid, 0 , 1 , "gpio_train");

        gpiodev.major = MKDEV(gpiodev.devid,0);

    }

 

    /*初始化 cdev*/

    gpiodev.cdev.owner = THIS_MODULE;

    cdev_init(&gpiodev.cdev,&gpiodev_fops);

    /*添加一个 cdev*/

    cdev_add(&gpiodev.cdev,gpiodev.devid, 1);

    /*创建类*/

    gpiodev.class = class_create(THIS_MODULE,"gpio_train");

    if (IS_ERR(gpiodev.class)){

        return PTR_ERR(gpiodev.class);

    }

    /*创建设备*/

    gpiodev.device = device_create(gpiodev.class, NULL, gpiodev.devid, NULL, "gpio_train");

    if(IS_ERR(gpiodev.device)){

        return PTR_ERR(gpiodev.device);

    }

    gpiodev.node = of_find_node_by_path("/gpio_train");             (2)

    if(gpiodev.node == NULL)

    {

        printk("gpio-train node nost find!\r\n");

        return -EINVAL;

    }

    gpiodev.led0 = of_get_named_gpio(gpiodev.node,"gpio", 0);

    printk("of gpio_train gpio: %d\r\n",gpiodev.led0);

    if(gpiodev.led0 < 0)

    {

        printk("can't get led-gpio \r\n");

        return -EINVAL;

    }

    gpio_request(gpiodev.led0,"led0");

    gpio_direction_output(gpiodev.led0, 1);

    printk("gpio_train probe is over!");

    return 0;

}

 

2.3 remove 函数

驱动 remove 函数为 gpio_remove,此函数用于注销和删除 probe 函数所注册的内容,主要是字符设备的注销和 GPIO 的设置,具体代码如下所示:

static int gpio_remove(struct platform_device *dev)

{

    gpio_set_value(gpiodev.led0, 1);

 

    cdev_del(&gpiodev.cdev);

    unregister_chrdev_region(gpiodev.devid, 1);

    device_destroy(gpiodev.class, gpiodev.devid);

    class_destroy(gpiodev.class);

    return 0;

}

 

3. 字符设备驱动

注册字符设备框架,主要是为了使用字符设备的 write 函数,在用户层调用 write 可以向驱动函数 write 传值,驱动函数根据从用户层接收到的数据,进行控制 GPIO 电平变化。

3.1 字符设备创建和注销

① 设备出入口函数:module_init(init 函数),module_exit(exit 函数):

module_init(gpio_train_init);

module_exit(gpio_train_exit);

 

② 设备许可函数,MODULE_LICENSE ( "GPL" ):

MODULE_LICENSE("GPL");

 

③ 设备结构体变量,包含不限于 dev_t,struct cdev,struct class *class,struct device *device,int major:

//设备结构体

struct gpiodev_dev

{

    dev_t devid;

    struct cdev cdev;

    struct class *class;

    struct device *device;

    struct device_node *node;

    struct pwm_device *pwm;

    int major;

    int led0;

};

 

④ 设备接口函数及其结构体变量,static struct file_operations gpiodev_fops,包含但不限于:

static struct file_operations gpiodev_fops = {

    .owner = THIS_MODULE,

    .open = gpio_open,

    .write = gpio_write,

    .read = gpio_read,

};

  

⑤ 创建设备号,alloc_chrdev_region 获取设备号,MKDEV 函数设备号转 dev_t:

alloc_chrdev_region(&gpiodev.devid, 0 , 1 , "gpio_train");

        gpiodev.major = MKDEV(gpiodev.devid,0);

 

⑥ 初始化 cdev:

           /*初始化 cdev*/

    gpiodev.cdev.owner = THIS_MODULE;

    cdev_init(&gpiodev.cdev,&gpiodev_fops);

 

⑦ 添加 cdev:

           /*添加一个 cdev*/

    cdev_add(&gpiodev.cdev,gpiodev.devid, 1);

 

⑧ 创建类:

    /*创建类*/

    gpiodev.class = class_create(THIS_MODULE,"gpio_train");

    if (IS_ERR(gpiodev.class)){

        return PTR_ERR(gpiodev.class);

    }

 

⑨ 创建设备:

       /*创建设备*/

    gpiodev.device = device_create(gpiodev.class, NULL, gpiodev.devid, NULL, "gpio_train");

    if(IS_ERR(gpiodev.device)){

        return PTR_ERR(gpiodev.device);

    }

 

⑩ 注销:

    cdev_del(&gpiodev.cdev);

    unregister_chrdev_region(gpiodev.devid, 1);

    device_destroy(gpiodev.class, gpiodev.devid);

    class_destroy(gpiodev.class);

 

3.2 write 函数

write 函数用于控制 GPIO 电平变化,当应用层使用 write 函数时,对应驱动此函数。

static ssize_t gpio_write(struct file *file, const char __user *buff, size_t cnt, loff_t *offt)

{  

    int flag;

    unsigned char writbuff[2];

    unsigned char state;

 

    flag = copy_from_user(writbuff, buff, cnt);

    if(flag < 0)

    {

        printk("write fail\r\n");

        return -EFAULT;

    }

   

    state = writbuff[0];

    printk("receipt data is: %c\r\n",state);

    if(state == 1)

    {

        gpio_set_value(gpiodev.led0, 0);

        printk("gpio_set_value data is: 0");

    }

    else if(state == 0)

    {   gpio_set_value(gpiodev.led0, 1);

        printk("gpio_set_value data is: 1");

    }

    return 0;

}

 

copy_from_user 接收来自用户空间的信息,gpio_set_value 设置 GPIO 管脚的输出电平,当接收到用户层传输 1 时,gpio 输出低电平,当接收到用户层传输 0 时,gpio 输出高电平

4. GPIO 子系统

使用 of_find_node_by_path 函数从设备树查找 /gpio_train 节点,查找成功时返回找到的节点,失败时返回NULL:

    gpiodev.node = of_find_node_by_path("/gpio_train");

    if(gpiodev.node == NULL)

    {

        printk("gpio-train node nost find!\r\n");

        return -EINVAL;

    }

 

从设备树节点中获取 GPIO 管脚号:

    gpiodev.led0 = of_get_named_gpio(gpiodev.node,"gpio", 0);

    printk("of gpio_train gpio: %d\r\n",gpiodev.led0);

    if(gpiodev.led0 < 0)

    {

        printk("can't get led-gpio \r\n");

        return -EINVAL;

    }

 

申请 GPIO 管脚:

   gpio_request(gpiodev.led0,"led0");

 

设置 GPIO 管脚模式和输出电平:

    gpio_direction_output(gpiodev.led0, 1);

 

5. Kernel 配置

5.1 编写 makefile

obj-$(CONFIG_GPIO_TRAIN_LED)   += gpio_train.o

 

5.2 编写 kconfig

① 新文件:

menuconfig GPIO_TRAIN

    bool "GPIO Train support"

    depends on OF_GPIO

 

if GPIO_TRAIN

config GPIO_TRAIN_LED

    tristate "GPIO Train block support"

    default n

    help

      This is enable GPIO Train support for the leds class

endif

 

② 添加节点:

config GPIO_TRAIN_LED

             tristate "Train GPIO"

             depends on OF_GPIO

             select GPIOLIB_IRQCHIP

      help

            train_gpio enable

 

5.3 文件引用

配置自己编写的 makefile 和 kconfig 的目录的上一级目录下的 makefile 和 kconfig:

 

① 添加 Makefile 包含:

# GPIO Train

odj-$(CONFIG_GPIO_TRAIN)           += gpio_train/

 

② 增加 makefile 配置:

obj-$(CONFIG_GPIO_TRAIN_LED)   += gpio_train.o

 

③ 添加Kconfig 包含:

source "drivers/gpio/gpio_train/Kconfig"

 

④ 增加 Kconfig 配置:

config GPIO_TRAIN_LED

     tristate "Train GPIO"

     depends on OF_GPIO

     select GPIOLIB_IRQCHIP

     help

       train_gpio enable

 

5.4设备树

       增加 gpio 设备树节点 gpio_train,增加 pinctrl gpio 管脚配置节点 pinctrl_train,把 gpio1_4 设置为输出模式。

gpio_train {

        #address-cells = <1>;   //用来描述子节点 "reg" 属性的地址表中用来描述首地址的 cell 的数量

        #size-cells = <1>;//用来描述子节点 "reg" 属性的地址表中用来描述地址长度的 cell 的数量。

        compatible = "gpio-trainled";

        pinctrl-name = "default";

        pinctrl-0 = <&pinctrl_train>;

        gpio = <&lsio_gpio1 4 GPIO_ACTIVE_LOW>;

        //pwms = <&pwm_lvds0 0 100000 0>;

        status = "okay";

};

 

pinctrl_train: traingrp {

        fsl,pins = <

            /* IMX8QM_QSPI1A_DQS_LSIO_GPIO4_IO22            0x06000021*/

             IMX8QM_LVDS0_GPIO00_LSIO_GPIO1_IO04             0x00000020

            /*IMX8QM_LVDS0_GPIO00_LVDS0_PWM0_OUT             0x00000020*/

            /*IMX8QM_LVDS1_GPIO00_LVDS1_PWM0_OUT           0x00000020*/

        >;

};

 

 

 

6. APP 编写

       使用 open 函数打开 GPIO 的设备节点,打开后使用 write 函数向内核驱动写字节数,写的内容由用胡决定,写 1 时 gpio 电平拉低,写 0 时 gpio 拉高,例如终端运行 ./app /dev/gpio_train 0 时,GPIO 电平拉高。

#include "stdio.h"

#include "unistd.h"

#include "sys/types.h"

#include "sys/stat.h"

#include "fcntl.h"

#include "stdlib.h"

#include "string.h"

/***************************************************************

使用方法   :./ledApp /dev/platled  0 关闭 LED

               ./ledApp /dev/platled  1 打开 LED      

 

***************************************************************/

int main(int argc, char *argv[])

{

     int fd, retvalue;

     char *filename;

     unsigned char databuf[2];

    

     if(argc != 3){

          printf("Error Usage!\r\n");

          return -1;

     }

 

     filename = argv[1];

    

     /* 打开led驱动 */

     fd = open(filename, O_RDWR);

     if(fd < 0){

          printf("file %s open failed!\r\n", argv[1]);

          return -1;

     }

    

     databuf[0] = atoi(argv[2]);  /* 要执行的操作:打开或关闭 */

     retvalue = write(fd, databuf, sizeof(databuf));

     if(retvalue < 0){

          printf("LED Control Failed!\r\n");

          close(fd);

          return -1;

     }

     printf("Filename: %s  write: %s\r\n ", filename, databuf[0]);

     retvalue = close(fd); /* 关闭文件 */

     if(retvalue < 0){

          printf("file %s close failed!\r\n", argv[1]);

          return -1;

     }

     return 0;

}



以上为 i.MX8QM 的 GPIO 驱动的编写以及测试 APP 的应用编写!


参考文献:

《iMX8QM_RM_Rev_E.pdf》
《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.6.pdf》

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论