当前位置: 首页 > news >正文

Linux驱动开发笔记(六)——pinctrl GPIO

 开发板:imx6ull mini

虚拟机:VMware17

ubuntu:ubuntu20.04

视频:第8.1讲 pinctrl和gpio子系统试验-pincrl子系统详解_哔哩哔哩_bilibili

文档:《【正点原子】I.MX6U嵌入式Linux驱动开发指南.pdf》四十五章

这一章看的很迷啊,驱动部分以后看懂了再补吧。这里先记一下怎么用。

目录

1. pinctrl

1.1 设备树部分

1.2 引脚配置

1.2.1 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19

1.2.2 0x17059

2. GPIO

2.1 设备树部分

2.1 获取GPIO信息的相关函数

2.1.1 of_gpio_named_count

2.1.2 of_gpio_count

2.1.3 of_get_named_gpio

2.1.4 gpio_request

2.1.5 gpio_free

2.1.6 gpio_direction_input / gpio_direction_output

2.1.7 gpio_get_value

2.1.8 gpio_set_value

3. 代码

3.1 修改设备树

3.2 驱动函数

3.2.1 文件结构

3.2.2 gpioled.c

3.2.3 gpioledAPP.c

附录

1、cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;

2、点灯代码流程


1. pinctrl

pinctrl子系统主要工作内容:

        ① 获取设备树中引脚信息

        ② 根据获取到的引脚信息来设置引脚的复用功能,决定这个引脚是作为GPIO,还是I2C、SPI等其他功能。(大部分SOC的引脚都支持复用)

        ③ 根据获取到的引脚信息来设置引脚的电气特性,如上/下拉、速度、驱动能力等

1.1 设备树部分

        需要在设备树里面设置引脚的功能,一般会在设备树里面创建一个节点来描述引脚的配置信 息。

        以imx6ull.dtsi文件中的iomuxc为例:

