0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

pinctrl与gpio子系统下的字符设备驱动框架

CHANBAEK 来源:嵌入式攻城狮 作者:安迪西 2023-04-13 15:19 次阅读

pinctrl与gpio子系统下的字符设备驱动框架

点亮Linux驱动开发路上的第一个灯一文中将与外设有关的寄存器信息,定义到驱动代码中,直接操作寄存器来控制外设。缺点是当芯片的寄存器发了变动,就要对底层的驱动进行重写。

设备树下的字符设备驱动框架一文中将与外设有关的寄存器信息,写到了设备树文件中,通过设备树API函数获取外设信息。当外设的信息有变化时,只需要修改设备树文件即可,无需修改底层驱动,提高了驱动代码的复用能力,但仍需要直接操作寄存器来控制外设。

本文介绍的pinctrl和gpio子系统实现了对寄存器的操作,我们只需要使用子系统提供的API函数即可,而无需再直接操作寄存器了。

1. pinctrl与gpio子系统介绍

1.1 pinctrl子系统

pinctrl子系统就是管理PIN引脚的一个系统,在设备树里设置PIN的配置信息,pinctrl会根据提供的信息来配置PIN功能。其主要工作内容如下:

  • 获取设备树中的PIN信息
  • 设置获取到的PIN的复用功能
  • 设置获取到的PIN的电气特性

打开设备树文件imx6ull-andyxi-emmc.dts,其中iomuxc节点就是I.MX6ULL的外设节点。代码中的pinctrl_hog_1子节点就是和热插拔有关的PIN集合

&iomuxc {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_hog_1>;
    imx6ul-evk {
        pinctrl_hog_1: hoggrp-1 {
            fsl,pins = <
                MX6UL_PAD_UART1_RTS_B__GPIO1_IO19        0x17059 
                MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT     0x17059 
                MX6UL_PAD_GPIO1_IO09__GPIO1_IO09         0x17059 
                MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID     0x13058
           >;
        };
        ......
    };
};

下面以UART1_RTS_B这个PIN为例,介绍如何添加PIN的配置信息:

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19     0x17059

前半部分:设置UART1_RTS_B引脚的复用功能

//MX6UL_PAD_UART1_RTS_B__GPIO1_IO19宏定义在文件arch/arm/boot/dts/imx6ul-pinfunc.h中
#define MX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS          0x0090 0x031C 0x0620 0x0 0x3
#define MX6UL_PAD_UART1_RTS_B__UART1_DTE_CTS          0x0090 0x031C 0x0000 0x0 0x0
#define MX6UL_PAD_UART1_RTS_B__ENET1_TX_ER            0x0090 0x031C 0x0000 0x1 0x0
#define MX6UL_PAD_UART1_RTS_B__USDHC1_CD_B            0x0090 0x031C 0x0668 0x2 0x1
#define MX6UL_PAD_UART1_RTS_B__CSI_DATA05             0x0090 0x031C 0x04CC 0x3 0x1
#define MX6UL_PAD_UART1_RTS_B__ENET2_1588_EVENT1_OUT  0x0090 0x031C 0x0000 0x4 0x0
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19             0x0090 0x031C 0x0000 0x5 0x0
#define MX6UL_PAD_UART1_RTS_B__USDHC2_CD_B            0x0090 0x031C 0x0674 0x8 0x2

上面代码中共有8个UART1_RTS_B引脚的宏定义,分别对应该引脚的8个复用IO。宏定义的值,被分为了5段,每段的值都有具体的含义:

图片

  • mux_reg:mux_reg寄存器偏移地址 (见下图1)
  • conf_reg:conf_reg寄存器偏移地址 (见下图2)
  • input_reg:input_reg寄存器偏移地址 (此处无效)
  • mux_mode:mux_reg寄存器的值,用于设置复用功能 (见下图3)
  • input_val:input_reg寄存器值 (此处无效)

图片

图片

图片

后半部分:此值就是conf_reg寄存器的值,用于设置UART1_RTS_B引脚的电气特性

1.2 gpio子系统

gpio子系统就是管理gpio功能的一个系统,其作用是初始化gpio,并提供对外的API接口。使用gpio子系统后,就无需自己操作寄存器,通过调用相关API函数即可实现对gpio的控制。

仍以热插拔节点为例,pinctrl子系统已经将UART1_RTS_B复用为GPIO1_IO19,并设置好了电气属性。驱动程序通过读取其高低电平来判断SD卡有没有插入。

那驱动程序怎么知道CD引脚连接的是GPIO1_IO19呢 ?这就需要设备树来告诉驱动了,在设备树的SD卡节点下添加一个描述CD引脚的属性:

&usdhc1 {
    pinctrl-names = "default", "state_100mhz", "state_200mhz";
    pinctrl-0 = <&pinctrl_usdhc1>;
    pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
    pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
    pinctrl-3 = <&pinctrl_hog_1>;
    cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
    keep-power-in-suspend;
    enable-sdio-wakeup;
    vmmc-supply = <®_sd1_vmmc>;
    status = "okay";
};

