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

iomuxc、pinctrl子系统、gpio子系统(学习总结)

iomuxc、pinctrl子系统、gpio子系统三者的关系

  1. 相互依赖:IOMUXC、pinctrl子系统和gpio子系统在功能上相互依赖。IOMUXC提供了引脚复用和电气属性的配置能力,pinctrl子系统负责从设备树中获取这些配置信息并完成初始化,而gpio子系统则在引脚被配置为GPIO功能时提供具体的操作接口。

  2. 分层结构:从系统架构的角度来看,这三者构成了一个分层的结构。IOMUXC位于最底层,提供硬件级别的引脚复用和电气属性配置;pinctrl子系统位于中间层,负责软件层面的引脚配置和初始化;gpio子系统则位于最上层,为开发者提供易于使用的GPIO操作接口。

  3. 协同工作:在实际应用中,这三者协同工作以实现引脚的有效管理和控制。例如,在配置一个LED灯的GPIO引脚时,首先需要通过IOMUXC将该引脚复用为GPIO功能,然后通过pinctrl子系统从设备树中获取该引脚的配置信息并完成初始化,最后通过gpio子系统提供的API接口来设置引脚的方向和输出值以控制LED灯的亮灭。

IOMUXC

一、IOMUXC的基本概念

  • 概念引入:为了推出功能丰富的核心板,SOC厂商引入了引脚复用-IOMUXC架构。这种架构通过输入输出多路复用器(Input/Output Multiplexer),使得每个引脚最多可复用好几种功能,每个功能又可以出现在不同的引脚上。
  • 核心功能:IOMUXC的核心功能是IO口复用,即利用有限的IO口资源,通过动态切换,满足多个内部外设的IO需求。

二、IOMUXC的架构与组成

  • 基本单元:IOMUX由一些基本的iomux_cell单元组成,每个基本IOMUX单元只处理一个pad复用信号。
  • 子块组成:IOMUXC通常由两个子块组成:IOMUXC_REGISTERS(包含所有IOMUXC寄存器)和IOMUXC_LOGIC(包含所有IOMUXC组合逻辑,如IP接口控制、地址解码器、可观察复用控制)。
  • 寄存器分类:在IOMUXC控制器中,寄存器主要分为三大类:IOMUXC_SW_PAD_CTRL_X(管脚控制寄存器)、IOMUXC_SW_MUX_CTL_PAD_X(输出路由寄存器)、IOMUXC_X_SELECT_INPUT(输入路由寄存器)。

三、IOMUXC的配置与使用

  • 配置方式:IOMUXC的配置通常涉及对控制器中的寄存器进行参数设置。这些参数包括控制寄存器的偏移地址、MUX控制寄存器的偏移地址、MUX模式、输入路由寄存器的偏移地址以及Daisy Chain模式等。
  • Linux内核配置:在Linux内核中,SOC厂商会把各功能引脚的配置参数值写到相应的宏定义头文件中(如xxx-pinfunc.h),用户可以根据芯片使用手册中的说明,在设备树(dtb)中添加或修改引脚定义。
  • 功能切换:IOMUXC只起了一个功能切换的作用,引脚配置完成后,还需要配置具体功能的控制器里寄存器参数,这样才能共同完成引脚的功能确定。

四、IOMUXC的应用场景

  • GPIO配置:在GPIO配置中,IOMUXC用于选择引脚的功能模式,如将某个引脚配置为GPIO输入或输出模式。
  • 外设接口配置:对于其他外设接口(如UART、SPI、I2C等),IOMUXC同样用于选择引脚的功能模式,以满足外设的IO需求。

五、总结

        IOMUXC是SOC设计中实现引脚复用的关键组件,它通过动态切换引脚功能,提高了IO资源的利用率,满足了不同外设和功能的IO需求。在配置和使用IOMUXC时,需要参考芯片使用手册和设备树文档,确保引脚功能的正确设置。