// imx6ull.dtsi文件中定义:
iomuxc: iomuxc@020e0000 {compatible = "fsl,imx6ul-iomuxc"; //Linux内核会根据compatbile来查找对应的驱动文件reg = <0x020e0000 0x4000>;
};
// 这里什么引脚都没有定义,具体的引脚在imx6ull-alientek-emmc.dts中对iomuxc追加// 有新的设备可以直接在imx6ull-alientek-emmc.dts中进行追加:
&iomuxc {pinctrl-names = "default";pinctrl-0 = <&pinctrl_hog_1>;imx6ul-evk {// 根据设备类型创建对应子节点,该设备所用的PIN都放到此节点下// 如pinctrl_hog_1里面是和热插拔有关的引脚配置pinctrl_hog_1: hoggrp-1 {fsl,pins = <MX6UL_PAD_UART1_RTS_B__GPIO1_IO19	    0x17059 /* SD1 CD (Card Detect?)SD卡检测引脚*/MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT	0x17059 /* SD1 VSELECT */MX6UL_PAD_GPIO1_IO09__GPIO1_IO09        0x17059 /* SD1 RESET */>;};// 根据设备类型创建对应子节点,该设备所用的PIN都放到此节点pinctrl_csi1: csi1grp {    下fsl,pins = <MX6UL_PAD_CSI_MCLK__CSI_MCLK		0x1b088MX6UL_PAD_CSI_PIXCLK__CSI_PIXCLK	0x1b088…………>;};…………};…………
};

1.2 引脚配置

        上面那段代码最重要的就是引脚的复用和配置fsl,pins = <……>。以其中的这一行为例:

    MX6UL_PAD_UART1_RTS_B__GPIO1_IO19	    0x17059

        这行代码前半部分MX6UL_PAD_UART1_RTS_B__GPIO1_IO19表示了电气属性寄存器的地址,后半部分表示要传入其中的电气属性配置值。

1.2.1 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19

        前半部分MX6UL_PAD_UART1_RTS_B__GPIO1_IO19是在imx6ul-pinfunc.h中定义的。 其中MX6UL_PAD_UART1_RTS_B这部分表示具体IO名,__GPIO1_IO19部分表示复用名。比如MX6UL_PAD_UART1_RTS_B__GPIO1_IO19表示将MX6UL_PAD_UART1_RTS_B复用为GPIO1_IO19。(imx6ul-pinfunc.h中关于MX6UL_PAD_UART1_RTS_B的复用都在这里↓)

#define MX6UL_PAD_UART1_CTS_B__UART1_DCE_CTS                      0x008C 0x0318 0x0000 0x0 0x0
#define MX6UL_PAD_UART1_CTS_B__UART1_DTE_RTS                      0x008C 0x0318 0x0620 0x0 0x2
#define MX6UL_PAD_UART1_CTS_B__ENET1_RX_CLK                       0x008C 0x0318 0x0000 0x1 0x0
#define MX6UL_PAD_UART1_CTS_B__USDHC1_WP                          0x008C 0x0318 0x066C 0x2 0x1
#define MX6UL_PAD_UART1_CTS_B__CSI_DATA04                         0x008C 0x0318 0x04D8 0x3 0x0
#define MX6UL_PAD_UART1_CTS_B__ENET2_1588_EVENT1_IN               0x008C 0x0318 0x0000 0x4 0x0
#define MX6UL_PAD_UART1_CTS_B__GPIO1_IO18                         0x008C 0x0318 0x0000 0x5 0x0
#define MX6UL_PAD_UART1_CTS_B__USDHC2_WP                          0x008C 0x0318 0x069C 0x8 0x1
#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

        上面这段代码每一行后面都跟着5个16进制数,其表示的含义为:

#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19       0x0090   0x031C    0x0000     0x5      0x0
//                                             <mux_reg conf_reg input_reg mux_mode input_val>
/*mux_reg  :mux寄存器偏移地址conf_reg :电气属性配置偏移地址input_reg:读取引脚的输入mux_mode :复用偏移地址input_val:写入input_reg地址的值
*/

        验证一下这五个数的含义:

        1、mux_reg = 0x0090

                表示一个偏移地址。它的父节点iomuxc首地址为020e0000,所以mux寄存器地址绝对地址为0x020e0000 + 0x0090 = 0x020e0090,对照(正点原子/07、I.MX6U参考资料/02、I.MX6ULL芯片资料/IMX6ULL参考手册.pdf)第1481页,可以看到mux寄存器地址为:

        2、conf_reg = 0x031C

                详见IMX6ULL参考手册.pdf第1813页。地址为020e0000 + 0x031C

        3、input_reg= 0x0000

                这里input_reg为0,即不偏移,表示UART1_RTS_B这个引脚没有input功能。

                这里找一个不是0的例子看看,以MX6UL_PAD_UART1_RTS_B为例,它的input_reg为0x0620:

#define MX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS         0x0090 0x031C 0x0620 0x0 0x3

                在IMX6ULL参考手册.pdf第2089页可以看到其input寄存器地址为0x020e 0620:

        4、mux_mode = 0x5

                表示复用为GPIO1_IO19。

        5、input_val = 0x0

        表示写给input_reg地址的数据。

        前面input_reg=0没有设置input内容,因此这部分写什么都行,不过一般都是0x0

1.2.2 0x17059

        这行代码的后半部分0x17059即是电气属性配置。此值由用户自行设置,通过此值来设置IO上/下拉、驱动能力和速度等。

    MX6UL_PAD_UART1_RTS_B__GPIO1_IO19	    0x17059

2. GPIO

        GPIO子系统用于初始化GPIO并且提供相应的API函数,如设置GPIO为输入输出、读取GPIO的值等。

2.1 设备树部分

fsl,pins = <MX6UL_PAD_UART1_RTS_B__GPIO1_IO19	    0x17059 /* SD1 CD  SD卡检测引脚*/…………>;
…………

        pinctrl部分中的这行代码已经将UART1_RTS_B引脚复用为GPIO1_IO19,并配置了其电气属性,用该引脚检测SD卡是否插入。

        那么驱动怎么知道CD引脚连的是GPIO1_IO19呢?可以在设备树中SD卡节点下添加一个属性来描述,SD卡驱动直接读取这个属性 就知道SD卡的CD引脚用的是哪个GPIO了。SD卡连接在imx6ull的usdhc1接口上,那么来看一下imx6ull-alientek-emmc.dts中&usdhc1节点的内容:

&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引脚复用?// 因为iomuxc节点中有&pinctrl_hog_1的配置,因此还是会初始化pinctrl_hog_1里的引脚的cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>; //(详见附录1)表示引脚使用的IO属于GPIO1组第19号IO,低电平有效enable-sdio-wakeup; vmmc-supply = <&reg_sd1_vmmc>; status = "okay"; 
}; 

