Linux驱动之 pinctrl和GPIO子系统


pinctrl 主要是用来进行pin脚的初始化

2、Linux pinctrl 子系统提供的功能是什么
​ (1) 管理系统中所有可以控制的pin, 在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin枚举所有可用的pin 脚 ,于是每个引脚就有的唯一的 ID (num) ,这个ID 很关键,对于以后的操作。

​ (2) 管理这些pin的复用(Multiplexing)。 对于SOC来说,其引脚除了配置成普通的GPIO之外,若干个引脚还可组成一个pin group,形成特定的功能。

​ (3) 配置这些pin的特性。例如使能或关闭引脚上的pull-up、pull-down电阻,配置引脚的driver strength 主要的功能, 1、设置引脚功能的复用 2、 配置pin脚的状态。 也就是对引脚进行初始化

GPIO子系统结构


pinctrl 子系统结构
pinctrl 子系统结构 与 GPIO子系统的结构和功能类似, 但是内部结构有所差异

 

 

pinctrl 子系统与GPIO的关系
在引入设备树之后,GPIO子系统是通过pinctrl子系统来实现的,这一点要牢记。

 

pinctrl 内部结构图
pinctrl的内部主要分为两部分功能:pin config管脚配置和pin mux管脚复用
驱动调用pin脚(不仅指GPIO,例如uart也需要用到pin脚),那么需要用到两部分硬件的功能:
A: 设置引脚功能的复用;
B:配置pin脚的状态。

 

3、pinctl 中的几个概念
与pinctl相关的设备树文件为exynos4412-pinctrl.dtsi 以下的操作均在该文件中

3.1 pin bank
​ 以引脚名为依据,这些引脚分为若干个组,每组称为一个bank

​ 例如 GPA 是一个bank

​ 每个bank中有若干个引脚例如 GPA0、GPA1 等等例如下面就被分为了11个bank

Port A(GPA) : 25-output port
Port B(GPB) : 9-input/output port
Port C(GPC) : 16-input/output port
Port D(GPD) : 16-input/output port
Port E(GPE) : 16-input/output port
Port F(GPF) : 8-input/output port
Port G(GPG) : 8-input/output port
Port H(GPH) : 15-input/output port
Port K(GPK) : 16-input/output port
Port L(GPL) : 7-input/output port
Port M(GPM) : 2-input port

之所以分成bank,主要是把特性相同的GPIO进行分组,方便控制。例如:这些bank中,只有GPF和GPG这两个bank上的引脚有中断功能,其他的都没有。

BANK属性    描述    实例
gpio-controller    说明该节点为 GPIO controller GPIO控制    
interrupt-controller    说明该节点为 interrupt controller 中断控制    
#gpio-cells    属性是一个GPIO controller的必须定义的属性,它描述了需要多少个cell来具体描述一个GPIO(这是和具体的GPIO controller相关的)。    
#interrupt-cells    和gpio-cells 的概念类似    
cell表示一个无符号的32位整数,xxxx-cells指定用多少个cell描述xxxx属性。

gpio-controller;

#gpio-cells = <2>;

“gpio-controller”表示这个节点是一个GPIO Controller,它下面有很多引脚。

“#gpio-cells = <2>”表示这个控制器下每一个引脚要用2个32位的数(cell)来描述。

为什么要用2个数?其实使用多个cell来描述一个引脚,这是GPIO Controller自己决定的。比如可以用其中一个cell来表示那是哪一个引脚,用另一个cell来表示它是高电平有效还是低电平有效,甚至还可以用更多的cell来示其他特性。

普遍的用法是,用第1个cell来表示哪一个引脚,用第2个cell来表示有效电平:

GPIO_ACTIVE_HIGH :高电平有效

GPIO_ACTIVE_LOW : 低电平有效

phandle(linux,phandle这个属性和phandle是一样的,只不过linux,phandle是old-style,多定义一个属性是为了兼容)定义了一个句柄,当其他的device node想要引用这个node的时候就可以使用该句柄。具体的例子参考下节client device的DTS的描述。