Pinctrl       

一、Pinctrl子系统的作用

        Pinctrl子系统的主要作用是管理和配置处理器上的引脚资源。在嵌入式系统中,引脚是处理器与外部世界进行通信和交互的接口。不同的处理器和硬件平台可能具有不同的引脚数量和功能,因此需要一种统一的机制来管理和配置这些引脚资源,以确保系统的正常运行。

二、Pinctrl子系统的组成

Pinctrl子系统主要由以下几个部分组成:

  1. Pinctrl Core:Pinctrl Core是Pinctrl子系统的核心部分,负责管理和维护系统上的引脚和Pinctrl配置,提供通用的Pinctrl接口供设备驱动程序使用,并管理设备驱动程序之间的引脚冲突和资源共享。

  2. Pinctrl驱动程序:Pinctrl驱动程序是一个用于管理芯片引脚的Linux内核驱动程序。它通过实现Pinctrl Core提供的接口来向Pinctrl Core注册自己,并提供芯片引脚的配置和控制功能。

  3. Pinctrl映射器:Pinctrl映射器是一个将Pinctrl配置映射到具体芯片引脚的模块。它负责解析Pinctrl配置,映射到具体的引脚,并将映射结果传递给Pinctrl驱动程序。Pinctrl映射器可以根据不同的芯片架构和芯片引脚布局进行定制。

三、Pinctrl子系统的工作原理

Pinctrl子系统的工作原理可以分为以下几个阶段:

  1. 解析阶段:在系统启动时,Pinctrl子系统会解析设备树(Device Tree)中的引脚配置信息。

  2. 配置阶段:解析完成后,Pinctrl子系统会根据解析得到的信息配置引脚资源。它会根据配置模式设置引脚的功能,并根据电气特性配置引脚的输入/输出电路。如果需要,Pinctrl子系统还会配置引脚的中断信息,以便处理外部事件。

  3. 应用阶段:配置完成后,Pinctrl子系统会将配置应用于处理器上的引脚。这意味着处理器上的引脚将按照配置信息进行工作。此时,其他驱动程序或应用程序可以通过Pinctrl子系统提供的接口来访问和操作这些引脚。

四、Pinctrl子系统的功能

Pinctrl子系统提供了丰富的功能,以满足不同的应用场景需求:

  1. 引脚复用:允许多个引脚通过复用的方式连接到芯片上的不同功能模块,从而提高了引脚的利用率。

  2. 引脚配置:支持对引脚进行详细的配置,包括设置引脚的电气特性(如上拉、下拉、开漏等)、驱动能力、速度等。

  3. 引脚组配置:支持将多个引脚组合成一个逻辑上的引脚组,以便进行批量配置和操作,提高了配置效率。

  4. 中断管理:支持引脚中断的配置和管理,使得开发者可以方便地处理外部事件。

五、imx6ull举例

以参数值为例进行分析:

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19       0x17059 /* SD1 CD */

参数分为前后的两部分 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059

首先是 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19  此参数是一个宏定义如下对应了5个数据

#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19            0x0090 0x031C 0x0000 0x5 0x0
参数值的宏定义对应了5个数据值对应关系如下表 :

说明数据值
mux_reg复用寄存器的地址或偏移量0x0090
conf_reg配置寄存器的地址或偏移量0x031C
input_reg输入寄存器的地址或偏移量0x0000
mux_mode复用模式0x5
input_val输入值0x0

iomuxc的 reg 属 性 可 知 IOMUXC 外 设 寄 存 器 起 始 地 址 为 0x020e0000 。

1.mux_reg :0x0090

UART1_RTS_Bpinmux_reg寄存器地址就是 :0x020e0000+0x0090= 0x020e0090。

2.conf_reg :0x031C

UART1_RTS_Bpinconf_reg寄存器地址就是 :0x020e0000+0x031c= 0x020e031c

3.input_reg :0x0000