2.1 获取GPIO信息的相关函数

        详见《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81.pdf》45.2.3和45.2.5节

        以下函数需要#include <linux/of_gpio.h>

2.1.1 of_gpio_named_count

        用于获取设备树某个属性里面定义了几个GPIO信息。

int of_gpio_named_count(struct device_node *np, const char  *propname)
// np      : 设备节点
// propname: 要统计的GPIO属性
// return  : 非负:统计到的GPIO信息数量   负数:失败

        但是这个函数并不聪明,比如下面这段代码,定义了4个GPIO信息,其中两个是空的。但是使用of_gpio_named_count仍然会返回4。

gpios = <0  &gpio1 1 2 0 &gpio2 3 4>; 

2.1.2 of_gpio_count

       统计“gpios”属性的GPIO数量(of_gpio_named_count统计任意属性的GPIO信息)

int of_gpio_count(struct device_node *np)
// np    : 设备节点
// return: 非负:统计到的GPIO数量   负值:失败

2.1.3 of_get_named_gpio

        用于获取GPIO编号。将设备树中类似<&gpio1 19 GPIO_ACTIVE_LOW>的属性信息转换为对应的GPIO编号。

int of_get_named_gpio(  struct device_node *np, const char *propname, int index)
//       np: 设备节点
// propname: 包含要获取GPIO信息的属性名
//    index: GPIO索引。一个属性里可能包含多个GPIO,此参数用于指定获取哪个GPIO
//   return: 
//        正值,获取到的GPIO编号;
//        负值,失败。// 示例:gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);    // led_gpio为int类型if(gpioled.led_gpio < 0) {    // 错误处理printk("can't get led-gpio");return -EINVAL;}printk("led-gpio num = %d\r\n", gpioled.led_gpio);

2.1.4 gpio_request

        在使用一个GPIO之前,需要先使用gpio_request申请一个GPIO管脚。

int gpio_request(unsigned gpio,  const char *label)
// gpio  : 要申请的GPIO标号,使用of_get_named_gpio函数获取
// label : 给GPIO设置一个名字
// return: 0申请成功;else申请失败

2.1.5 gpio_free

        不使用某个GPIO了,需要调用gpio_free函数释放。

void gpio_free(unsigned gpio)
// gpio: 要释放的GPIO标号

2.1.6 gpio_direction_input / gpio_direction_output

        将指定标号的GPIO设置为输入或输出。


int gpio_direction_input(unsigned gpio)
// gpio  :要设置为输入的GPIO标号
// return: 0成功;负值失败int gpio_direction_output(unsigned gpio, int value)
// gpio  :要设置为输出的GPIO标号
// value :GPIO 默认输出值
// return: 0成功;负值失败

2.1.7 gpio_get_value

        如果GPIO为output,则使用该函数读取该GPIO的值(0或1)。

#define gpio_get_value  __gpio_get_value 
int __gpio_get_value(unsigned gpio) 
// gpio  :要获取的GPIO标号
// return:0或1,表示得到的GPIO值;else失败

