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.pswp.cn/pingmian/94153.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/94153.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot 快速上手:从环境搭建到 HelloWorld 实战

在 Java 开发领域&#xff0c;Spring 框架占据着举足轻重的地位&#xff0c;但它复杂的配置曾让不少开发者望而却步。SpringBoot 的出现&#xff0c;如同为 Spring 框架装上了 “加速器”&#xff0c;以 “约定大于配置” 的理念简化了开发流程。本文将从环境准备、Maven 配置入…

图、最小生成树与最短路径

目录 并查集 并查集实现 图 概念 图的存储结构 邻接矩阵 邻接表 无向图 有向图 图的遍历 广度优先遍历 深度优先遍历 最小生成树 Kruskal算法&#xff08;克鲁斯卡尔算法&#xff09; Prim算法&#xff08;普利姆算法&#xff09; 最短路径 单源最短路径--Dij…

互联网电商新生态:开源AI智能名片、链动2+1模式与S2B2C商城小程序的融合赋能

摘要&#xff1a;本文聚焦互联网电商领域&#xff0c;探讨在当下直播电商蓬勃发展的背景下&#xff0c;开源AI智能名片、链动21模式以及S2B2C商城小程序如何相互融合&#xff0c;为创业者、企业和淘宝主播IP等电商参与者带来新的发展机遇。通过分析各要素的特点与优势&#xff…

企业车辆|基于SprinBoot+vue的企业车辆管理系统(源码+数据库+文档)

企业车辆管理系统 基于SprinBootvue的企业车辆管理系统 一、前言 二、系统设计 三、系统功能设计 系统功能实现 后台模块实现 管理员模块实现 驾驶员模块实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博…

自学嵌入式第二十五天:数据结构-队列、树

一、队列队列是只允许一段进行插入&#xff0c;另一端进行删除操作的线性表&#xff1b;允许插入的一端叫队尾&#xff0c;允许删除的一端叫对头&#xff1b;先进先出&#xff1b;用于解决速度不匹配&#xff08;例如一快一慢&#xff09;&#xff0c;做缓冲用&#xff1b;二、…

MySQL索引原理与优化全解析

1、MySQL索引是什么&#xff1f; 在关系数据库中&#xff0c;索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构&#xff0c;它是某个表中一列或若干列值的集合和相应的指向表中物理标志这些值的数据页的逻辑指针清单。索引的作用相当于图书的目录&a…

模型对话状态管理方法详解

模型对话状态管理方法详解 目录 简介手动管理对话状态构建对话历史追加响应内容 API 支持的自动化对话状态管理使用 previous_response_id 链接话轮 Token 及上下文窗口管理上下文窗口定义与限制Token 计数与工具 安全与合规注意事项结语1. 简介 在多轮对话场景中&#xff0c;合…

GPT-5 上线风波深度复盘:从口碑两极到策略调整,OpenAI 的变与不变

摘要&#xff1a; 近日&#xff0c;备受瞩目的 GPT-5 正式上线&#xff0c;却意外地在社区引发了两极化争议。面对技术故障与用户质疑&#xff0c;OpenAI 迅速推出一系列补救措施。本文将深度复盘此次发布风波&#xff0c;解析其背后的技术挑战与应对策略&#xff0c;并探讨这一…

【Android】使用FragmentManager动态添加片段

三三要成为安卓糕手 上一篇文章&#xff0c;我们是在xml中静态添加fragment&#xff0c;但是一些修改或者其他事情是做不了的&#xff1b; 本章我们达成在java代码中灵活添加、删除、替换fragment操作 一&#xff1a;核心代码展示 简单做一个这种页面public class FragmentActi…

MiniOB环境部署开发(使用开源学堂)

整体思路&#xff1a; 1.使用开源学堂在线编程环境开发MiniOB编译环境 2.使用vscode进行代码调试和开发以及上传到仓库 MiniOB源码&#xff1a;https://github.com/oceanbase/miniob MiniOB文档&#xff1a;MiniOB 介绍 - MiniOB 数据库大赛官网&#xff1a;OceanBase 社区…

09_常用内置模块进阶