pin bank 的例子

        gpa0: gpa0 {
            gpio-controller;        // 表明该节点为GPIO控制
            #gpio-cells = <2>;        // 槽为2  这里是根据文档获得的
//#interrupt-cells – 与#address-cells 和 #size-cells相似,它表明连接此中断控制器的设备的interrupts属性的cell大小。    #address-cells 表示子节点的reg address属性的大小 如果为1 那么代表只有一个address
            interrupt-controller;    // 中断控制
            #interrupt-cells = <2>;    
        };
    gpj0: gpj0 { gpio-controller;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>; };

3.2 pin group
​ 以功能为依据,具有相同功能的引脚称为一个Group

比如4412的串口 TxD、RxD 引脚使用 GPA0_0, GPA0_1 那么这2个引脚可以列一组

属性名称    功能    实例
samsung,pins    用来引用管脚    [pin bank name]-[pin number within the bank]
samsung,pin-function    功能复用 设置功能    samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
samsung,pin-val    设置输出缓冲区的初始值    samsung,pin-val = <1> 例如使用LED时,输出1 高电平
samsung,pin-pud    上拉/下拉配置    samsung,pin-pud = <EXYNOS_PIN_PULL_NONE>;
samsung,pin-drv    驱动器强度配置    samsung,pin-drv = <EXYNOS4_PIN_DRV_LV1>;
samsung,pin-pud-pdn    掉电模式下的上拉/下拉配置    
samsung,pin-drv-pdn    电源关闭模式下的驱动器强度配置    
        /*pin group*/
        uart0_data: uart0-data {
            samsung,pins = "gpa0-0", "gpa0-1";              //引用管脚    [pin bank name]-[pin number within the bank]    
            samsung,pin-function = <EXYNOS_PIN_FUNC_2>;   // 设置功能复用模式
            samsung,pin-pud = <EXYNOS_PIN_PULL_NONE>;      // pud 上拉下拉配置
            samsung,pin-drv = <EXYNOS4_PIN_DRV_LV1>;      // 驱动强度为LV1
        };

这里的宏定义EXYNOS_PIN_FUNC_2 定义在了 include/dt-bindings/pinctrl/samsung.h中

/*
 * Samsung's Exynos pinctrl bindings
 *
 * Copyright (c) 2016 Samsung Electronics Co., Ltd.
 *        http://www.samsung.com
 * Author: Krzysztof Kozlowski <krzk@kernel.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
*/
#ifndef __DT_BINDINGS_PINCTRL_SAMSUNG_H__
#define __DT_BINDINGS_PINCTRL_SAMSUNG_H__

#define EXYNOS_PIN_PULL_NONE        0
#define EXYNOS_PIN_PULL_DOWN        1
#define EXYNOS_PIN_PULL_UP        3

#define S3C64XX_PIN_PULL_NONE        0
#define S3C64XX_PIN_PULL_DOWN        1
#define S3C64XX_PIN_PULL_UP        2

/* Pin function in power down mode */
#define EXYNOS_PIN_PDN_OUT0        0
#define EXYNOS_PIN_PDN_OUT1        1
#define EXYNOS_PIN_PDN_INPUT        2
#define EXYNOS_PIN_PDN_PREV        3

#define EXYNOS_PIN_FUNC_INPUT        0        //输入
#define EXYNOS_PIN_FUNC_OUTPUT        1        //输出
#define EXYNOS_PIN_FUNC_2        2
#define EXYNOS_PIN_FUNC_3        3
#define EXYNOS_PIN_FUNC_4        4
#define EXYNOS_PIN_FUNC_5        5
#define EXYNOS_PIN_FUNC_6        6
#define EXYNOS_PIN_FUNC_EINT        0xf
#define EXYNOS_PIN_FUNC_F        EXYNOS_PIN_FUNC_EINT


#endif /* __DT_BINDINGS_PINCTRL_SAMSUNG_H__ */

上面的FUNC 就是用来设置GPx_CON寄存器的

例如上面的pin group 使用的GPA0_0 、 GPA0_1 这个是串口的引脚,所以使用 FUNC_2 0x2 代表串口的RxD、TxD