input_reg 寄存器偏移地址,有些外设有 input_reg 寄存器,有 input_reg 寄存器的外设需要配置 input_reg 寄存器。

UART1_RTS_Bpininput_reg寄存器没有。

4.mux_mode:0x5

表示UART1_RTS_B复用为gpio1_io19 

5.input_val :0x0

input_val:就是写入 input_reg, 寄存器的值为0

        然后数据值 0x17059 就是 conf_reg 寄存器值。此值由用户自行设置,通 过此值来设置一个 IO 的上 / 下拉、驱动能力和速度等。在这里就相当于设置寄存器IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的值为 0x17059 。

向设备树中的节点添加pinCtrl节点例子如下

pinctrl_test: testgrp {
    fsl,pins = <
    MX6UL_PAD_GPIO1_IO00__GPIO1_IO00   config //onfig 是具体设置值
    ..........
    >;
};
        设备树是通过属性来保存信息的,因此需要添加一个属性,属性名字一定要为“fsl,pins ”,
因为对于 I.MX 系列 SOC 而言, pinctrl 驱动程序是通过读取“ fsl,pins ”属性值来获取 PIN 的配
置信息.

GPIO

        GPIO子系统是Linux内核中用于统一和便捷地访问GPIO(通用输入输出)引脚的一个子系统。它提供了一组API函数,使得驱动程序和用户空间程序能够方便地设置GPIO的方向、读取或写入GPIO的值,以及处理GPIO中断等。以下是对GPIO子系统的详细介绍,包括相关的API函数和OF(设备树)函数。

一、GPIO子系统的作用

        GPIO子系统的主要作用是方便驱动开发者使用GPIO引脚。通过GPIO子系统,开发者可以在设备树中添加GPIO相关的信息,然后在驱动程序中使用GPIO子系统提供的API函数来操作GPIO引脚,如设置GPIO的方向、读取或写入GPIO的值等。这样,开发者就无需直接操作硬件寄存器,从而简化了驱动程序的开发过程。

二、GPIO子系统的架构

Linux的GPIO子系统驱动框架主要由以下几个部分组成:

  1. GPIO控制器驱动程序:这是与硬件相关的代码,用于处理GPIO控制器与系统总线之间的通信。该部分代码通常由芯片厂商提供,以与特定的GPIO硬件交互。

  2. 平台驱动程序:平台驱动程序用于与硬件平台交互,识别GPIO硬件并将其与相关的GPIO控制器驱动程序关联起来。平台驱动程序的主要任务是向系统注册GPIO控制器设备,与GPIO控制器驱动程序进行绑定,并为其分配资源。

  3. GPIO字符设备驱动程序:该驱动程序提供了用户空间API,使应用程序能够使用GPIO。用户可以通过/sys/class/gpio目录下的文件系统接口,通过读取和写入GPIO寄存器来控制GPIO。

三、GPIO子系统的API函数

GPIO子系统提供了一系列API函数,用于操作GPIO引脚。以下是一些常用的API函数及其功能:

  1. gpio_request(unsigned gpio, const char *label):用于申请一个GPIO引脚。在使用一个GPIO引脚之前,必须先通过此函数进行申请。

  2. gpio_free(unsigned gpio):用于释放之前申请的GPIO引脚。当不再需要使用某个GPIO引脚时,应调用此函数进行释放。

  3. gpio_direction_input(unsigned gpio):用于设置GPIO引脚为输入模式。

  4. gpio_direction_output(unsigned gpio, int value):用于设置GPIO引脚为输出模式,并可选地设置初始输出值。

  5. gpio_get_value(unsigned gpio):用于获取GPIO引脚的值(0或1)。

  6. gpio_set_value(unsigned gpio, int value):用于设置GPIO引脚的值(0或1)。

四、GPIO子系统的OF函数