2.1.8 gpio_set_value

        如果GPIO为input,则使用函数设置该GPIO的值。

#define gpio_set_value  __gpio_set_value 
void __gpio_set_value(unsigned gpio, int value)
//gpio :要设置的GPIO标号
//value:要设置的值

3. 代码

  1. 添加pinctrl信息
  2. 在设备树中检查要使用的IO是否被占用
  3. 添加设备节点,描述所使用的GPIO
  4. 编写驱动,获取GPIO(具体详见3.2.2)

3.1 修改设备树

        在imx6ull-alientek-emmc.dts文件中找到&iomuxc,在里面的imx6ul-evk里加入新的pinctrl:

		pinctrl_gpioled: ledgrp{fsl,pins = <MX6UL_PAD_GPIO1_IO03__GPIO1_IO03	0x10b0>;};

        在根节点部分的最后加入下面这段代码(也就是上次实验加自己led节点的地方)

	gpioled{compatible = "alientek,gpioled";pinctrl-names = "default";pinctrl-0 = <&pinctrl_gpioled>;led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;  // GPIO1_pin3 低电平有效status = "okay";};

编译、复制到tftproot,然后重启开发板:

make dtbs
sudo cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /.../tftpboot/ -f

3.2 驱动函数

3.2.1 文件结构

        和之前的实验一样,文件结构如下:

6_GPIOLED(工作区)
└── 6_gpioled├── .vscode│   ├── c_cpp_properties.json│   └── settings.json├── gpioled.c├── gpioled.code-workspace├── gpioledAPP.c└── Makefile

        把Makefile的obj-m这一行改成obj-m := gpioled.o

3.2.2 gpioled.c

        获取GPIO流程:

                获取GPIO所在节点(of_find_node_by_path等)

                获取GPIO编号(of_get_named_gpio)

                申请该GPIO编号(gpio_requst)(如果不申请也可以直接用,但是无法检查该IO有没有被其他设备占用,通过申请可以检查该问题并进行对应处理)

                设置GPIO输入输出(gpio_direction_input或gpio_direction_output)

                读取或写入GPIO的值

        (具体代码详见下面驱动入口部分)

       将以下代码写入gpioled.c文件:(如果只是看一下流程,并不关心代码完整性的话可以直接看附录2)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/of_gpio.h>#define GPIOLED_NAME "gpioled"
#define GPIOLED_CNT 1#define LEDON 1
#define LEDOFF 0/* 设备结构体 */
struct gpioled_dev{dev_t devid;int major;int minor;struct cdev cdev;struct device *device;struct class *class;struct device_node *nd;int led_gpio;
};struct gpioled_dev gpioled;/* 操作集 */
static int led_release(struct inode *inode, struct file *filp){return 0;
}
static int led_open(struct inode *inode, struct file *filp){filp->private_data = &gpioled;return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos){int ret;unsigned char databuf[1];struct gpioled_dev *dev = filp->private_data;ret = copy_from_user(databuf, buf, count);if(ret < 0){return -EINVAL;}if(databuf[0] == LEDON){ //开灯gpio_set_value(dev->led_gpio, 0); // 低电平开灯} else if(databuf[0] == LEDOFF){gpio_set_value(dev->led_gpio, 1); // 高电平关灯}return 0;
}
static const struct file_operations led_fops = {.owner = THIS_MODULE,.write = led_write,.open = led_open,.release = led_release,
};/* 驱动入口 */
static int __init led_init(void){int ret = 0;/* 1.注册字符设备驱动 */gpioled.devid = 0;if(gpioled.devid){gpioled.devid = MKDEV(gpioled.devid, 0);register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);} else {alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);gpioled.major = MAJOR(gpioled.devid);gpioled.minor = MINOR(gpioled.devid);}/* 2.初始化cdev */gpioled.cdev.owner = THIS_MODULE;  cdev_init(&gpioled.cdev, &led_fops);/* 3.添加cdev */cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT); // 错误处理先略过了/* 4.创建类 */gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);if(IS_ERR(gpioled.class)){return PTR_ERR(gpioled.class);}/* 5.创建设备 */gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if(IS_ERR(gpioled.device)){return PTR_ERR(gpioled.device);}/* 1.获取设备节点 */gpioled.nd = of_find_node_by_path("/gpioled");  // 找到刚才在imx6ull-alientek-emmc.dts根节点下加入的gpioled节点if(gpioled.nd == NULL){ret = -EINVAL;goto fail_findnode;}/* 2.获取LED对应的GPIO */  // 也就是节点中led-gpios那一行内容gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);if(gpioled.led_gpio < 0){printk("can't find led_gpio\r\n");ret = -EINVAL;goto fail_findnode;}printk("led_gpio num = %d\r\n",gpioled.led_gpio);/*  3.申请IO */ret = gpio_request(gpioled.led_gpio, "led-gpios");if(ret){printk("Failed to request the led gpio\r\n");ret = -EINVAL;goto fail_findnode;        }/* 4.使用IO,设置为输出 */ret = gpio_direction_output(gpioled.led_gpio, 1);if(ret){goto fail_setoutput; // 如果代码走到这一步,一定已经成功进行了IO申请,因此这里错误处理时需要释放IO}/* 5.输出高电平,关灯 */gpio_set_value(gpioled.led_gpio, 1);return 0;fail_setoutput:gpio_free(gpioled.led_gpio);
fail_findnode:return ret;
}/* 驱动出口 */
static void __exit led_exit(void){gpio_set_value(gpioled.led_gpio, 1); // 高电平 关灯/* 注销字符设备驱动 */cdev_del(&gpioled.cdev);unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);device_destroy(gpioled.class, gpioled.devid);class_destroy(gpioled.class);gpio_free(gpioled.led_gpio);}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

        编译、复制:

# VSCODE
make
sudo cp gpioled.ko /.../nfs/rootfs/lib/modules/4.1.15/ -f# 串口
cd /lib/modules/4.1.15/
depmod
modprobe gpioled.ko

        然后此时会发现报错了:

        卡在了申请IO这一步。这一步卡住一般来说都是因为该IO口已经被占用了。现在去imx6ull-alientek-emmc.dts看看哪个节点还用了GPIO1_IO03这个口:

        发现是这个玩意占用了。这个东西现在不用,直接把GPIO1_IO03先注释掉,之后再加回来。

        另外,还要检查GPIO使用,也就是类似led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;这一行的“gpio1 3”:(这行代码的含义详见附录1)

        把这一行注释掉(或者status改成disabled也可以)。重新编译并传给开发板,重启开发板:

make dtbs
sudo cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /.../tftpboot/ -f

        此时重新modprobe不再报错了。

3.2.3 gpioledAPP.c

        这个代码和以前的点灯实验使用的应用代码一样,只是调用方法有变化。直接粘过来:

#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 成功; else失败* 调用   ./gpioledAPP <filename> <0:1>  0关灯,1开灯* ./gpioledAPP /dev/gpioledAPP 0  关灯* ./gpioledAPP /dev/gpioledAPP 1  开灯*/ #define LEDOFF 0
#define LEDON  1int main(int argc, char *argv[]){if(argc != 3){  // 判断用法是否错误printf("Error Usage!\r\n");return -1;}char *filename;int fd = 0;unsigned char databuf[1];int retvalue = 0;filename = argv[1];fd = open(filename, O_RDWR);  // 读写模式打开驱动文件filenameif(fd <0){printf("file %s open failed!\r\n");return -1;}databuf[0] = atoi(argv[2]);  // char 2 intretvalue = write(fd, databuf, sizeof(databuf));   // 缓冲区数据写入fdif(retvalue <0){printf("LED Control Failed!\r\n");close(fd);return -1;}close(fd);return 0;
}