3.3 State:
设备的某种状态, 比如内核自己定义的"default",“init”,“idel”,"sleep"状态;
也可以是其他自己定义的状态, 比如串口的"flow_ctrl"状态(使用流量控制);
设备处于某种状态时, 它可以使用若干个Group引脚。

这个是在设备树节点下使用的 , 上面的两个都是定义在了pinctrl节点中

serial@50000000 {undefined

pinctrl-names = “default”, “sleep”; /* 既是名字, 也称为state(状态) */
pinctrl-0 = <&uart0_data>;
pinctrl-1 = <&uart0_sleep>;
};

pinctrl-names中定义了2种state: default 和 sleep,
default 对应的引脚是: pinctrl-0, 它指定了使用哪些pin group: uart0_data
sleep 对应的引脚是: pinctrl-1, 它指定了使用哪些pin group: uart0_sleep

实例 添加LED pinctrl节点
因为使用LED需要用到GPL2 寄存器,而GPL2 BANK是定义已经定义好的,所以我们直接在pin group中使用即可

exynos4412-pinctrl-dtsi 文件

/*在pinctrl_1节点中*/
/* 因为gpl2定义在了pinctrl_1 节点里面*/
    /*添加自定义的LED pinctrl节点*/
    /*pin bank*/
pinctrl_1: pinctrl@11000000 {
            gpl1: gpl1 {
            gpio-controller;
            #gpio-cells = <2>;

            interrupt-controller;
            #interrupt-cells = <2>;
        };
        /*pin group*/
        my_led: my_led {
            samsung,pins = "gpl2-0"; //引用GPL2_0
            samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; // 设置为输出模式
            samsung,pin-val = <0x1>;         //初始值输出高电平            
        };
};

在设备树节点中,使用pin group 节点

    /* 添加自定义节点*/
    
    myled:myled {
        #address-cells = <1>;        
        #size-cells = <1>;
        compatible = "myledxxx";  //描述
//        reg = <0x11000100 0x4 0x11000104 0x1>; //GPL2CON GPL2DAT
        status = "okay";
        pinctrl-names = "default";    /* 即是名字也是 状态status*/
        pinctrl-0 = <&my_led>;        /* 当使用default状态时,就会使用 所引用节点的 配置*/
        /*这里的配置主要是将gpl2_0 引脚设置为GPIO*/
        
        /*管脚的描述信息  主要是*/
        gpios = <&gpl2 0 GPIO_ACTIVE_HIGH>;    //GPL2_0   GPIO_ACTIVE_HIGH 表示高电平有效
    };

参数    功能
pinctrl-names 是names    配置的名字,也是状态, 在driver中使用该参数中的 名字 就可以引用相应的配置对管脚进行初始化
pinctrl-0    当pinctrl-names的参数只有一个的时候,例如pinctrl-names = “default”; 在driver中 使用"default就会使用这个配置",
pinctrl-1    当pinctrl-names 的参数有两个的时候,pinctrl-names = “default”, “led_on”; 在driver中 使用"led_on"就会使用这个配置
4、pinctrl 的API
struct pinctrl 结构体 每个设备引脚控制状态
#include <linux/pinctrl/consumer.h> 
struct pinctrl {
    struct list_head node;    //全局列表节点
    struct device *dev;        //使用这个pin控制的设备
    struct list_head states;//这个设备的状态列表
    struct pinctrl_state *state;//当前状态
    struct list_head dt_maps;    //:从设备树动态解析的映射表块
    struct kref users;            //引用计数
};

struct pinctrl_state 设备的pinctrl状态
struct pinctrl_state {
    struct list_head node;    //states字段的列表节点
    const char *name;        //该状态的名称
    struct list_head settings;    //这个状态的设置列表
};

1、 获取pinctrl 句柄
#include <linux/pinctrl/consumer.h> 
struct pinctrl *devm_pinctrl_get(struct device *dev)  ;
//返回值 成功返回pinctrl句柄  失败返回NULL