在Linux内核中,设备树(Device Tree)是一种用于描述硬件设备的数据结构。GPIO子系统提供了一系列OF(设备树)函数,允许驱动程序从设备树中获取GPIO引脚的信息。以下是一些常用的OF函数及其功能:

  1. of_gpio_named_count(struct device_node *np, const char *propname):用于统计设备树中某个属性里面定义了几个GPIO信息。

  2. of_gpio_count(struct device_node *np):与of_gpio_named_count类似,但专门用于统计“gpios”属性的GPIO数量。

  3. of_get_named_gpio(struct device_node *np, const char *propname, int index):用于从设备树中获取指定属性的GPIO编号。该函数是驱动开发中常用的,因为它允许驱动程序根据设备树中的配置信息动态地获取GPIO编号。

五、举例

GPIO节点基本结构

在设备树中,一个典型的GPIO节点可能包含以下几个属性:

  1. gpio-controller:指定GPIO控制器的名称或引用。这通常是一个phandle,指向设备树中定义GPIO控制器的节点。

  2. #gpio-cells:表示每个GPIO引脚描述所占用的单元数。这通常与GPIO控制器的硬件设计相关。

  3. gpio-range:定义GPIO引脚的范围,包括起始引脚号和引脚数量。例如,<&gpio0 0 16>表示从GPIO控制器gpio0的第0个引脚开始,共有16个引脚。

  4. interrupt-parent:指定中断控制器的名称或引用,如果GPIO引脚用于中断的话。

  5. interrupts:描述GPIO引脚的中断配置,包括中断号、触发方式和中断处理函数等。

  6. status:表示GPIO节点的状态,通常是"okay"表示节点有效。

&gpio0 {  
    /* GPIO控制器引用,假设gpio0是在设备树的其他部分定义的GPIO控制器节点 */  
    gpio-controller; /* 声明这是一个GPIO控制器节点 */  
    #gpio-cells = <2>; /* 每个GPIO引脚描述占用2个单元 */  
  
    example_device_gpios: example_gpio@0 {  
        /* 定义一个名为example_gpio的GPIO子节点,起始地址为0 */  
        reg = <0>; /* 指定GPIO引脚的起始地址,这里为0 */  
        #gpio-cells = <1>; /* 在这个子节点中,每个GPIO描述占用1个单元(通常对于单个引脚来说是这样) */  
        gpio-range = <&gpio0 0 1>; /* 从gpio0控制器的第0个引脚开始,使用1个引脚 */  
          
        /* 如果需要,还可以添加中断相关属性 */  
        // interrupt-parent = <&intc>; /* 指定中断控制器 */  
        // interrupts = <0 1>; /* 中断号和触发方式(例如,0表示中断号,1表示边沿触发) */  
  
        status = "okay"; /* 节点状态为有效 */  
    };  
};  
  
/* 注意:上述示例中的具体值(如#gpio-cells、gpio-range等)会根据实际的硬件和GPIO控制器设计而有所不同。 */  
/* 另外,设备树中的节点和属性命名也应遵循具体的硬件和驱动程序的要求。 */

解释

  • &gpio0:这是对设备树中其他地方定义的GPIO控制器节点的引用。
  • gpio-controller:声明这个节点是一个GPIO控制器节点。
  • #gpio-cells:指定每个GPIO描述所需的单元数。这取决于GPIO控制器的硬件设计。
  • example_device_gpios: example_gpio@0:定义了一个名为example_gpio的子节点,用于描述特定设备的GPIO配置。@0表示这个节点的起始地址(或索引)是0。
  • reg:指定了GPIO引脚的起始地址。在某些情况下,这个属性可能不是必需的,因为gpio-range已经提供了足够的信息。
  • #gpio-cells(在子节点中):在这个特定的子节点中,每个GPIO描述所需的单元数。对于单个引脚来说,这通常是1。
  • gpio-range:定义了从哪个GPIO控制器的哪个引脚开始使用,以及使用多少个引脚。
  • interrupt-parentinterrupts(可选):如果GPIO引脚用于中断,则需要指定中断控制器和中断配置。
  • status:表示节点的状态。通常是"okay"表示节点有效且应该被内核包含。