上面代码中pinctrl-3指定了CD引脚的pinctrl信息,cd-gpios属性描述了CD引脚使用哪个IO,属性值共有三个,具体含义如下:

  • &gpio1 表示CD引脚所使用的IO属于GPIO1组
  • 19 表示GPIO1组的第19号IO
  • GPIO_ACTIVE_LOW 表示低电平有效

设置好设备树后就可使用gpio子系统提供的API函数来操作指定的GPIO,gpio子系统向开发人员屏蔽了具体的读写寄存器过程。下图中有常用的API函数介,此外还有pinctrl和gpio子系统的使用模板:

图片

**2. **pinctrl与gpio子系统字符设备驱动框架

下图为pinctrl与gpio子系统下的字符设备驱动框架:

图片

接下来根据上面的框架图,以驱动LED (GPIO1_IO03) 为例,分步介绍具体的代码编写流程

2.1 修改设备树文件

添加pinctrl节点:在iomuxc节点的imx6ul-evk子节点下创建pinctrl_led节点,复用GPIO1_IO03

pinctrl_led: ledgrp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0x10B0
    >;
};
//MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 复用GPIO1_IO03
//0x10B0 设置pin的电气特性

添加LED设备节点:在根节点下创建LED设备节点,指定对应的pinctrl节点,指定所使用的GPIO

gpioled {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "alpha-gpioled";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_led>;
    led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
    status = "okay";
};

检查PIN是否冲突:检查pinctrl和设备节点中指定的引脚有没有被别的外设占用

//检查GPIO_IO03这个PIN有没有被其他的pinctrl节点使用
pinctrl_tsc: tscgrp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
        MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
        //GPIO_IO03被pinctrl_tsc节点占用,因此需要屏蔽掉
        /* MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0 */ 
        MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
    >;
};
//检查"gpio1 3"有没有被其他设备节点占用
&tsc {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_tsc>;
    //"gpio1 3"被tsc设备节点占用,因此需要屏蔽掉
    /* xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; */
    measure-delay-time = <0xffff>;
    pre-charge-time = <0xfff>;
    status = "okay";
};

编译设备树:使用make dtbs命令编译设备树,并使用该设备树启动Linux系统

#在内核根目录下
make dtbs                #编译设备树
#启动Linux系统后
cd /proc/device-tree     #查看"gpioled"节点是否存在

2.2 编写驱动程序

创建驱动程序文件gpioled.c,添加如下代码

宏定义及设备结构体定义

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;               //所使用的gpio编号
};

struct gpioled_dev gpioled;     //定义led设备

编写设备操作函数:write函数中,直接使用gpio子系统提供的API函数操作GPIO

static ssize_t led_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    struct gpioled_dev *dev = filp->private_data;
    retvalue = copy_from_user(databuf, buf, cnt); 
    if(retvalue < 0){
        printk("kernel write failed!\\r\\n");
        return -EFAULT;
    }

    ledstat = databuf[0];
    if(ledstat == LEDON){
        gpio_set_value(dev->led_gpio, 0);
    }else if(ledstat == LEDOFF){
        gpio_set_value(dev->led_gpio, 1);
    }
    return 0;
}

驱动入口函数中:获取GPIO编号后初始化