参数是dev是包含这个pin的device结构体即xxx这个设备的device

devm机制

​ 内核自动分配内存,回收内存

2、获取引脚状态
#include <linux/pinctrl/consumer.h> 
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p, const char *name) ;
// 返回值  成功返回pinctrl 状态  失败返回NULL
   

参数    功能
p    pinctrl句柄
name    配置的名字,用于pinctrl检索
3、设置引脚状态
#include <linux/pinctrl/consumer.h> 
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state) ;
// 成功返回 0  失败返回错误码

参数    功能
p    pinctrl句柄
state    要设置的引脚状态
4、回收pinctrl句柄资源
void devm_pinctrl_put(struct pinctrl *p);

5、实例
这里以LED为例

1、在pinctrl.dtsi 中添加pinctrl group节点

        /*添加自定义的LED pinctrl节点*/
    /* 在pinctrl_1 中 ,因为 gpl2 在pinctrl_1中*/    
        my_led_on: my_led-on {
            samsung,pins = "gpl2-0"; //引用GPL2_0
            samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; // 设置为输出模式
            samsung,pin-val = <0x1>;         //初始值输出高电平            
            samsung,pin-pud = <EXYNOS_PIN_PULL_UP>;
        };
        my_led_off: my_led-off {
            samsung,pins = "gpl2-0"; //引用GPL2_0
            samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; // 设置为输出模式
            samsung,pin-val = <0x0>;         //初始值输出低电平            
            samsung,pin-pud = <EXYNOS_PIN_PULL_UP>;
        };


2、在 dts中添加节点

    myled:myled {
        compatible = "myledxxx";  //描述
        status = "okay";
        pinctrl-names = "default", "my_led_off";    /* 即是名字也是 状态status*/
        pinctrl-0 = <&my_led_on>;        /* 当使用default状态时,就会使用所引用节点的配置*/
        pinctrl-1 = <&my_led_off>;
    };

3、driver的probe函数中使用pinctrl API 进行pin的初始化

int ledprobe(struct platform_device *pdev)
{
    struct pinctrl * led_pinctrl;
    struct pinctrl_state * led_pinctrl_state;
    int ret;
    /*1、获取pinctrl句柄*/
    led_pinctrl = devm_pinctrl_get(&pdev->dev);
    if (NULL == led_pinctrl)
    {
        printk("devm pinctrl get is error!\n");
        return -1;
    }
    /*2、获取 指定的name的 state*/
    /*    pinctrl-name = "default, led_off";     即是名字也是 状态status*/
    led_pinctrl_state = pinctrl_lookup_state(led_pinctrl, "default"); //获取pinctrl-0的配置状态
    if (NULL == led_pinctrl_state)
    {
        printk("pinctrl lookup state is error!\n");
        return -1;
    }
    /*3、设置引脚状态*/
    ret = pinctrl_select_state(led_pinctrl, led_pinctrl_state);
    if (0 != ret)
    {
        printk("pinctrl select state is error!\n");
        return -1;
    }
    /*4、 句柄销毁*/
   // devm_pinctrl_put(led_pinctrl);  //     
    return 0;    
}

划重点:
对于probe函数中设置pinctrl,如果说在设备树中 pinctrl-name = “default”,“init”,“idel”,“sleep” 那么我们在设备树中就不需要调用pinctrl的相关API,内核会自动帮我们设置

pinctrl子系统总结
pin bank 用来设置引脚的功能 例如 GPIO功能

pin group 主要是用来设置pin的复用

pinctrl 子系统主要是用来对管脚进行配置和复用初始化 设置引脚的复用关系和电气属性

真正操作GPIO 还需要使用GPIO子系统来完成,在引入了设备树的内核中,GPIO子系统是由pinctrl子系统来实现的

 

16、GPIO子系统
​ 上一节我们学习了pinctrl子系统(主要是用来对管脚进行配置和复用初始化 复用关系和电气属性的初始化),linux内核提供了pinctrl子系统和gpio子系统用于GPIO驱动