请注意,上述模板是一个简化的示例,实际设备树中的GPIO节点可能会根据具体的硬件和驱动程序要求包含更多的属性和子节点。在编写设备树时,应仔细参考硬件手册和驱动程序文档以确保正确的配置。

CODE-LED

/*************************************************************************
#    > File Name: pinctrl_gpio.c
#    > Author: HENG-W
#    > Mail: 
#    > Created Time: 2024年09月04日 星期三 18时36分27秒
#    > Describe: pinctrl_gpio子系统实验练习
#*************************************************************************/
/*头文件*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/mach/map.h>
/*
1.首先在 imx6ull-zealginat-emmc.dts 文件,
在根节点“/”下创建如下内容
	zealginat-pinctrl-gpio-led {
		compatible = "zealginat-pinctrl-gpio-led";
		status = "okay";
		pinctrl-0 = <&zealginat_zealgaint_pinctrl_led>;
		pinctrl-names = "pinctrl-gpio-led";
		pinctrl-gpio-led-pin = <&gpio1 3 GPIO_ACTIVE_LOW>;
	};

2. iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“zealginat_zealgaint_pinctrl_led”的子节点
    zealginat_zealgaint_pinctrl_led: ledgrp {
        fsl,pins = <
            MX6UL_PAD_GPIO1_IO03__GPIO1_IO03	0x10B0
        >;
    };
3.检查 PIN 是否被其他外设使用!!!
    检查 PIN 是否被其他外设使用!!!
    检查 PIN 是否被其他外设使用!!!
    检查 PIN 是否被其他外设使用!!!
    查找如下两个参数是否有重复
    MX6UL_PAD_GPIO1_IO03__GPIO1_IO03
    <&gpio1 3 GPIO_ACTIVE_LOW>;
*/
/*定义设备数量、设备名、开灯关灯宏定义*/
#define GPIO_DEV_COUNT 1
#define GPIO_DEV_NAME "pinctrl_gpio"
#define LED_ON  1
#define LED_OFF 0

/*定义pinctrl_gpio设备结构体*/
struct pinctrl_gpio_dev {
    dev_t devid;             /* 设备号*/
    int major;              /* 主设备号*/
    int minor;              /* 次设备号*/
    struct cdev cdev;       /* cdev结构体*/
    struct device *device;  /* 设备结构体指针*/
    struct class  *class;   /* 类结构体指针*/   
    struct device_node *node;   /* 设备树节点指针*/
    int led_gpio_num;       /* led gpio编号*/
    int led_status;         /* led状态*/
};
/*声明并初始化pinctrl_gpio_dev结构体*/  
static struct pinctrl_gpio_dev pinctrl_gpio_led;
/*打开设备*/
static int pinctrl_gpio_open(struct inode *inode, struct file *file)
{
    /*设置私有数据*/
    file->private_data = &pinctrl_gpio_led;
    printk("pinctrl_gpio_open\n");
    return 0;
}
/*向设备写数据*/
static ssize_t pinctrl_gpio_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    int retvale = 0;
    unsigned char databuf[1];
    unsigned char ledstatus;
    struct pinctrl_gpio_dev *dev = file->private_data; /* 获取私有数据*/
    /*4、从用户空间拷贝数据*/
    retvale = copy_from_user(databuf, buf, count);
    if (retvale< 0) {
        printk("copy_from_user failed\n");
        return -EFAULT;
    }
    ledstatus = databuf[0]; /* 获取用户输入 状态值*/
    if (ledstatus == LED_ON) {
        gpio_set_value(dev->led_gpio_num, LED_ON);
        dev->led_status = LED_ON;
    } else if (ledstatus == LED_OFF) {
        gpio_set_value(dev->led_gpio_num, LED_OFF);
        dev->led_status = LED_OFF;
    } else {
        printk("input error\n");
    }
    return 0;
}
/*关闭设备*/
static int pinctrl_gpio_release(struct inode *inode, struct file *file)
{
    printk("pinctrl_gpio_release\n");
    return 0;
}