编译、复制:

arm-linux-gnueabihf-gcc gpioledAPP.c -o gpioledAPP
sudo cp gpioledAPP /.../rootfs/lib/modules/4.1.15/

测试:

cd /lib/modules/4.1.15/
depmod
modprobe gpioled.ko  # 关灯./gpioledAPP /dev/gpioled 1  # 此时亮灯
./gpioledAPP /dev/gpioled 0  # 关灯rmmod gpioled.ko    # 关灯

(结束后记得把imx6ull-alientek-emmc.dts改回去)

附录

1、cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;

这一行的含义:

        ①gpio1表示引脚所使用的IO属于GPIO1组,定义:

// imx6ull.dtsi
gpio1: gpio@0209c000 {compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";reg = <0x0209c000 0x4000>;  // GPIO1控制器的寄存器基地址为0x0209c000,寄存器地址空间大小为0x4000interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,    // 中断<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;gpio-controller;    // 表示该节点是一个GPIO控制器#gpio-cells = <2>;  // 表示一共有两个cell,第一个cell为GPIO编号,如"&gpio1 3";// 第二个cell表示GPIO极性,如GPIO_ACTIVE_LOWinterrupt-controller;#interrupt-cells = <2>;
};

        其节点格式的要求如下:

/*以下内容来自资料包正点原子\01、例程源码\10、开发板教程对应的uboot和linux源码\02、linux\02、阿尔法V2.4版本及以后和miniV2.2及以后底板使用的linux\linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\Documentation\devicetree\bindings\gpio\fsl-imx-gpio.txt
*/* Freescale i.MX/MXC GPIO controllerRequired properties:
- compatible : Should be "fsl,<soc>-gpio"
- reg : Address and length of the register set for the device
- interrupts : Should be the port interrupt shared by all 32 pins, ifone number.  If two numbers, the first one is the interrupt sharedby low 16 pins and the second one is for high 16 pins.
- gpio-controller : Marks the device node as a gpio controller.
- #gpio-cells : Should be two.  The first cell is the pin number andthe second cell is used to specify the gpio polarity:0 = active high1 = active low
- interrupt-controller: Marks the device node as an interrupt controller.
- #interrupt-cells : Should be 2.  The first cell is the GPIO number.The second cell bits[3:0] is used to specify trigger type and level flags:1 = low-to-high edge triggered.2 = high-to-low edge triggered.4 = active high level-sensitive.8 = active low level-sensitive.Example:gpio0: gpio@73f84000 {compatible = "fsl,imx51-gpio", "fsl,imx35-gpio";reg = <0x73f84000 0x4000>;interrupts = <50 51>;gpio-controller;#gpio-cells = <2>;interrupt-controller;#interrupt-cells = <2>;
};

        ②“19 GPIO_ACTIVE_LOW”是一个#gpio-cell,19表示GPIO1组的第19号IO;GPIO_ACTIVE_LOW是一个宏,表示1,低电平有效,具体定义如下:

// include/linux/gpio/machine.h
enum gpio_lookup_flags {GPIO_ACTIVE_HIGH = (0 << 0),    // 0 高电平有效GPIO_ACTIVE_LOW = (1 << 0),     // 1 低电平有效GPIO_OPEN_DRAIN = (1 << 1),     // 2 开漏模式GPIO_OPEN_SOURCE = (1 << 2),    // 4 开集模式
};

2、点灯代码流程

        点灯代码150行还是看的眼晕,这里把所有错误处理、操作集等等都去掉了,背一下流程:

#define GPIOLED_NAME "gpioled"  // 设备名
#define GPIOLED_CNT 1           // 数量/* 设备结构体 */
struct gpioled_dev{dev_t devid;    // 设备号int major;      // 主设备号int minor;      // 次设备号struct cdev cdev;      // cdevstruct device *device; // 设备struct class *class;   // 类struct device_node *nd;// 设备节点int led_gpio;          // gpio数据
};
struct gpioled_dev gpioled;/* 操作集 */
// 略
static const struct file_operations led_fops = {…………
};/* 驱动入口 */
static int __init led_init(void){/* 1.注册字符设备驱动 */gpioled.devid = 0;alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); // 获取设备号/* 2.初始化cdev */gpioled.cdev.owner = THIS_MODULE;  cdev_init(&gpioled.cdev, &led_fops);/* 3.添加cdev */cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT); // 错误处理先略过了/* 4.创建类 */gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);/* 5.创建设备 */gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);/* 1.获取设备节点 */gpioled.nd = of_find_node_by_path("/gpioled");  // 找到刚才在imx6ull-alientek-emmc.dts根节点下加入的gpioled节点/* 2.获取LED对应的GPIO */  // 也就是节点中led-gpios那一行内容gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);printk("led_gpio num = %d\r\n",gpioled.led_gpio);/* 3.申请IO */gpio_request(gpioled.led_gpio, "led-gpios");/* 4.使用IO,设置为输出 */gpio_direction_output(gpioled.led_gpio, 1);/* 5.输出低电平,点灯*/gpio_set_value(gpioled.led_gpio, 0);return 0;
}/* 驱动出口 */
static void __exit led_exit(void){gpio_set_value(gpioled.led_gpio, 1); // 高电平 关灯/* 注销字符设备驱动 */cdev_del(&gpioled.cdev);unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);device_destroy(gpioled.class, gpioled.devid);class_destroy(gpioled.class);gpio_free(gpioled.led_gpio);
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

http://www.xdnf.cn/news/1344997.html

相关文章:

  • MTK Linux DRM分析(十三)- Mediatek KMS实现mtk_drm_drv.c(Part.1)
  • chapter07_初始化和销毁方法
  • 【连接器专题】连接器接触界面的理解
  • CoreShop微信小程序商城框架开启多租户-添加一个WPF客户端以便进行本地操作--读取店铺信息(6)
  • 彩笔运维勇闯机器学习--最小二乘法的数学推导
  • 在线教育领域的视频弹题功能如何打造高互动性在线课程
  • 【Tech Arch】Hadoop YARN 大数据集群的 “资源管家”
  • 全栈开发:从LAMP到云原生的技术革命
  • Kali Linux 发布重构版Vagrant镜像:通过命令行快速部署预配置DebOS虚拟机
  • Pandas中的SettingWithCopyWarning警告出现原因及解决方法
  • DbLens:告别手动Mock数据,右键一键智能生成数据库内容
  • httpclient与hertzclient在处理Host header时的差别
  • 【GPT入门】第53课 LlamaFactory微调效果与vllm部署效果不一致问题解决
  • open webui源码分析6-Function
  • FPGA学习笔记——简单的IIC读写EEPROM
  • FPGA高端项目:图像采集+Aurora 8B10B+UDP图传架构,基于GTH高速收发器的光口转网口,提供工程源码和技术支持
  • IntelliJ IDEA 常用快捷键笔记(Windows)
  • SRE系列(二) | 从可用性到 SLI/SLO
  • 【数据结构】B 树——高度近似可”独木成林“的榕树——详细解说与其 C 代码实现
  • MySQL编程开发(了解)
  • 08高级语言逻辑结构到汇编语言之逻辑结构转换 continue break 完结汇编按逻辑结构
  • Redis---事务
  • 51单片机-驱动步进电机模块教程
  • C#_组合优于继承的实际应用
  • Kafka Broker 核心原理全解析:存储、高可用与数据同步
  • 如何从根源上理解并解决前端的CORS跨域问题
  • 【PSINS工具箱】MATLAB例程,二维平面上的组合导航,EKF融合速度、位置和IMU数据,4维观测量
  • Unreal Engine ClassName Rule
  • Python 中 SQLAlchemy 和 MySQLdb 的关系
  • IKE 与 ISAKMP 核心笔记