当我们在pin bank中使用了gpio-controller的时候,我们就可以使用GPIO子系统来操作管脚了,也就是说pinctrl子系统初始化的时候把引脚设置为gpio的时候,那么就可以使用GPIO子系统来操作管脚
​ 当然,pinctrl子系统负责的不仅仅是GPIO子系统负责的就不仅仅是GPIO的驱动,而是所有pin脚的配置

pinctrl子系统是随着设备树的加入而加入的,依赖于设备树。GPIO子系统在之前的内核中也是存在的,但是pinctrl子系统的加入GPIO子系统也有很大的改变。

​ 在以前的内核版本中,如果要配置GPIO的话,一般要使用SOC厂家实现的配置函数,例如三星的配置函数s3c_gpio_cfgpin等,这样带来的问题就是各家有各家的接口函数,与实现方法,但是内核的代码复用率低而且开发者很难记住这么多函数,如果使用多种平台的话背函数是很麻烦的,所以在引入了设备树后对GPIO子系统进行了很大的改造,使用设备树来实现,并提供统一的接口。通过GPIO子系统功能主要实现引脚功能的配置,如设置为GPIO特殊功能,GPIO的方向,设置为中断等等。

probe代码中如何引用pinctrl
这是透明的,我们的驱动基本不用管。当设备切换状态时,对应的pinctrl就会被调用。

比如在platform_device和platform_driver的枚举过程中,流程如下:

这个经过实验,发现即使init 和 default同时存在,还是会优先使用default状态的引脚

1、设备树使用pinctrl和GPIO子系统描述一个GPIO
pinctrl.dtsi

pinctrl_1: pinctrl@11000000 {
        gpl2: gpl2 {
            gpio-controller;
            #gpio-cells = <2>;

            interrupt-controller;
            #interrupt-cells = <2>;
        };
        
        my_led_on: my_led-on {
            samsung,pins = "gpl2-0"; //引用GPL2_0
            samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; // 设置为输出模式
            samsung,pin-val = <0x1>;         //初始值输出高电平            
            samsung,pin-pud = <EXYNOS_PIN_PULL_UP>;        //电气属性, 上拉/下拉
        };
        my_led_off: my_led-off {
            samsung,pins = "gpl2-0"; //引用GPL2_0
            samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; // 设置为输出模式
            samsung,pin-val = <0x0>;         //初始值输出低电平            
            samsung,pin-pud = <EXYNOS_PIN_PULL_UP>;
        };
};

在 pin Bank中我们只关心两个属性

gpio-controller;

#gpio-cells = <2>;

“gpio-controller”表示这个节点是一个GPIO Controller,它下面有很多引脚。

“#gpio-cells = <2>”表示这个控制器下每一个引脚要用2个32位的数(cell)来描述。

为什么要用2个数?其实使用多个cell来描述一个引脚,这是GPIO Controller自己决定的。比如可以用其中一个cell来表示那是哪一个引脚,用另一个cell来表示它是高电平有效还是低电平有效,甚至还可以用更多的cell来示其他特性。

普遍的用法是,用第1个cell来表示哪一个引脚,用第2个cell来表示有效电平:

GPIO_ACTIVE_HIGH : 高电平有效

GPIO_ACTIVE_LOW : 低电平有效

pin group 中 设置复用关系和电气属性

    my_led_on: my_led-on {
        samsung,pins = "gpl2-0"; //引用GPL2_0
        samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; // 设置为输出模式
        samsung,pin-val = <0x1>;         //初始值输出高电平            
        samsung,pin-pud = <EXYNOS_PIN_PULL_UP>;        //电气属性, 上拉/下拉
    };

定义GPIO Controller是芯片厂家的事,我们怎么引用某个引脚呢?在自己的设备节点中使用属性" [ name- ]gpios",示例如下:

exynos4412-itop-elit.dts

    myled:myled {
        compatible = "myledxxx";  //描述
        reg = <0x11000100 0x4 0x11000104 0x1>; //GPL2CON GPL2DAT
        status = "okay";
        /*pinctrl*/
        pinctrl-names = "default", "my_led_off";    /* 即是名字也是 状态status*/
        pinctrl-0 = <&my_led_on>;        /* 当使用default状态时,就会使用所引用节点的配置*/
        pinctrl-1 = <&my_led_off>;
        /*GPIO*/    
        led-gpios = <&gpl2 0 GPIO_ACTIVE_HIGH>;    //GPL2_0   GPIO_ACTIVE_HIGH 表示高电平有效
        //gpios  = <&gpl2 0 GPIO_ACTIVE_HIGH>;
    };

上图中,可以使用gpios属性,也可以使用name-gpios属性。

2、GPIO子系统提供的API函数
1、of_get_named_gpio 函数 获取GPIO标号
作用:此函数获取 GPIO 编号,因为Linux内核中关于GPIO的API函数都要使用GPIO编号,此函数会将设备数中类似 <&gpl2 0 GPIO_ACTIVE_HIGH> 的属性信息转换为对应的GPIO编号

#include<linux/of_gpio.h>

int of_get_named_gpio(struct device_node *np,const char *propname, int index);
// 返回值  成功返回 GPIO编号,  失败返回 负数

参数    描述
np    设备节点
propname    包含要获取GPIO信息的属性名
index    因为一个属性里面可能包含多个GPIO,此参数指定要获取哪个GPIO的编号,如果只有一个GPIO信息的话此参数为0

2、gpio_request 函数
作用:用于申请一个GPIO管脚

#include<linux/gpio.h>
int gpio_request(unsigned gpio, const char *label);
// 返回值  成功 0,  失败返回 非0

参数    描述
gpio    要申请的gpio标号,使用of_get_named_gpio函数从设备树中获取指定GPIO属性信息,此函数会返回这个GPIO的标号
label    给这个gpio标号 起个名字 最好起与硬件相关的名字

3、gpio_free 函数
作用: 如果不使用某个GPIO了,那么就可以调用gpio_free函数进行释放

#include<linux/gpio.h>
void gpio_free(unsigned gpio);

参数    描述
gpio    要释放的gpio号

4、gpio_direction_input 函数
作用: 此函数用于设置某个GPIO为输入

#include <linux/gpio.h>
int gpio_direction_input(unsigned gpio);
//返回值  成功返回0 , 失败返回负数    

参数    描述
gpio    要设置为输入的gpio的标号

5、gpio_direction_output 函数
作用:此函数用于设置某个GPIO为输出, 并且设置默认输出

#include <linux/gpio.h>
int gpio_direction_output(unsigned gpio, int value);
//返回值 成功返回0 ,失败返回 负数

参数    描述
gpio    要设置为输出的GPIO标号
value    GPIO 默认输出值。 例如 1 默认输出高电平, 0 输出低电平

6、gpio_get_value 函数
作用: 此函数用于获取某个GPIO的值(0 或 1)

#include <linux/gpio.h>
int gpio_get_value(unsigned gpio);
//返回值, 成功返回GPIO的值, 失败返回 负数

参数    描述
gpio    要获取的GPIO标号

7、gpio_set_value 函数
#include<linux/gpio.h>
void gpio_set_value(unsigned gpio, int value);

参数    描述
gpio    要设置的GPIO标号
value    要设置的值

3、实例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/pinctrl/consumer.h> 
#include <dt-bindings/pinctrl/samsung.h>    
#include <linux/of_gpio.h>        

/*  字符设备框架
    1、注册 platform driver
    2、构建file_operations
    3、获取硬件资源
    4、注册字符设备驱动
    4、生成字符设备节点
*/  

struct device *device_cdev; 
struct class *class_cdev;

int GPIO_ID;

ssize_t led_read (struct file *file, char __user * user, size_t size, loff_t * loff )
{
    printk("read is success!\n");
    return 0;
}
ssize_t led_write (struct file *file, const char __user *user, size_t size, loff_t * loff)
{   char buf[128] = {0};
    int ret ;
    printk("write is success!\n");
    ret  = copy_from_user(buf, user, size);
    if (0 != ret)
    {
        printk("copy form user is error!\n");
        return ret;
    }
    if (buf[0] == 1 || buf[0] == 0)
        gpio_set_value(GPIO_ID, buf[0]);
    return 0;
}
int led_open(struct inode *inode, struct file *file)
{
    printk("open is success!\n");
    return 0;
}
int led_release (struct inode *inode, struct file *file)
{
    printk("release is success!\n");
    return 0;
}