/*设备操作函数结构体*/
static const struct file_operations pinctrl_gpio_fops = {
   .owner = THIS_MODULE,
   .open = pinctrl_gpio_open,
   .write = pinctrl_gpio_write,
   .release = pinctrl_gpio_release,
};

/*驱动程序加载函数*/
static int __init pinctrl_gpio_init(void)
{
    int ret;
    /*1、获取设备树节点*/
    pinctrl_gpio_led.node = of_find_node_by_path("/zealginat-pinctrl-gpio-led");
    if (!pinctrl_gpio_led.node) {
        printk("can't find zealginat-pinctrl-gpio-led node\n");
        return -ENOENT;
    }
    /*2、获取led gpio编号*/
    pinctrl_gpio_led.led_gpio_num = of_get_named_gpio(pinctrl_gpio_led.node, "pinctrl-gpio-led-pin", 0);
    if (pinctrl_gpio_led.led_gpio_num < 0) {
        printk("can't get pinctrl-gpio-led-pin\n");
        return -ENOENT;
    } else {
        printk("pinctrl-gpio-led-pin num:%d\n", pinctrl_gpio_led.led_gpio_num);
    }
    /*3、设置GPIO1_IO03为输出模式,输出高电平,默认关闭*/
    ret = gpio_request(pinctrl_gpio_led.led_gpio_num, "pinctrl-gpio-led-pin");
    if (ret < 0) {
        printk("can't request gpio %d\n", pinctrl_gpio_led.led_gpio_num);
        gpio_free(pinctrl_gpio_led.led_gpio_num);
        return -1;
    }
    ret = gpio_direction_output(pinctrl_gpio_led.led_gpio_num, LED_ON);/*设置为输出模式,默认关闭,高电平关闭所以是LED_ON  1 */
    if (ret < 0) {
        printk("can't set direction of gpio %d\n", pinctrl_gpio_led.led_gpio_num);
        gpio_free(pinctrl_gpio_led.led_gpio_num);
        return -1;
    }  
    /*4、申请字符设备号*/
    /*初始化主设备号*/
    pinctrl_gpio_led.major = 0;
    if (pinctrl_gpio_led.major)
    {
        pinctrl_gpio_led.devid = MKDEV(pinctrl_gpio_led.major, 0);
        ret = register_chrdev_region(pinctrl_gpio_led.devid, GPIO_DEV_COUNT, GPIO_DEV_NAME);    /* 申请设备号*/
    }else{
        ret = alloc_chrdev_region(&pinctrl_gpio_led.devid, 0, GPIO_DEV_COUNT, GPIO_DEV_NAME);   /* 申请设备号*/
        pinctrl_gpio_led.major = MAJOR(pinctrl_gpio_led.devid);                                 /* 获取主设备号*/
        pinctrl_gpio_led.minor = MINOR(pinctrl_gpio_led.devid);                                 /* 获取次设备号*/                          
    }
    if (ret<0)
    {
        printk("can't register char device\n");
        goto err_unregister_chrdev;
    }else{
        printk("pinctrl_gpio_led major num:%d, minor num:%d\n", pinctrl_gpio_led.major, pinctrl_gpio_led.minor);
    }
    /*5、创建字符设备*/
    pinctrl_gpio_led.cdev.owner = THIS_MODULE;
    cdev_init(&pinctrl_gpio_led.cdev, &pinctrl_gpio_fops);
    ret = cdev_add(&pinctrl_gpio_led.cdev, pinctrl_gpio_led.devid, GPIO_DEV_COUNT);
    if (ret < 0) {
        printk("can't add cdev\n");
        goto err_destroy_cdev;
    }else {
        printk("cdev add success\n");
    }
    /*6、创建类*/
    pinctrl_gpio_led.class = class_create(THIS_MODULE, GPIO_DEV_NAME);
    if (IS_ERR(pinctrl_gpio_led.class)) {
        printk("can't create class\n");
        goto err_destroy_class;
    } else {
        printk("class create success\n");
    }
    /*7、创建设备*/
    pinctrl_gpio_led.device = device_create(pinctrl_gpio_led.class, NULL, pinctrl_gpio_led.devid, NULL, GPIO_DEV_NAME);
    if (IS_ERR(pinctrl_gpio_led.device)) {
        printk("can't create device\n");
        goto err_device_create;     
    } else {
        printk("device create success\n");
    }

    return 0;
err_device_create:
    class_destroy(pinctrl_gpio_led.class);
    cdev_del(&pinctrl_gpio_led.cdev);
    unregister_chrdev_region(pinctrl_gpio_led.devid, GPIO_DEV_COUNT);
    return 0;
err_destroy_class:
    cdev_del(&pinctrl_gpio_led.cdev);
    unregister_chrdev_region(pinctrl_gpio_led.devid, GPIO_DEV_COUNT);
    return 0;
err_destroy_cdev:
    unregister_chrdev_region(pinctrl_gpio_led.devid, GPIO_DEV_COUNT);
    return 0;
err_unregister_chrdev:
    return 0;
}

