顺义深圳网站建设公司,成全免费观看在线看,重庆蜡像制作,厦门网盛网站开发目录pinctrl子系统pinctrl子系统简介(半导体厂商写的)I.MX6ULL的pinctrl子系统驱动分析0、通过compatbile属性查找对应驱动文件1、PIN配置信息详解(获取寄存器地址)2、PIN 驱动程序流程讲解(流程图#xff0c;了解)设备树中添加pinctrl节点模板(半导体厂商瑞芯微、海思厂商写的…
目录pinctrl子系统pinctrl子系统简介(半导体厂商写的)I.MX6ULL的pinctrl子系统驱动分析0、通过compatbile属性查找对应驱动文件1、PIN配置信息详解(获取寄存器地址)2、PIN 驱动程序流程讲解(流程图了解)设备树中添加pinctrl节点模板(半导体厂商瑞芯微、海思厂商写的)gpio子系统驱动分析gpio子系统简介I.MX6ULL 的gpio子系统驱动gpio子系统API函数(申请、释放、输入、输出、读写值) 重要设备树中添加gpio节点模板与gpio 相关的OF函数硬件原理图分析实验程序编写(使用子系统点灯类似HAL库调用API操作GPIO)修改设备树文件LED灯驱动程序编写编写测试APP运行测试编译驱动程序和测试APP运行测试上一章我们编写了基于设备树的LED 驱动但是驱动的本质还是没变都是配置LED 灯所使用的GPIO 寄存器驱动开发方式和裸机基本没啥区别这种配置方式比较繁琐。Linux 内核提供了pinctrl和gpio子系统用于GPIO 驱动简化驱动开发。
pinctrl子系统
pinctrl子系统简介(半导体厂商写的)
Linux 驱动讲究驱动分离与分层pinctrl 和gpio 子系统就是驱动分离与分层思想下的产物驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架。
我们先来回顾一下上一章是怎么初始化LED 灯所使用的GPIO步骤如下
①、修改设备树添加相应的节点节点里面重点是设置reg 属性reg 属性包括了GPIO相关寄存器。②、获取reg 属性中IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 和 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 这两个寄存器地址并且初始化这两个寄存器这两个寄存器用于设置GPIO1_IO03 这个PIN 的复用功能、上下拉、速度等。③、在②里面将GPIO1_IO03 这个PIN 复用为了GPIO 功能因此需要设置GPIO1_IO03这个GPIO 相关的寄存器也就是GPIO1_DR 和GPIO1_GDIR 这两个寄存器。
pinctrl 子系统主要工作内容如下
①、获取设备树中pin 信息。②、根据获取到的pin 信息来设置pin 的复用功能③、根据获取到的pin 信息来设置pin 的电气特性比如上/下拉、速度、驱动能力等。
对于我们使用者来讲只需要在设备树里面设置好某个pin的相关属性即可其他的初始化工作均由pinctrl 子系统来完成(半导体厂商帮我们完成)pinctrl 子系统源码目录为drivers/pinctrl。
I.MX6ULL的pinctrl子系统驱动分析
0、通过compatbile属性查找对应驱动文件 1、PIN配置信息详解(获取寄存器地址)
要使用pinctrl 子系统我们需要在设备树里面设置PIN 的配置信息毕竟pinctrl 子系统要根据你提供的信息来配置PIN 功能一般会在设备树里面创建一个节点来描述PIN 的配置信息。打开imx6ull.dtsi 文件找到一个叫做iomuxc 的节点如下所示
756 iomuxc: iomuxc020e0000 {
757 compatible fsl,imx6ul-iomuxc;
758 reg 0x020e0000 0x4000;
759 };iomuxc 节点就是I.MX6ULL 的IOMUXC 外设对应的节点看起来内容很少继续打开imx6ull-alientek-emmc.dts找到如下所示内容
311 iomuxc {
312 pinctrl-names default;
313 pinctrl-0 pinctrl_hog_1;
314 imx6ul-evk {
315 pinctrl_hog_1: hoggrp-1 {
316 fsl,pins
317 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
318 MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
319 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
320 MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
321 ;
322 };
......
371 pinctrl_flexcan1: flexcan1grp{
372 fsl,pins
373 MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020
374 MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020
375 ;
376 };
......
587 pinctrl_wdog: wdoggrp {
588 fsl,pins
589 MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY 0x30b0
590 ;
591 };
592 };
593 };示例代码45.1.2.2 就是向iomuxc 节点追加数据不同的外设使用的PIN 不同、其配置也不同将某个外设所使用的所有PIN 都组织在一个子节点里面。示例代码45.1.2.2 中pinctrl_hog_1 子节点就是和热插拔有关的PIN 集合比如USB OTG 的ID 引脚。
pinctrl_flexcan1 子节点是flexcan1 这个外设所使用的PINpinctrl_wdog 子节点是wdog 外设所使用的PIN。如果需要在iomuxc 中添加我们自定义外设的PIN那么需要新建一个子节点然后将这个自定义外设的所有PIN 配置信息都放到这个子节点中。
将其与示例代码45.1.2.1 结合起来就可以得到完成的iomuxc 节点如下所示
1 iomuxc: iomuxc020e0000 {
2 compatible fsl,imx6ul-iomuxc;
3 reg 0x020e0000 0x4000;
4 pinctrl-names default;
5 pinctrl-0 pinctrl_hog_1;
6 imx6ul-evk {
7 pinctrl_hog_1: hoggrp-1 {
8 fsl,pins
9 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
10 MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
11 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
12 MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
13 ;
......
16 };
17 };
18 };第2 行compatible 属性值为“fsl,imx6ul-iomuxc”前面讲解设备树的时候说过Linux 内核会根据compatbile 属性值来查找对应的驱动文件所以我们在Linux 内核源码中全局搜索字符串“fsl,imx6ul-iomuxc”就会找到I.MX6ULL 这颗SOC 的pinctrl 驱动文件。稍后我们会讲解这个pinctrl 驱动文件。 第9~12 行pinctrl_hog_1 子节点所使用的PIN 配置信息我们就以第9 行的UART1_RTS_B这个PIN 为例讲解一下如何添加PIN 的配置信息UART1_RTS_B 的配置信息如下
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059首先说明一下UART1_RTS_B 这个PIN 是作为SD 卡的检测引脚也就是通过此PIN 就可以检测到SD 卡是否有插入。UART1_RTS_B 的配置信息分为两部分 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 和0x17059
我们重点来看一下这两部分是什么含义前面说了对于一个PIN 的配置主要包括两方面一个是设置这个PIN 的复用功能另一个就是设置这个PIN 的电气特性。所以我们可以大胆的猜测UART1_RTS_B 的这两部分配置信息一个是设置UART1_RTS_B 的复用功能一个是用来设置UART1_RTS_B 的电气特性。
首先来看一下MX6UL_PAD_UART1_RTS_B__GPIO1_IO19这是一个宏定义定义在文件 arch/arm/boot/dts/imx6ul-pinfunc.h 中imx6ull.dtsi 会引用imx6ull-pinfunc.h 这个头文件而imx6ull-pinfunc.h 又会引用imx6ul-pinfunc.h 这个头文件绕啊绕。从这里可以看出可以在设备树中引用C 语言中.h 文件中的内容。MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 的宏定义内容如下
190 #define MX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS 0x0090 0x031C 0x0620 0x0 0x3
191 #define MX6UL_PAD_UART1_RTS_B__UART1_DTE_CTS 0x0090 0x031C 0x0000 0x0 0x0
192 #define MX6UL_PAD_UART1_RTS_B__ENET1_TX_ER 0x0090 0x031C 0x0000 0x1 0x0
193 #define MX6UL_PAD_UART1_RTS_B__USDHC1_CD_B 0x0090 0x031C 0x0668 0x2 0x1
194 #define MX6UL_PAD_UART1_RTS_B__CSI_DATA05 0x0090 0x031C 0x04CC 0x3 0x1
195 #define MX6UL_PAD_UART1_RTS_B__ENET2_1588_EVENT1_OUT 0x0090 0x031C 0x0000 0x4 0x0
196 #define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
197 #define MX6UL_PAD_UART1_RTS_B__USDHC2_CD_B 0x0090 0x031C 0x0674 0x8 0x2示例代码45.1.2.4 中一共有8 个以“MX6UL_PAD_UART1_RTS_B”开头的宏定义大家仔细观察应该就能发现这8 个宏定义分别对应UART1_RTS_B 这个PIN 的8 个复用IO。查阅《I.MX6ULL 参考手册》可以知UART1_RTS_B 的可选复用IO 如图45.1.2.1 所示 示例代码196 行的宏定义MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 表示将UART1_RTS_B 这个IO 复用为GPIO1_IO19。此宏定义后面跟着5 个数字也就是这个宏定义的具体值如下所示
0x0090 0x031C 0x0000 0x5 0x0这5 个值的含义如下所示
mux_reg conf_reg input_reg mux_mode input_val综上所述可知
0x0090mux_reg 寄存器偏移地址设备树中的iomuxc 节点就是IOMUXC 外设对应的节点根据其reg 属性可知IOMUXC 外设寄存器起始地址为0x020e0000 。因此0x020e00000x00900x020e0090IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 寄存器地址 正好是0x020e0090 大家可以在《IMX6ULL 参考手册》中找到 IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 这个寄存器的位域图如图45.1.2.2 所示 因此可知0x020e0000mux_reg 就是PIN 的复用寄存器地址。 0x031Cconf_reg 寄存器偏移地址和mux_reg 一样0x020e00000x031c0x020e031c这个就是寄存器IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的地址。 0x0000input_reg 寄存器偏移地址有些外设有input_reg 寄存器有input_reg 寄存器的外设需要配置input_reg 寄存器。没有的话就不需要设置UART1_RTS_B 这个PIN 在做GPIO1_IO19 的时候是没有input_reg 寄存器因此这里intput_reg 是无效的。
0x5 mux_reg 寄存器值在这里就相当于设置 IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 寄存器为0x5也即是设置UART1_RTS_B 这个PIN 复用为GPIO1_IO19。 0x0input_reg 寄存器值在这里无效。
这就是宏MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 的含义看的比较仔细的同学应该会发现并没有conf_reg 寄存器的值config_reg 寄存器是设置一个PIN 的电气特性的这么重要的寄存器怎么没有值呢回到示例代码45.1.2.3 中第9 行的内容如下所示
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 我们上面已经分析了就剩下了一个0x17059反应快的同学应该已经猜出来了0x17059 就是conf_reg 寄存器值此值由用户自行设置通过此值来设置一个IO 的上/下拉、驱动能力和速度等。在这里就相当于设置寄存器 IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的值为0x17059。
2、PIN 驱动程序流程讲解(流程图了解)
本小节会涉及到Linux 驱动分层与分离、平台设备驱动等还未讲解的知识所以本小节教程可以不用看不会影响后续的实验。如果对Linux 内核的pinctrl 子系统实现原理感兴趣的话可以看本小节。
所有的东西都已经准备好了包括寄存器地址和寄存器值Linux 内核相应的驱动文件就会根据这些值来做相应的初始化。接下来就找一下哪个驱动文件来做这一件事情iomuxc 节点中compatible 属性的值为“fsl,imx6ul-iomuxc”在Linux 内核中全局搜索“fsl,imx6ul-iomuxc”字符串就会找到对应的驱动文件。在文件drivers/pinctrl/freescale/pinctrl-imx6ul.c 中有如下内容
326 static struct of_device_id imx6ul_pinctrl_of_match[] {
327 { .compatible fsl,imx6ul-iomuxc, .data imx6ul_pinctrl_info, },
328 { .compatible fsl,imx6ull-iomuxc-snvs, .data imx6ull_snvs_pinctrl_info, },
329 { /* sentinel */ }
330 };
331
332 static int imx6ul_pinctrl_probe(struct platform_device *pdev)
333 {
334 const struct of_device_id *match;
335 struct imx_pinctrl_soc_info *pinctrl_info;
336
337 match of_match_device(imx6ul_pinctrl_of_match, pdev-dev);
338
339 if (!match)第326~330 行of_device_id 结构体数组第四十三章讲解设备树的时候说过了of_device_id里面保存着这个驱动文件的兼容性值设备树中的compatible 属性值会和of_device_id 中的所有兼容性字符串比较查看是否可以使用此驱动。imx6ul_pinctrl_of_match 结构体数组一共有两个兼容性字符串分别为“fsl,imx6ul-iomuxc”和“fsl,imx6ull-iomuxc-snvs”因此iomuxc 节点与此驱动匹配所以pinctrl-imx6ul.c 会完成I.MX6ULL 的PIN 配置工作。
第347~355 行platform_driver 是平台设备驱动这个是我们后面章节要讲解的内容platform_driver 是个结构体有个probe 成员变量。在这里大家只需要知道当设备和驱动匹配成功以后platform_driver 的probe 成员变量所代表的函数就会执行在353 行设置probe 成员变量为imx6ul_pinctrl_probe 函数因此在本章实验中imx6ul_pinctrl_probe 这个函数就会执行可以认为imx6ul_pinctrl_probe 函数就是I.MX6ULL 这个SOC 的PIN 配置入口函数。以此为入口如图45.1.2.3 所示的函数调用路径 在图45.1.2.3 中函数imx_pinctrl_parse_groups 负责获取设备树中关于PIN 的配置信息也就是我们前面分析的那6个u32 类型的值。处理过程如下所示
488 /*
489 * Each pin represented in fsl,pins consists of 5 u32 PIN_FUNC_ID
490 * and 1 u32 CONFIG, so 24 types in total for each pin.
491 */第496 和497 行设备树中的mux_reg 和conf_reg 值会保存在info 参数中input_reg、mux_mode、input_val 和config 值会保存在grp 参数中。 第560~564 行获取mux_reg、conf_reg、input_reg、mux_mode 和input_val 值。 第570 行获取config 值。 接下来看一下函数pinctrl_register此函数用于向Linux 内核注册一个PIN 控制器此函数原型如下
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
struct device *dev,
void *driver_data)参数pctldesc 非常重要因为此参数就是要注册的PIN 控制器PIN 控制器用于配置SOC的PIN 复用功能和电气特性。参数pctldesc 是pinctrl_desc 结构体类型指针pinctrl_desc 结构体如下所示
128 struct pinctrl_desc {
129 const char *name;
130 struct pinctrl_pin_desc const *pins;
131 unsigned int npins;
132 const struct pinctrl_ops *pctlops;
133 const struct pinmux_ops *pmxops;
134 const struct pinconf_ops *confops;
135 struct module *owner;
136 #ifdef CONFIG_GENERIC_PINCONF
137 unsigned int num_custom_params;
138 const struct pinconf_generic_params *custom_params;
139 const struct pin_config_item *custom_conf_items;
140 #endif
141 };第132~124 行这三个“_ops”结构体指针非常重要因为这三个结构体就是PIN 控制器的“工具”这三个结构体里面包含了很多操作函数通过这些操作函数就可以完成对某一个PIN 的配置。pinctrl_desc 结构体需要由用户提供结构体里面的成员变量也是用户提供的。但是这个用户并不是我们这些使用芯片的程序员而是半导体厂商半导体厂商发布的Linux 内核源码中已经把这些工作做完了。比如在imx_pinctrl_probe 函数中可以找到如下所示代码
648 int imx_pinctrl_probe(struct platform_device *pdev,
649 struct imx_pinctrl_soc_info *info)
650 {
651 struct device_node *dev_np pdev-dev.of_node;
652 struct device_node *np;
653 struct imx_pinctrl *ipctl;
654 struct resource *res;
655 struct pinctrl_desc *imx_pinctrl_desc;
......
663
664 imx_pinctrl_desc devm_kzalloc(pdev-dev, sizeof(*imx_pinctrl_desc),
665 GFP_KERNEL);
666 if (!imx_pinctrl_desc)
667 return -ENOMEM;
......
705
706 imx_pinctrl_desc-name dev_name(pdev-dev);
707 imx_pinctrl_desc-pins info-pins;
708 imx_pinctrl_desc-npins info-npins;
709 imx_pinctrl_desc-pctlops imx_pctrl_ops;
710 imx_pinctrl_desc-pmxops imx_pmx_ops;
711 imx_pinctrl_desc-confops imx_pinconf_ops;
712 imx_pinctrl_desc-owner THIS_MODULE;
......
723 ipctl-pctl pinctrl_register(imx_pinctrl_desc, pdev-dev, ipctl);
......
732 }第655 行定义结构体指针变量imx_pinctrl_desc。 第664 行向指针变量imx_pinctrl_desc 分配内存。 第706~712 行初始化imx_pinctrl_desc 结构体指针变量重点是pctlops、pmxops 和confops这三个成员变量分别对应imx_pctrl_ops、imx_pmx_ops 和imx_pinconf_ops 这三个结构体。 第723 行调用函数pinctrl_register 向Linux 内核注册imx_pinctrl_desc注册以后Linux 内核就有了对I.MX6ULL 的PIN 进行配置的工具。 imx_pctrl_ops、imx_pmx_ops 和imx_pinconf_ops 这三个结构体定义如下
174 static const struct pinctrl_ops imx_pctrl_ops {
175 .get_groups_count imx_get_groups_count,
176 .get_group_name imx_get_group_name,
177 .get_group_pins imx_get_group_pins,
178 .pin_dbg_show imx_pin_dbg_show,
179 .dt_node_to_map imx_dt_node_to_map,
180 .dt_free_map imx_dt_free_map,
181
182 };
......
374 static const struct pinmux_ops imx_pmx_ops {
375 .get_functions_count imx_pmx_get_funcs_count,
376 .get_function_name imx_pmx_get_func_name,
377 .get_function_groups imx_pmx_get_groups,
378 .set_mux imx_pmx_set,
379 .gpio_request_enable imx_pmx_gpio_request_enable,
380 .gpio_set_direction imx_pmx_gpio_set_direction,
381 };
......
481 static const struct pinconf_ops imx_pinconf_ops {
482 .pin_config_get imx_pinconf_get,
483 .pin_config_set imx_pinconf_set,
484 .pin_config_dbg_show imx_pinconf_dbg_show,
485 .pin_config_group_dbg_show imx_pinconf_group_dbg_show,
486 };示例代码45.1.2.9 中这三个结构体下的所有函数就是I.MX6ULL 的PIN 配置函数我们就此打住不再去分析这些函数了否则本章就没完没了了有兴趣的可以去看一下。
设备树中添加pinctrl节点模板(半导体厂商瑞芯微、海思厂商写的)
我们已经对pinctrl 有了比较深入的了解接下来我们学习一下如何在设备树中添加某个外设的PIN 信息。关于I.MX 系列SOC 的pinctrl 设备树绑定信息可以参考文档 Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt。这里我们虚拟一个名为“test”的设备test 使用了GPIO1_IO00 这个PIN 的GPIO 功能pinctrl 节点添加过程如下
1、创建对应的节点 同一个外设的PIN 都放到一个节点里面打开imx6ull-alientek-emmc.dts在iomuxc 节点中的“imx6ul-evk”子节点下添加“pinctrl_test”节点注意节点前缀一定要为“pinctrl_”。添加完成以后如下所示
1 pinctrl_test: testgrp {
2 /* 具体的PIN信息*/
3 };2、添加“fsl,pins”属性 设备树是通过属性来保存信息的因此我们需要添加一个属性属性名字一定要为“fsl,pins”因为对于I.MX 系列SOC 而言pinctrl 驱动程序是通过读取“fsl,pins”属性值来获取PIN 的配置信息完成以后如下所示
1 pinctrl_test: testgrp {
2 fsl,pins
3 /* 设备所使用的PIN配置信息*/
4 ;
5 };3、在“fsl,pins”属性中添加PIN 配置信息 最后在“fsl,pins”属性中添加具体的PIN 配置信息完成以后如下所示
1 pinctrl_test: testgrp {
2 fsl,pins
3 MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config是具体设置值*/
4 ;
5 };至此我们已经在imx6ull-alientek-emmc.dts 文件中添加好了test 设备所使用的PIN 配置信息。
gpio子系统驱动分析
gpio子系统简介
上一小节讲解了pinctrl 子系统pinctrl 子系统重点是设置PIN(有的SOC 叫做PAD)的复用和电气属性如果pinctrl 子系统将一个PIN 复用为GPIO 的话那么接下来就要用到gpio 子系统了。
gpio 子系统顾名思义就是用于初始化GPIO 并且提供相应的API 函数比如设置GPIO为输入输出、中断、读取GPIO 的值等。
gpio 子系统的主要目的就是方便驱动开发者使用gpio驱动开发者在设备树中添加gpio 相关信息然后就可以在驱动程序中使用gpio 子系统提供的API函数来操作GPIOLinux 内核向驱动开发者屏蔽掉了GPIO 的设置过程极大的方便了驱动开发者使用GPIO。 I.MX6ULL 的gpio子系统驱动
1、设备树中的gpio 信息
I.MX6ULL-ALPHA 开发板上的UART1_RTS_B 做为SD 卡的检测引脚UART1_RTS_B 复用为GPIO1_IO19通过读取这个GPIO 的高低电平就可以知道SD 卡有没有插入。首先肯定是将UART1_RTS_B 这个PIN 复用为GPIO1_IO19并且设置电气属性也就是上一小节讲的pinctrl 节点。打开imx6ull-alientek-emmc.dtsUART1_RTS_B 这个PIN 的pincrtl 设置如下
316 pinctrl_hog_1: hoggrp-1 {
317 fsl,pins
318 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
......
322 ;
323 };第318 行设置UART1_RTS_B 这个PIN 为GPIO1_IO19。
pinctrl 配置好以后就是设置gpio 了SD 卡驱动程序通过读取GPIO1_IO19 的值来判断SD卡有没有插入但是SD 卡驱动程序怎么知道CD 引脚连接的GPIO1_IO19 呢肯定是需要设备树告诉驱动啊在设备树中SD 卡节点下添加一个属性来描述SD 卡的CD 引脚就行了SD卡驱动直接读取这个属性值就知道SD 卡的CD 引脚使用的是哪个GPIO 了。SD 卡连接在 I.MX6ULL 的usdhc1 接口上在imx6ull-alientek-emmc.dts 中找到名为“usdhc1”的节点这个节点就是SD 卡设备节点如下所示
760 usdhc1 {
761 pinctrl-names default, state_100mhz, state_200mhz;
762 pinctrl-0 pinctrl_usdhc1;
763 pinctrl-1 pinctrl_usdhc1_100mhz;
764 pinctrl-2 pinctrl_usdhc1_200mhz;
765 /* pinctrl-3 pinctrl_hog_1; */
766 cd-gpios gpio1 19 GPIO_ACTIVE_LOW;
767 keep-power-in-suspend;
768 enable-sdio-wakeup;
769 vmmc-supply reg_sd1_vmmc;
770 status okay;
771 };第765 行此行本来没有是作者添加的usdhc1 节点作为SD 卡设备总节点usdhc1 节点需要描述SD 卡所有的信息因为驱动要使用。本行就是描述SD 卡的CD 引脚pinctrl 信息所在的子节点因为SD 卡驱动需要根据pincrtl 节点信息来设置CD 引脚的复用功能等。 762~ 764行的pinctrl-0~2 都是SD 卡其他PIN 的pincrtl 节点信息。但是大家会发现其实在usdhc1 节点中并没有“pinctrl-3 pinctrl_hog_1”这一行也就是说并没有指定CD 引脚的pinctrl 信息那么SD 卡驱动就没法设置CD 引脚的复用功能啊这个不用担心因为在“iomuxc”节点下引用了pinctrl_hog_1 这个节点所以Linux 内核中的iomuxc 驱动就会自动初始化pinctrl_hog_1节点下的所有PIN。
第766 行属性“cd-gpios”描述了SD 卡的CD 引脚使用的哪个IO。属性值一共有三个我们来看一下这三个属性值的含义“gpio1”表示CD 引脚所使用的IO 属于GPIO1 组“19”表示GPIO1 组的第19 号IO通过这两个值SD 卡驱动程序就知道CD 引脚使用了GPIO1_IO19这GPIO。“GPIO_ACTIVE_LOW”表示低电平有效如果改为“GPIO_ACTIVE_HIGH”就表示高电平有效。
根据上面这些信息SD 卡驱动程序就可以使用GPIO1_IO19 来检测SD 卡的CD 信号了打开imx6ull.dtsi在里面找到如下所示内容
504 gpio1: gpio0209c000 {
505 compatible fsl,imx6ul-gpio, fsl,imx35-gpio;
506 reg 0x0209c000 0x4000;
507 interrupts GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH,
508 GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH;
509 gpio-controller;
510 #gpio-cells 2;
511 interrupt-controller;
512 #interrupt-cells 2;
513 };gpio1 节点信息描述了GPIO1 控制器的所有信息重点就是GPIO1 外设寄存器基地址以及兼容属性。关于I.MX 系列SOC 的GPIO 控制器绑定信息请查看文档Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt。
第505 行设置gpio1 节点的compatible 属性有两个分别为“fsl,imx6ul-gpio”和“fsl,imx35-gpio”在Linux 内核中搜索这两个字符串就可以找到I.MX6UL 的GPIO 驱动程序。
第506 行reg 属性设置了GPIO1 控制器的寄存器基地址为0X0209C000大家可以打开《I.MX6ULL 参考手册》找到“Chapter 28:General Purpose Input/Output(GPIO)”章节第28.5 小节有如图45.2.2.1 所示的寄存器地址表
从图45.2.2.1 可以看出GPIO1 控制器的基地址就是0X0209C000。 第509 行“gpio-controller”表示gpio1 节点是个GPIO 控制器。 第510 行“#gpio-cells”属性和“#address-cells”类似#gpio-cells 应该为2表示一共有两个cell第一个cell 为GPIO 编号比如“gpio1 3”就表示GPIO1_IO03。第二个cell 表示GPIO 极性如果为0(GPIO_ACTIVE_HIGH) 的话表示高电平有效如果为1(GPIO_ACTIVE_LOW)的话表示低电平有效。
2、GPIO 驱动程序简介
本小节会涉及到Linux 驱动分层与分离、平台设备驱动等还未讲解的知识所以本小节教程可以不用看不会影响后续的实验。如果对Linux 内核的GPIO 子系统实现原理感兴趣的话可以看本小节。
gpio1 节点的compatible 属性描述了兼容性在Linux 内核中搜索“fsl,imx6ul-gpio”和“fsl,imx35-gpio”这两个字符串查找GPIO 驱动文件。drivers/gpio/gpio-mxc.c 就是I.MX6ULL的GPIO 驱动文件在此文件中有如下所示of_device_id 匹配表
152 static const struct of_device_id mxc_gpio_dt_ids[] {
153 { .compatible fsl,imx1-gpio, .data mxc_gpio_devtype[IMX1_GPIO], },
154 { .compatible fsl,imx21-gpio, .data mxc_gpio_devtype[IMX21_GPIO], },
155 { .compatible fsl,imx31-gpio, .data mxc_gpio_devtype[IMX31_GPIO], },
156 { .compatible fsl,imx35-gpio, .data mxc_gpio_devtype[IMX35_GPIO], },
157 { /* sentinel */ }
158 };第156 行的compatible 值为“fsl,imx35-gpio”和gpio1 的compatible 属性匹配因此gpio-mxc.c 就是I.MX6ULL 的GPIO 控制器驱动文件。gpio-mxc.c 所在的目录为drivers/gpio打开这个目录可以看到很多芯片的gpio 驱动文件“gpiolib”开始的文件是gpio 驱动的核心文件如图45.2.2.2 所示 我们重点来看一下gpio-mxc.c 这个文件在gpio-mxc.c 文件中有如下所示内容
496 static struct platform_driver mxc_gpio_driver {
497 .driver {
498 .name gpio-mxc,
499 .of_match_table mxc_gpio_dt_ids,
500 },
501 .probe mxc_gpio_probe,
502 .id_table mxc_gpio_devtype,
503 };可以看出GPIO 驱动也是个平台设备驱动因此当设备树中的设备节点与驱动的of_device_id 匹配以后probe 函数就会执行在这里就是mxc_gpio_probe 函数这个函数就是I.MX6ULL 的GPIO 驱动入口函数。我们简单来分析一下mxc_gpio_probe 这个函数函数内容如下
403 static int mxc_gpio_probe(struct platform_device *pdev)
404 {
405 struct device_node *np pdev-dev.of_node;
406 struct mxc_gpio_port *port;
407 struct resource *iores;
408 int irq_base;
409 int err;
410
411 mxc_gpio_get_hw(pdev);
412
413 port devm_kzalloc(pdev-dev, sizeof(*port), GFP_KERNEL);
414 if (!port)
415 return -ENOMEM;
416
417 iores platform_get_resource(pdev, IORESOURCE_MEM, 0);
418 port-base devm_ioremap_resource(pdev-dev, iores);
419 if (IS_ERR(port-base))
420 return PTR_ERR(port-base);
421
422 port-irq_high platform_get_irq(pdev, 1);
423 port-irq platform_get_irq(pdev, 0);
424 if (port-irq 0)
第405 行设备树节点指针。 第406 行定义一个结构体指针port结构体类型为mxc_gpio_port。gpio-mxc.c 的重点工作就是维护mxc_gpio_portmxc_gpio_port 就是对I.MX6ULL GPIO 的抽象。mxc_gpio_port 结构体定义如下
61 struct mxc_gpio_port {
62 struct list_head node;
63 void __iomem *base;
64 int irq;
65 int irq_high;
66 struct irq_domain *domain;
67 struct bgpio_chip bgc;
68 u32 both_edges;
69 };mxc_gpio_port 的bgc 成员变量很重要因为稍后的重点就是初始化bgc。 继续回到mxc_gpio_probe 函数函数第411 行调用mxc_gpio_get_hw 函数获取gpio 的硬件相关数据其实就是gpio 的寄存器组函数mxc_gpio_get_hw 里面有如下代码
364 static void mxc_gpio_get_hw(struct platform_device *pdev)
365 {
366 const struct of_device_id *of_id
367 of_match_device(mxc_gpio_dt_ids, pdev-dev);
368 enum mxc_gpio_hwtype hwtype;
......
383
注意第385 行mxc_gpio_hwdata 是个全局变量如果硬件类型是IMX35_GPIO 的话设置mxc_gpio_hwdat 为imx35_gpio_hwdata。对于I.MX6ULL 而言硬件类型就是IMX35_GPIOimx35_gpio_hwdata 是个结构体变量描述了GPIO 寄存器组内容如下
101 static struct mxc_gpio_hwdata imx35_gpio_hwdata {
102 .dr_reg 0x00,
103 .gdir_reg 0x04,
104 .psr_reg 0x08,
105 .icr1_reg 0x0c,
106 .icr2_reg 0x10,
107 .imr_reg 0x14,
108 .isr_reg 0x18,
109 .edge_sel_reg 0x1c,
110 .low_level 0x00,
111 .high_level 0x01,
112 .rise_edge 0x02,
113 .fall_edge 0x03,
114 };大家将imx35_gpio_hwdata 中的各个成员变量和图45.2.2.1 中的GPIO 寄存器表对比就会发现imx35_gpio_hwdata 结构体就是GPIO 寄存器组结构。这样我们后面就可以通过mxc_gpio_hwdata 这个全局变量来访问GPIO 的相应寄存器了。
继续回到示例代码45.2.2.5 的mxc_gpio_probe 函数中第417 行调用函数 platform_get_resource 获取设备树中内存资源信息也就是reg 属性值。前面说了reg 属性指定了GPIO1 控制器的寄存器基地址为0X0209C000在配合前面已经得到的mxc_gpio_hwdata这样Linux 内核就可以访问gpio1 的所有寄存器了。 第418 行调用devm_ioremap_resource 函数进行内存映射得到0x0209C000 在Linux 内核中的虚拟地址。 第422、423 行通过platform_get_irq 函数获取中断号第422 行获取高16 位GPIO 的中断号第423 行获取底16 位GPIO 中断号。 第428、429 行操作GPIO1 的IMR 和ISR 这两个寄存器关闭GPIO1 所有IO 中断并且清除状态寄存器。 第438~448 行设置对应GPIO 的中断服务函数不管是高16 位还是低16 位中断服务函数都是mx3_gpio_irq_handler。 第450~453 行bgpio_init 函数第一个参数为bgc是bgpio_chip 结构体指针。bgpio_chip结构体有个gc 成员变量gc 是个gpio_chip 结构体类型的变量。gpio_chip 结构体是抽象出来的GPIO 控制器gpio_chip 结构体如下所示(有缩减)
74 struct gpio_chip {
75 const char *label;
76 struct device *dev;
77 struct module *owner;
78 struct list_head list;
79
80 int (*request)(struct gpio_chip *chip,
81 unsigned offset);
82 void (*free)(struct gpio_chip *chip,
83 unsigned offset);
84 int (*get_direction)(struct gpio_chip *chip,
85 unsigned offset);
86 int (*direction_input)(struct gpio_chip *chip,
87 unsigned offset);
88 int (*direction_output)(struct gpio_chip *chip,
89 unsigned offset, int value);
90 int (*get)(struct gpio_chip *chip,
91 unsigned offset);
92 void (*set)(struct gpio_chip *chip,
93 unsigned offset, int value);
......
145 };可以看出gpio_chip 大量的成员都是函数这些函数就是GPIO 操作函数。bgpio_init 函数主要任务就是初始化bgc-gc 。bgpio_init 里面有三个setup 函数bgpio_setup_io 、bgpio_setup_accessors 和bgpio_setup_direction。这三个函数就是初始化bgc-gc 中的各种有关GPIO 的操作比如输出输入等等。第451~453 行的GPIO_PSR、GPIO_DR 和GPIO_GDIR 都是I.MX6ULL 的GPIO 寄存器。这些寄存器地址会赋值给bgc 参数的reg_dat、reg_set、reg_clr和reg_dir 这些成员变量。至此bgc 既有了对GPIO 的操作函数又有了I.MX6ULL 有关GPIO的寄存器那么只要得到bgc 就可以对I.MX6ULL 的GPIO 进行操作。
继续回到mxc_gpio_probe 函数第461 行调用函数gpiochip_add 向Linux 内核注册gpio_chip也就是port-bgc.gc。注册完成以后我们就可以在驱动中使用gpiolib 提供的各个API 函数。
gpio子系统API函数(申请、释放、输入、输出、读写值) 重要
对于驱动开发人员设置好设备树以后就可以使用gpio 子系统提供的API 函数来操作指定的GPIOgpio 子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处大家各司其职做好自己的本职工作即可。gpio 子系统提供的常用的API 函数有下面几个
1、gpio_request 函数
gpio_request 函数用于申请一个GPIO 管脚在使用一个GPIO 之前一定要使用gpio_request进行申请函数原型如下
int gpio_request(unsigned gpio, const char *label)函数参数和返回值含义如下 gpio要申请的gpio 标号使用of_get_named_gpio 函数从设备树获取指定GPIO 属性信息此函数会返回这个GPIO 的标号。 label给gpio 设置个名字。 返回值0申请成功其他值申请失败。
2、gpio_free 函数
如果不使用某个GPIO 了那么就可以调用gpio_free 函数进行释放。函数原型如下
void gpio_free(unsigned gpio)函数参数和返回值含义如下 gpio要释放的gpio 标号。 返回值无。
3、gpio_direction_input 函数
此函数用于设置某个GPIO 为输入函数原型如下所示
int gpio_direction_input(unsigned gpio)函数参数和返回值含义如下 gpio要设置为输入的GPIO 标号。 返回值0设置成功负值设置失败。
4、gpio_direction_output 函数
此函数用于设置某个GPIO 为输出并且设置默认输出值函数原型如下
int gpio_direction_output(unsigned gpio, int value)函数参数和返回值含义如下 gpio要设置为输出的GPIO 标号。 valueGPIO 默认输出值。 返回值0设置成功负值设置失败。
5、gpio_get_value 函数
此函数用于获取某个GPIO 的值(0 或1)此函数是个宏定义所示
#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)函数参数和返回值含义如下
gpio要获取的GPIO 标号。 返回值非负值得到的GPIO 值负值获取失败。
6、gpio_set_value 函数
此函数用于设置某个GPIO 的值此函数是个宏定义如下
#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)函数参数和返回值含义如下 gpio要设置的GPIO 标号。 value要设置的值。 返回值无
关于gpio 子系统常用的API 函数就讲这些这些是我们用的最多的。
设备树中添加gpio节点模板
继续完成45.1.3 中的test 设备在45.1.3 中我们已经讲解了如何创建test 设备的pinctrl 节点。本节我们来学习一下如何创建test 设备的GPIO 节点。
1、创建test 设备节点 在根节点“/”下创建test 设备子节点如下所示
1 test {
2 /* 节点内容*/
3 };2、添加pinctrl 信息 在45.1.3 中我们创建了pinctrl_test 节点此节点描述了test 设备所使用的GPIO1_IO00 这个PIN 的信息我们要将这节点添加到test 设备节点中如下所示
1 test {
2 pinctrl-names default;
3 pinctrl-0 pinctrl_test;
4 /* 其他节点内容*/
5 };第2 行添加pinctrl-names 属性此属性描述pinctrl 名字为“default”。 第3 行添加pinctrl-0 节点此节点引用45.1.3 中创建的pinctrl_test 节点表示tset 设备的所使用的PIN 信息保存在pinctrl_test 节点中。
3、添加GPIO 属性信息 我们最后需要在test 节点中添加GPIO 属性信息表明test 所使用的GPIO 是哪个引脚添加完成以后如下所示
1 test {
2 pinctrl-names default;
3 pinctrl-0 pinctrl_test;
4 gpio gpio1 0 GPIO_ACTIVE_LOW;
5 };
第4 行test 设备所使用的gpio。 关于pinctrl 子系统和gpio 子系统就讲解到这里接下来就使用pinctrl 和gpio 子系统来驱动I.MX6ULL-ALPHA 开发板上的LED 灯。
与gpio 相关的OF函数
在示例代码45.2.4.3 中我们定义了一个名为“gpio”的属性gpio 属性描述了test 这个设备所使用的GPIO。在驱动程序中需要读取gpio 属性内容Linux 内核提供了几个与GPIO 有关的OF 函数常用的几个OF 函数如下所示
1、of_gpio_named_count 函数 of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个GPIO 信息要注意的是空的GPIO 信息也会被统计到比如
gpios 0
gpio1 1 2
0
gpio2 3 4;上述代码的“gpios”节点一共定义了4 个GPIO但是有2 个是空的没有实际的含义。通过of_gpio_named_count 函数统计出来的GPIO 数量就是4 个此函数原型如下
int of_gpio_named_count(struct device_node *np, const char *propname)函数参数和返回值含义如下
np设备节点。propname要统计的GPIO 属性。返回值正值统计到的GPIO 数量负值失败。
2、of_gpio_count 函数 和of_gpio_named_count 函数一样但是不同的地方在于此函数统计的是“gpios”这个属性的GPIO 数量而of_gpio_named_count 函数可以统计任意属性的GPIO 信息函数原型如下所示
int of_gpio_count(struct device_node *np)函数参数和返回值含义如下
np设备节点。返回值正值统计到的GPIO 数量负值失败。
3、of_get_named_gpio 函数 此函数获取GPIO 编号因为Linux 内核中关于GPIO 的API 函数都要使用GPIO 编号此函数会将设备树中类似gpio5 7 GPIO_ACTIVE_LOW的属性信息转换为对应的GPIO 编号此函数在驱动中使用很频繁函数原型如下
int of_get_named_gpio(struct device_node *np,
const char *propname,
int index)函数参数和返回值含义如下 np设备节点。 propname包含要获取GPIO 信息的属性名。 indexGPIO 索引因为一个属性里面可能包含多个GPIO此参数指定要获取哪个GPIO的编号如果只有一个GPIO 信息的话此参数为0。 返回值正值获取到的GPIO 编号负值失败。
硬件原理图分析
本章实验硬件原理图参考8.3 小节即可。
实验程序编写(使用子系统点灯类似HAL库调用API操作GPIO)
本章实验我们继续研究LED 灯在第四十四章实验中我们通过设备树向dtsled.c 文件传递相应的寄存器物理地址然后在驱动文件中配置寄存器。本章实验我们使用pinctrl和gpio子系统来完成LED 灯驱动。
本实验对应的例程路径为开发板光盘- 2、Linux 驱动例程- 5_gpioled。
修改设备树文件
1、添加pinctrl 节点
I.MX6U-ALPHA 开发板上的LED 灯使用了GPIO1_IO03 这个PIN打开imx6ull-alientek-emmc.dts在iomuxc 节点的imx6ul-evk 子节点下创建一个名为“pinctrl_led”的子节点节点内容如下所示
1 pinctrl_led: ledgrp {
2 fsl,pins
3 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
4 ;
5 };第3 行将GPIO1_IO03 这个PIN 复用为GPIO1_IO03电气属性值为0X10B0。
2、添加LED 设备节点
在根节点“/”下创建LED 灯节点节点名为“gpioled”节点内容如下
1 gpioled {
2 #address-cells 1;
3 #size-cells 1;
4 compatible atkalpha-gpioled;
5 pinctrl-names default;
6 pinctrl-0 pinctrl_led;
7 led-gpio gpio1 3 GPIO_ACTIVE_LOW;
8 status okay;
9 };第6 行pinctrl-0 属性设置LED 灯所使用的PIN 对应的pinctrl 节点。 第7 行led-gpio 属性指定了LED 灯所使用的GPIO在这里就是GPIO1 的IO03低电平有效。稍后编写驱动程序的时候会获取led-gpio 属性的内容来得到GPIO 编号因为gpio 子系统的API 操作函数需要GPIO 编号。
3、检查PIN 是否被其他外设使用
这一点非常重要
很多初次接触设备树的驱动开发人员很容易因为这个小问题栽了大跟头因为我们所使用的设备树基本都是在半导体厂商提供的设备树文件基础上修改而来的而半导体厂商提供的设备树是根据自己官方开发板编写的很多PIN 的配置和我们所使用的开发板不一样。比如A 这个引脚在官方开发板接的是I2C 的SDA而我们所使用的硬件可能将A 这个引脚接到了其他的外设比如LED 灯上接不同的外设A 这个引脚的配置就不同。一个引脚一次只能实现一个功能如果A 引脚在设备树中配置为了I2C 的SDA 信号那么A 引脚就不能再配置为GPIO否则的话驱动程序在申请GPIO 的时候就会失败。检查PIN 有没有被其他外设使用包括两个方面
①、检查pinctrl 设置。②、如果这个PIN 配置为GPIO 的话检查这个GPIO 有没有被别的外设使用。
在本章实验中LED 灯使用的PIN 为GPIO1_IO03因此先检查GPIO_IO03 这个PIN 有没有被其他的pinctrl 节点使用在imx6ull-alientek-emmc.dts 中找到如下内容
480 pinctrl_tsc: tscgrp {
481 fsl,pins
482 MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
483 MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
484 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0
485 MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
486 ;
487 };pinctrl_tsc 节点是TSC(电阻触摸屏接口)的pinctrl 节点从第484 行可以看出默认情况下GPIO1_IO03 作为了TSC 外设的PIN。所以我们需要将第484 行屏蔽掉和C 语言一样在要屏蔽的内容前后加上“/”和“/”符号即可。其实在I.MX6U-ALPHA 开发板上并没有用到TSC接口所以第482~485 行的内容可以全部屏蔽掉。
因为本章实验我们将GPIO1_IO03 这个PIN 配置为了GPIO所以还需要查找一下有没有其他的外设使用了GPIO1_IO03在imx6ull-alientek-emmc.dts 中搜索“gpio1 3”找到如下内容
723 tsc {
724 pinctrl-names default;
725 pinctrl-0 pinctrl_tsc;
726 xnur-gpio gpio1 3 GPIO_ACTIVE_LOW;
727 measure-delay-time 0xffff;
728 pre-charge-time 0xfff;
729 status okay;
730 };tsc 是TSC 的外设节点从726 行可以看出tsc 外设也使用了GPIO1_IO03同样我们需要将这一行屏蔽掉。然后在继续搜索“gpio1 3”看看除了本章的LED 灯以外还有没有其他的地方也使用了GPIO1_IO03找到一个屏蔽一个。
设备树编写完成以后使用“make dtbs”命令重新编译设备树然后使用新编译出来的imx6ull-alientek-emmc.dtb 文件启动Linux 系统。启动成功以后进入“/proc/device-tree”目录中查看“gpioled”节点是否存在如果存在的话就说明设备树基本修改成功(具体还要驱动验证)结果如图45.4.1.1 所示
LED灯驱动程序编写
设备树准备好以后就可以编写驱动程序了本章实验在第四十四章实验驱动文件dtsled.c 的基础上修改而来。新建名为“5_gpioled”文件夹然后在5_gpioled 文件夹里面创建vscode 工程工作区命名为“gpioled”。工程创建好以后新建gpioled.c 文件在gpioled.c 里面输入如下内容
#include linux/types.h
#include linux/kernel.h
#include linux/delay.h
#include linux/ide.h
#include linux/init.h
#include linux/module.h
#include linux/errno.h
#include linux/gpio.h
#include linux/cdev.h
#include linux/device.h
#include linux/of.h
#include linux/of_address.h
#include linux/of_gpio.h
#include asm/mach/map.h
#include asm/uaccess.h
#include asm/io.h
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : gpioled.c
作者 : 左忠凯
版本 : V1.0
描述 : 采用pinctrl和gpio子系统驱动LED灯。
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/7/13 左忠凯创建
***************************************************************/
#define GPIOLED_CNT 1 /* 设备号个数 */
#define GPIOLED_NAME gpioled /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */struct device_node *nd; /* 设备节点 */int led_gpio; /* led所使用的GPIO编号 */
};struct gpioled_dev gpioled; /* led设备 *//** description : 打开设备* param - inode : 传递给驱动的inode* param - filp : 设备文件file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* return : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{filp-private_data gpioled; /* 设置私有数据 */return 0;
}/** description : 从设备读取数据 * param - filp : 要打开的设备文件(文件描述符)* param - buf : 返回给用户空间的数据缓冲区* param - cnt : 要读取的数据长度* param - offt : 相对于文件首地址的偏移* return : 读取的字节数如果为负值表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** description : 向设备写数据 * param - filp : 设备文件表示打开的文件描述符* param - buf : 要写给设备写入的数据* param - cnt : 要写入的数据长度* param - offt : 相对于文件首地址的偏移* return : 写入的字节数如果为负值表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev filp-private_data;retvalue copy_from_user(databuf, buf, cnt);if(retvalue 0) {printk(kernel write failed!\r\n);return -EFAULT;}ledstat databuf[0]; /* 获取状态值 */if(ledstat LEDON) { gpio_set_value(dev-led_gpio, 0); /* 打开LED灯 */} else if(ledstat LEDOFF) {gpio_set_value(dev-led_gpio, 1); /* 关闭LED灯 */}return 0;
}/** description : 关闭/释放设备* param - filp : 要关闭的设备文件(文件描述符)* return : 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{return 0;
}/* 设备操作函数 */
static struct file_operations gpioled_fops {.owner THIS_MODULE,.open led_open,.read led_read,.write led_write,.release led_release,
};/** description : 驱动出口函数* param : 无* return : 无*/
static int __init led_init(void)
{int ret 0;/* 设置LED所使用的GPIO *//* 1、获取设备节点gpioled */gpioled.nd of_find_node_by_path(/gpioled);//根节点下添加的if(gpioled.nd NULL) {printk(gpioled node not find!\r\n);return -EINVAL;//错误处理 添加GOTO跳转} else {printk(gpioled node find!\r\n);}/* 2、 获取设备树中的gpio属性得到LED所使用的LED编号 */gpioled.led_gpio of_get_named_gpio(gpioled.nd, led-gpio, 0);if(gpioled.led_gpio 0) {printk(cant get led-gpio);return -EINVAL;//错误处理 添加GOTO跳转}printk(led-gpio num %d\r\n, gpioled.led_gpio);//视频里讲 此处还需要申请IO 防止冲突 使用的API是 gpio_request/* 3、设置GPIO1_IO03为输出并且输出高电平默认关闭LED灯 */ret gpio_direction_output(gpioled.led_gpio, 1);if(ret 0) {printk(cant set gpio!\r\n);}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major) { /* 定义了设备号 */gpioled.devid MKDEV(gpioled.major, 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); /* 获取分配号的次设备号 */}printk(gpioled major%d,minor%d\r\n,gpioled.major, gpioled.minor); /* 2、初始化cdev */gpioled.cdev.owner THIS_MODULE;cdev_init(gpioled.cdev, gpioled_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);}return 0;
}/** description : 驱动出口函数* param : 无* return : 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(gpioled.cdev);/* 删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);//摧毁设备class_destroy(gpioled.class);//摧毁类
}//视频里申请完需要释放IO gpio_free()module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE(GPL);
MODULE_AUTHOR(zuozhongkai);第41 行在设备结构体gpioled_dev 中加入led_gpio 这个成员变量此成员变量保存LED等所使用的GPIO 编号。 第55 行将设备结构体变量gpioled 设置为filp 的私有数据private_data。 第85 行通过读取filp 的private_data 成员变量来得到设备结构体变量也就是gpioled。这种将设备结构体设置为filp 私有数据的方法在Linux 内核驱动里面非常常见。 第96、97 行直接调用gpio_set_value 函数来向GPIO 写入数据实现开/关LED 的效果。不需要我们直接操作相应的寄存器。 第133 行获取节点“/gpioled”。 第142 行通过函数of_get_named_gpio 函数获取LED 所使用的LED 编号。相当于将gpioled 节点中的“led-gpio”属性值转换为对应的LED 编号。 第150 行调用函数gpio_direction_output 设置GPIO1_IO03 这个GPIO 为输出并且默认高电平这样默认就会关闭LED 灯。
可以看出gpioled.c 文件中的内容和第四十四章的dtsled.c 差不多只是取消掉了配置寄存器的过程改为使用Linux 内核提供的API 函数。在GPIO 操作上更加的规范化符合Linux代码框架而且也简化了GPIO 驱动开发的难度以后我们所有例程用到GPIO 的地方都采用此方法。
编写测试APP
本章直接使用第四十二章的测试APP将上一章的ledApp.c 文件复制到本章实验工程下即可。
运行测试
编译驱动程序和测试APP
1、编译驱动程序 编写Makefile 文件本章实验的Makefile 文件和第四十章实验基本一样只是将obj-m 变量的值改为gpioled.oMakefile 内容如下所示
KERNELDIR : /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH : $(shell pwd)obj-m : gpioled.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) clean第4 行设置obj-m 变量的值为gpioled.o。 输入如下命令编译出驱动模块文件
make -j32编译成功以后就会生成一个名为“gpioled.ko”的驱动模块文件。
2、编译测试APP 输入如下命令编译测试ledApp.c 这个测试程序
arm-linux-gnueabihf-gcc ledApp.c -o ledApp编译成功以后就会生成ledApp 这个应用程序。
运行测试
将上一小节编译出来的gpioled.ko 和ledApp 这两个文件拷贝到rootfs/lib/modules/4.1.15 目录中重启开发板进入到目录lib/modules/4.1.15 中输入如下命令加载gpioled.ko 驱动模块
depmod //第一次加载驱动的时候需要运行此命令
modprobe gpioled.ko //加载驱动驱动加载成功以后会在终端中输出一些信息如图45.5.2.1 所示
从图45.5.2.1 可以看出gpioled 这个节点找到了并且GPIO1_IO03 这个GPIO 的编号为3。驱动加载成功以后就可以使用ledApp 软件来测试驱动是否工作正常输入如下命令打开LED灯
./ledApp /dev/gpioled 1 //打开LED 灯输入上述命令以后观察I.MX6U-ALPHA 开发板上的红色LED 灯是否点亮如果点亮的话说明驱动工作正常。在输入如下命令关闭LED 灯
./ledApp /dev/gpioled 0 //关闭LED 灯输入上述命令以后观察I.MX6U-ALPHA 开发板上的红色LED 灯是否熄灭。如果要卸载驱动的话输入如下命令即可
rmmod gpioled.ko