/*2、获取硬件资源 */
int ledprobe(struct platform_device *pdev)
{
    int ret;
    /*1、获取GPIO号*/
    GPIO_ID = of_get_named_gpio(pdev->dev.of_node, "led-gpios", 0);//index=0 ,因为在设备树中只引用了一个
    if (GPIO_ID < 0)
    {
        printk("of get named gpio is error!\n");
        return -1;
    }
    /*2、申请一个GPIO管脚*/
    ret = gpio_request(GPIO_ID, "led_gpio");
    if (ret != 0)
    {
        printk("gpio request is error!\n");
        return -1;
    }
    /*3、 将管脚设置为输出*/
    /* 这里先不设置,因为在pinctrl复用中已经将管脚设置为了OUTPUT*/
    return 0;    
}
int ledremove(struct platform_device *pdev)
{
    return 0;
}
struct of_device_id of_match_table = {      // 与设备树节点进行匹配
    .compatible = "myledxxx"
};

/*1、初始化platform driver*/
struct platform_driver pdev = {
    .probe = ledprobe,          // 与 of_match_table 匹配成功后进入probe函数获取硬件资源
    .remove = ledremove,
    .driver = {
        .name = "myledxxx",     //无设备树时 使用.name 与device进行匹配
        .owner = THIS_MODULE,
        .of_match_table = &of_match_table,
    }
};
//3、注册字符设备驱动
/*3.1 分配设备号*/
dev_t dev_number;
/*3.2 定义cdev*/
struct cdev cdev_;
/*3.3 构建file_operation结构体*/
struct file_operations fop = {
    .owner = THIS_MODULE,
    .open    = led_open,
    .release = led_release,
    .write   = led_write,
    .read    = led_read,
};
static int char_driver_init(void)
{
    /*1、注册platform driver*/
    int ret = platform_driver_register(&pdev);
    if (0 != ret)
    {   
        printk("platform driver register is error!\n");
        return -1;
    }
    /*3.1 分配设备号(动态分配设备号)*/
    ret = alloc_chrdev_region(&dev_number, 0, 1, "my_led");
    if (0 != ret)
    {
        printk("alloc chrdev region is error!\n");
        return ret;
    }
    /*3.4 初始化cdev*/
    cdev_.owner = THIS_MODULE;
    cdev_init(&cdev_, &fop);
    /*3.5 注册字符设备到内核*/
    ret = cdev_add(&cdev_, dev_number, 1);
    if (0 != ret)
    {
        printk("cdev add is error!\n");
        return -1;
    }
    /*4、生成设备节点*/
    /*4.1 创建字符设备类*/
   class_cdev =  class_create(THIS_MODULE, "my_led");
    if (NULL == class_cdev)
    {
        printk("class create is error!\n");
        return -1;
    }
    /*生成设备节点*/
    device_cdev = device_create (class_cdev, NULL, dev_number, NULL,  "my_led");
    if (NULL == device_cdev)
    {
        printk("device create is error!\n");
    }
    return 0;
};

static void char_driver_exit(void)
{
    gpio_free(GPIO_ID);                     //释放GPIO
    device_destroy(class_cdev, dev_number); // 卸载设备节点
    class_destroy(class_cdev);              //卸载设备类
    cdev_del(&cdev_);                       //卸载cdev
    unregister_chrdev_region(dev_number, 1);// 注销设备号 
    platform_driver_unregister(&pdev);       // 注销platform driver
   
}
module_init(char_driver_init);
module_exit(char_driver_exit);
MODULE_LICENSE("GPL");
————————————————
版权声明:本文为CSDN博主「小袁OVO」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43937576/article/details/116131178

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值