15、pinctrl 和 GPIO子系统
在没有使用这两个子系统之前,我们控制GPIO是直接操作寄存器来完成的,例如 LED灯,就是直接对寄存器进行操作
1、什么是pinctrl 和 gpio子系统
pinctrl 这个是linux用来控制引脚相关的
GPIO 通用输入输出
在引入设备树之后,GPIO子系统是通过pinctrl子系统来实现的,这一点要牢记。
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
1
2
3
4
5
6
7
8
9
10
11
12Port 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 的例子
1
2
3
4
5
6
7
8
9
10
11
12gpa0: 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 | 电源关闭模式下的驱动器强度配置 | |
1
2
3
4
5
6
7
8/*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中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41/* * 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 {
…
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 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/*在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 节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/* 添加自定义节点*/ 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 结构体 每个设备引脚控制状态
1
2
3
4
5
6
7
8
9
10#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状态
1
2
3
4
5
6struct pinctrl_state { struct list_head node; //states字段的列表节点 const char *name; //该状态的名称 struct list_head settings; //这个状态的设置列表 };
1、 获取pinctrl 句柄
1
2
3
4#include <linux/pinctrl/consumer.h> struct pinctrl *devm_pinctrl_get(struct device *dev) ; //返回值 成功返回pinctrl句柄 失败返回NULL
参数是dev是包含这个pin的device结构体即xxx这个设备的device
devm机制
内核自动分配内存,回收内存
2、获取引脚状态
1
2
3
4
5#include <linux/pinctrl/consumer.h> struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p, const char *name) ; // 返回值 成功返回pinctrl 状态 失败返回NULL
参数 | 功能 |
---|---|
p | pinctrl句柄 |
name | 配置的名字,用于pinctrl检索 |
3、设置引脚状态
1
2
3
4#include <linux/pinctrl/consumer.h> int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state) ; // 成功返回 0 失败返回错误码
参数 | 功能 |
---|---|
p | pinctrl句柄 |
state | 要设置的引脚状态 |
4、回收pinctrl句柄资源
1
2void devm_pinctrl_put(struct pinctrl *p);
5、实例
这里以LED为例
1、在pinctrl.dtsi 中添加pinctrl group节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/*添加自定义的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中添加节点
1
2
3
4
5
6
7
8myled: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的初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32int 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23pinctrl_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 中 设置复用关系和电气属性
1
2
3
4
5
6
7my_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
1
2
3
4
5
6
7
8
9
10
11
12
13myled: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编号
1
2
3
4
5#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管脚
1
2
3
4#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函数进行释放
1
2
3#include<linux/gpio.h> void gpio_free(unsigned gpio);
参数 | 描述 |
---|---|
gpio | 要释放的gpio号 |
4、gpio_direction_input 函数
作用: 此函数用于设置某个GPIO为输入
1
2
3
4#include <linux/gpio.h> int gpio_direction_input(unsigned gpio); //返回值 成功返回0 , 失败返回负数
参数 | 描述 |
---|---|
gpio | 要设置为输入的gpio的标号 |
5、gpio_direction_output 函数
作用:此函数用于设置某个GPIO为输出, 并且设置默认输出
1
2
3
4#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)
1
2
3
4#include <linux/gpio.h> int gpio_get_value(unsigned gpio); //返回值, 成功返回GPIO的值, 失败返回 负数
参数 | 描述 |
---|---|
gpio | 要获取的GPIO标号 |
7、gpio_set_value 函数
1
2
3#include<linux/gpio.h> void gpio_set_value(unsigned gpio, int value);
参数 | 描述 |
---|---|
gpio | 要设置的GPIO标号 |
value | 要设置的值 |
3、实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164#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");
最后
以上就是成就发卡最近收集整理的关于Linux驱动之 pinctrl和GPIO子系统的全部内容,更多相关Linux驱动之内容请搜索靠谱客的其他文章。
发表评论 取消回复