/* 设置 LED 所使用的 GPIO */
/* 1、获取设备节点:gpioled */
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL) {
    printk("gpioled node cant not found!\\r\\n");
    return -EINVAL;
} else {
    printk("gpioled node has been found!\\r\\n");
}
/* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
if(gpioled.led_gpio < 0) {
    printk("can't get led-gpio");
    return -EINVAL;
}
printk("led-gpio num = %d\\r\\n", gpioled.led_gpio);
/* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0) {
    printk("can't set gpio!\\r\\n");
}

驱动入口函数中:注册字符设备驱动,代码与Linux点灯中一样

驱动出口函数中:注销设备驱动,删除类和设备,代码可参考Linux点灯一文

2.3 编写测试程序

实现操作驱动文件对外设进行控制的功能。创建测试程序文件gpioledApp.c,代码内容与Linux点灯一文中的测试程序代码一致,此处不再赘述

2.4 编译测试

编译驱动程序:当前目录下创建Makefile文件,并make编译

KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := gpioled.o

build: kernel_modules

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

编译测试程序:无需内核参与,直接编译即可

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

运行测试:启动开发板后,加载驱动模块,之后使用应用程序测试驱动是否正常工作

depmod                         #第一次加载驱动的时候需要运行此命令
modprobe gpioled.ko            #加载驱动
./gpioledApp /dev/gpioled 1    #打开LED灯
./gpioledApp /dev/gpioled 0    #关闭LED灯
rmmod gpioled.ko               #卸载驱动模块
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 寄存器
    +关注

    关注

    31

    文章

    5292

    浏览量

    119802
  • Linux
    +关注

    关注

    87

    文章

    11204

    浏览量

    208701
  • 子系统
    +关注

    关注

    0

    文章

    109

    浏览量

    12373
  • 字符
    +关注

    关注

    0

    文章

    230

    浏览量

    25154
  • GPIO
    +关注

    关注

    16

    文章

    1188

    浏览量

    51828
收藏 人收藏

    评论

    相关推荐

    一文搞懂Linux pinctrl/gpio子系统

    GPIO的寄存器操作。分享给刚刚接触外设bsp的小伙伴们。当然后面有时间还会分享GPIO子系统框架pinctrl
    发表于 06-09 09:52 2575次阅读

    「正点原子Linux连载」第四十五章 pinctrlgpio子系统实验(一)

    驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架,关于驱动的分离与分层我们后面会讲。本来
    发表于 03-19 14:58

    「正点原子Linux连载」第四十五章 pinctrlgpio子系统实验(二)

    =5};第4行,test设备所使用的gpio。关于pinctrl子系统gpio子系统就讲解到
    发表于 03-19 14:59

    字符设备驱动 —— 字符设备驱动框架

    1、概述:linux中一切皆文件,设备也如此,并且以操作文件即文件IO的方式访问设备。  应用程序只能通过库函数中的系统调用来操作硬件,对于每个系统调用,
    发表于 10-19 17:08

    基于GPIO子系统的LED驱动程序分享

    Pinctrl 子系统把引脚的复用、配置抽出来,做成 Pinctrl 子系统,给 GPIO、I2C 等模块使用。让我们在使用某个引脚功能时不
    发表于 12-16 07:16

    怎样去使用linux的pintcrl和gpio子系统

    pinctrlgpio内部的原理是如何实现的?怎样去使用linux的pintcrl和gpio子系统呢?
    发表于 03-07 13:38

    RK3399开发板的pinctrlgpio子系统相关资料介绍

    驱动工程师只做驱动,应用工程师专注做应用。  linux的pintcrl和gpio子系统就类似于ST的“BSP库”,但是linux的
    发表于 09-16 17:27

    更新 | 持续开源 迅为RK3568驱动指南第十一篇-pinctrl子系统

    篇 热插拔 第11篇 pinctrl子系统 未完待续,持续更新中... 视频教程更新至十二期 第一期_驱动基础 第二期_字符设备基础 第三期
    发表于 10-18 11:12

    gpiopinctrl子系统的关系与区别

    gpiopinctrl 子系统在内核里的使用率非常高,和嵌入式产品的关联非常大。从这两个子系统开始学习驱动开发是个不错的入门选择。
    的头像 发表于 03-15 11:40 4797次阅读

    嵌入式驱动开发两大子系统的使用

    本文的关注点是 gpio driver --> gpio subsystem core -> gpio consumer 这一路径,读者如果想更深入地了解 pinctrl
    的头像 发表于 03-15 13:41 1741次阅读

    【i.MX6ULL】驱动开发6——GPIO子系统点亮LED

    本篇介绍了使用**Pinctrl子系统GPIO子系统**的方式来点亮LED,与之前的寄存器版点亮LED与设备树版点亮LED的最大区别在于不
    的头像 发表于 05-21 21:50 3126次阅读
    【i.MX6ULL】<b class='flag-5'>驱动</b>开发6——<b class='flag-5'>GPIO</b><b class='flag-5'>子系统</b>点亮LED

    使用pinctrlgpio子系统实现LED灯驱动

    前边已经学了两种点灯,本质依然还是通过配置寄存器;在学习STM32的时候除了学习配置一寄存器,基本都是使用库来开发,那么在i.MX6ULL还使用寄存器开发明显是不太适合,那么i.MX6ULL有更方便的开发呢,这篇就来学习一使用 pi
    的头像 发表于 04-03 10:17 1265次阅读

    RK3568pinctrlgpio 子系统详解

    如果 pinctrl 子系统将 PIN 复用为 GPIO,那么接下来就要配置 gpio 子系统,且 gp
    的头像 发表于 12-20 10:22 2684次阅读
    RK3568<b class='flag-5'>pinctrl</b> 和 <b class='flag-5'>gpio</b> <b class='flag-5'>子系统</b>详解

    Linux中pinctrl操作GPIO只需要几步

    pinctrl 子系统 API pinctrl 子系统的 API 有很多,对于驱动工程师来说,pinct
    的头像 发表于 09-27 17:24 3222次阅读

    瑞芯微RK3568-iomuxc和pinctrl子系统初窥

    pinctrl子系统作用:从设备树中获取PIN的描述信息来设置PIN的复用和电气属性,PIN可复用为I2C、SPI、GPIOgpio
    发表于 12-20 10:10 54次下载