第9课&#xff1a;常用内置模块进阶 课程目标 深入学习Python常用内置模块掌握collections、itertools、functools等模块学习json、csv、pickle等数据处理模块 1. collections模块 1.1 Counter类 from collections import Counter# 统计元素出现次数 text "hello world p…

⚡ Ranger 基础命令与功能详解

&#x1f4cc; 1. Ranger简介 Ranger&#xff08;游侠&#xff09;是一款 Linux 专用的 指令式文件管理器&#xff0c;其操作风格类似 Vim&#xff0c;通过输入指令即可完成目录跳转、文件编辑、移动、复制等操作。 相比于 mc&#xff08;Midnight Commander&#xff09;&…

CUDA安装教程(包括cuDNN的教程)一个博客带你了解所有问题

前言 windows10 版本安装 CUDA &#xff0c;首先需要下载两个安装包 CUDA toolkit&#xff08;toolkit就是指工具包&#xff09;cuDNN 注&#xff1a;cuDNN 是用于配置深度学习使用 官方教程 CUDA&#xff1a;Installation Guide Windows :: CUDA Toolkit Documentation …

ArkTS 语言全方位解析:鸿蒙生态开发新选择

在鸿蒙生态蓬勃发展的当下&#xff0c;一款高效、健壮的开发语言成为开发者的迫切需求。ArkTS 语言应运而生&#xff0c;作为鸿蒙生态的核心应用开发语言&#xff0c;它在 TypeScript&#xff08;简称 TS&#xff09;基础上进行创新扩展&#xff0c;为开发者打造高性能、易维护…

JavaScript性能优化实战:从瓶颈识别到极致体验

文章目录JavaScript性能优化实战&#xff1a;从瓶颈识别到极致体验1. 引言&#xff1a;为什么JavaScript性能至关重要1.1 性能对用户体验的影响1.2 JavaScript性能瓶颈的多样性2. JavaScript内存管理优化2.1 JavaScript内存模型详解2.2 垃圾回收机制与优化策略2.3 内存分析实战…

批量归一化:不将参数上传到中心服务器,那服务器怎么进行聚合?

联邦批量归一化&#xff08;FedBN&#xff09; 是一种联邦学习客户端本地模型优化算法。它的核心思想是&#xff1a;在联邦学习的客户端本地训练过程中&#xff0c;保留并独立更新批量归一化层&#xff08;Batch Normalization, BN&#xff09;的参数&#xff0c;而不将这些参数…

Qt中使用MySQL数据库

一、MySQL 入门 核心概念 在 QT 中操作数据库,主要使用两个模块: QSqlDatabase:代表一个数据库连接。 QSqlQuery:用于执行 SQL 语句(如 SELECT, INSERT, UPDATE, DELETE)并处理结果。 环境准备 在编写代码之前,你需要确保系统已具备以下条件: 1. 安装 MySQL 从 M…

Android - 统一资源标识符 Uri

一、概念URI&#xff08;Uniform Resource Identifier&#xff09;统一资源标识符&#xff0c;用于标识资源的字符串&#xff08;如图片、网页、文件、应用等&#xff09;。1.1 与 URL 的区别URL&#xff08;统一资源定位符&#xff09;是 URI&#xff08;统一资源标识符&#…

开源 AR 眼镜怎么选?OpenGlass ,OSSG,cheApR 分析推荐

开源项目横评&#xff08;看完你会知道自己属于哪一类&#xff09; 1&#xff09;OpenGlass&#xff1a;最低成本跑通“能用的AI眼镜” 卖点&#xff1a;用不到$25的通用元件&#xff0c;把任意普通眼镜改造成“可黑客化”的智能眼镜&#xff1b;能录制、识别、翻译、记人等。…

RAGFlow (一) 开发环境搭建

本文介绍如何在Windows上进行RAGFlow开发环境搭建 一. 环境准备 前提条件 CPU ≥ 4 核内存 ≥ 16 GB磁盘 ≥ 50 GBDocker ≥ 24.0.0 & Docker Compose ≥ v2.26.1 安装Docker Desktop为wsl安装Ubuntu 1.启用 WSL2​​&#xff08;Windows Subsystem for Linux&#xff09…