/*驱动程序卸载载函数*/
static void __exit pinctrl_gpio_exit(void)
{
    gpio_free(pinctrl_gpio_led.led_gpio_num);
    device_destroy(pinctrl_gpio_led.class, pinctrl_gpio_led.devid);
    class_destroy(pinctrl_gpio_led.class);
    cdev_del(&pinctrl_gpio_led.cdev);
    unregister_chrdev_region(pinctrl_gpio_led.devid, GPIO_DEV_COUNT);
    printk("pinctrl_gpio_led exit\n");
}

module_init(pinctrl_gpio_init);
module_exit(pinctrl_gpio_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("HENG-W");
MODULE_DESCRIPTION("pinctrl_gpio driver for i.MX6ull");
MODULE_VERSION("1.0");


http://www.kler.cn/news/290334.html

相关文章:

  • 使用命令行编译cces工程(Blackfin 或 SHARC)
  • 如何在国内下载llama模型
  • Go入门:gin框架极速搭建图书管理系统
  • ubuntu24安装cuda和cudnn
  • 图像缩放操作
  • go-gin响应被覆盖为400,即使正常返回
  • 向对象八股文(长期跟新_整理收集_排版未优化_day04_20个)
  • IEC61968标准是什么?
  • 《Java面试题集中营》- Redis
  • OceanBase block_file与log过大 的问题
  • 污点、容忍和数据卷
  • 将x减到零的最小操作数问题
  • 应用层(Web与HTTP)
  • 什么是CAPTCHA?工作原理详解与应对方案
  • git 常用基础命令
  • 【MeterSphere】vnc连接不上selenium-chrome容器
  • 编译原理项目——C++实现C语言编译器输出为8086级汇编(代码/报告材料)
  • vue的侦听器、表单输入绑定、模版引用
  • Redis过期键监听
  • 使用Java实现LRU缓存和LFU缓存
  • Oracle 19C 数据操纵语言DML
  • ES6中try-catch
  • 智能工厂监控升级:Sovit2D大屏展示和ARM计算机的完美搭档
  • 注意力机制
  • LangChain学习
  • 收徒弟了。
  • Oracle 常用函数大全
  • 深入理解区间调度问题:从贪心算法到动态规划的加权优化
  • SprinBoot+Vue实验室考勤管理微信小程序的设计与实现
  • UEFI开发——编写一个简单的PPI