在前面两章我们通过设备树实现了最基础的点灯操作,但是实质上还是在设备树里写出来要操作的寄存器地址,然后在驱动中内核通过of函数获取到寄存器物理地址后经过映射后进行操作,整个过程其实和裸机开发的流程基本一样,而在Linux中,GPIO这种最基本的操作是有专门的驱动框架来供我们使用的,这就是pinctrl和gpio两个子系统。
pinctrl子系统
Linux驱动归根到底都是要用到最基础的GPIO,而GPIO到目前为止的使用无非就是PIN的复用功能和电气特性的设置,pinctrl子系统就可以根据我们在设备树中的设置来对pin进行相关的设置。pinctrl子系统的路径为drivers/pinctrl,在该目录下为各个半导体厂商维护的pin信息
我们使用的imx6ull属于飞思卡尔的,所以主要要关注下面两个个文件
pinctrl子系统我们这里不展开讲了,首先要能用就可以!
设备树中的pinctrl配置信息
因为pinctrl属于LINUX最基础的配置,不管哪个开发板的后期驱动是在这个节点上延伸的,所以pinctrl的基础设置在imx6ull.dtsi这个设备树文件中。在里面搜索一下gpio的关键字iomuxc,可以找到下面几个节点
gpr: iomuxc-gpr@020e4000 { compatible = "fsl,imx6ul-iomuxc-gpr", "fsl,imx6q-iomuxc-gpr", "syscon"; reg = <0x020e4000 0x4000>; }; iomuxc_snvs: iomuxc-snvs@02290000 { compatible = "fsl,imx6ull-iomuxc-snvs"; reg = <0x02290000 0x10000>; };
这三个节点,就对应了IMX6ULL手册中三个GPIO的基础属性
再看下各组寄存器的基地址
刚好就是设备树中定义的地址,然后这三组节点就构成了手册中GPIO的定义。但是在设备树文件中只有这几行描述IOMUXC,很显然这是不够的,一个IO接口具体功能是由具体的板子硬件决定的。所以我们要看继承了imx6ull.dtsi设备树文件的具体开发板的设备树文件。可以
iomuxc控制器节点
回顾一下,我们在写驱动时候最主要的就是IOMUXC这个节点(电气属性、复用功能都是在这个部分里),所以我们主要看下开发板设备树中的iomuxc这一部分(imx6ull-alientek-emmc.dts文件内搜索iomuxc)
&iomuxc { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_hog_1>; imx6ul-evk { pinctrl_enet1_reset: enet1resetgrp { fsl,pins = < MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0 >; }; pinctrl_enet2_reset: enet2resetgrp { fsl,pins = < MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0 >; }; pinctrl_hog_1: hoggrp-1 { fsl,pins = < MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */ MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /* SD1 VSELECT */ MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 /* SD1 RESET */ >; }; /*中间省略若干节点*/ }; };
节点pinctrl_hog_1: hoggrp-1里那上面那个像宏一样的内容来讲一下
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
这个宏是在imx6ul-pinfunc.h(注意是imx6ul,不是6ull,6ull里内容很少,只有几十行)里定义的
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
注意看一下这行宏前后的内容,前面的内容是一样的一组有8条宏(MX6UL_PAD_UART1_RTS_B)双下划线后面跟的就是复用的功能选择
一共8个,对应手册里的复用模式(其中DCE和DTE两个功能都是复用的ALT0)
但是少了几个,就是把最常用的复用功能给拿出来了,如果没有我们要的功能驱动里没有定义的话只能自己写了。想把pin复用成哪个功能,把那个宏调用出来就可以了。
每个宏定义时候后面跟了4个参数,这个在imx6ul-pinfunc.h文件开头已经给我们说明了
/* * The pin function ID is a tuple of * <mux_reg conf_reg input_reg mux_mode input_val> */
结合前面的MX6UL_PAD_UART1_RTS_B__GPIO1_IO19来看,每个值如下
mux_reg -------->0x0090 conf_reg -------->0x031C input_reg -------->0x0000 mux_mode -------->0x5 input_val -------->0x0
而hoggrp-1节点是属于iomuxc的,基地址是0x020e0000,上面各个reg的地址就是在这个基地址进行便宜得到的地址
iomuxc: iomuxc@020e0000 { compatible = "fsl,imx6ul-iomuxc"; reg = <0x020e0000 0x4000>; };
mux_reg的地址就是0x020e0090,对应手册里的地址
对应后面的mux_mode=0x5就是该寄存器的值
复用为ALT5,就是复用为GPIO1_IO19。
conf_reg地址为0x020e031c,对应手册上的寄存器为
是电气属性设置地址。仔细看下这里并没有指定conf_val,是因为这个值在我们调用这个宏时后面传了个参数
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
后面那个0x17509就是电气属性寄存器给定的值,把他分解到寄存器各个bit上就知道其电气属性了,这属于裸机开发里应该了解的内容。
input_reg输入寄存器值为0,即没有偏移量表示这个PIN没有input功能。
input_val写给input_reg寄存器的值。
有了上面的内容,我们就可以根据实习外设需要在设备树文件中添加新的信息。比方开发板上的蜂鸣器、LED,就可以根据IO接口进行设置。
GPIO子系统
上面我们大致知道了pinctrl子系统,说白了pinctrl子系统主要用来设置PIN的(),如果pinctrl子系统将PIN复用为GPIO的时候,那么接下来就要用到另外一个子系统——gpio子系统。gpio子系统主要用来初始化GPIO并且并提供相应的API接口,例如设置输入输出、读取输入值、设置输出值等等。gpio子系统的存在主要就是为了方便驱动开发人员能够更加便利店使用GPIO,在写驱动的时候只要在设备树中添加需要的gpio信息,就可以在驱动使用系统提供的API来操作GPIO,在开发流程上省略了GPIO的设置过程。
还是利用前面那个MX6UL_PAD_UART1_RTS_B__GPIO1_IO19对应的设备,因为在pinctrl中该PIN已经被复用为GPIO1的IO19,我们可以在设备树中搜索关键字gpio1 19,可以找到下面的节点
&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-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>; keep-power-in-suspend; enable-sdio-wakeup; vmmc-supply = <®_sd1_vmmc>; status = "okay"; };
注意一点,注释掉的那一行是根据教程上新加的,其中usdhc1是SD卡设备的总结点,表述了SD卡所有的信息,新加的那一行就是告诉设备树根据pinctrl_hog_1来找相应的pin设置(因为前面的讲pinctrl时是在该节点下
设备树需要根据这个节点进行相应的pinctrl设置,这里要注意的是,虽然在usdhc1节点下我们并没有指定通过引用pinctrl_hog_1节点,但是在iomuxc节点下引用了pinctrl_hog_1节点,内核中iomuxc驱动就会自动初始化该节点下所有pin
回到前面的gpio子系统,加粗的那一行代码,属性cd-gpios描述了SD卡的CD引脚使用了哪个IO——&gpio1 19表示GPIO1组的io19,GPIO_ACTIVE_LOW表示低电平有效,如果改成GPIO_ACTIVE_HIGH表示高电平有效。就根据这行代码,就可以功过GPIO1_IO19来判定SD卡的CD信号了。
gpio设备树分析
gpio1也是个设备树节点,在imx6ull.dtsi文件下,对其进行了描述
gpio1: gpio@0209c000 { compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio"; reg = <0x0209c000 0x4000>; interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>; gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; };
主要涉及到就是寄存器基地址:0x0209c000,对应手册里的寄存器是
也就是GPIO1组的设置。后面的gpio-controller表示该gpio1节点是个GPIO控制器,gpio-cells和address-cells类似,表示一共有2个cell来对其进行描述,第一个cell为&gpio1 3就表示GPIO1_IO03,第二个cell表示GPIO的极性,如果为0(GPIO_ACTIVE_HIGH)表示高电平有效,反之为1的时候表示低电平有效(GPIO_ACTIVE_LOW)。gpio相关节点一共有五组,从gpio1一直到gpio5,对应5组GPIO。
gpio子系统API函数
gpio子系统的流程我们开始不用太关注,但是这里的API是我们在写驱动的时候要了解大致的使用方法的。下面几个函数需要我们了解(路径为/linux/include/linux/gpio.h)
gpio_request
用于申请一个GPIO管脚,在使用GPIO前应该先申请,如果不申请的话也许不报错,但是如果资源被占用就会报错!函数原型如下
static inline int gpio_request(unsigned gpio, const char *label) { return -ENOSYS; }
参数gpio为要申请的gpio标号,一般使用of_get_named_gpio函数从设备树中获取GPIO属性信息
参数label为gpio设置的名字
返回值如果为0表示申请成功,不为0是申请失败
gpio_free
和gpio_request为配对的函数,用来释放申请的GPIO资源,函数原型如下
static inline void gpio_free(unsigned gpio) { might_sleep(); /* GPIO can never have been requested */ WARN_ON(1); }
参数gpio为要释放掉的gpio标号
gpio_direction_input
用来设置gpio为输入,原型如下
static inline int gpio_direction_input(unsigned gpio) { return -ENOSYS; }
参数gpio为要设置的gpio标号
返回值为0时表示设置成功,否则设置失败
gpio_direction_output
用来设置GPIO为输出,并设置默认的输出状态
static inline int gpio_direction_output(unsigned gpio, int value) { return -ENOSYS; }
参数gpio为要设置的gpio标号
参数value为默认输出状态,可以为0或1
返回值为0时表示设置成功,否则设置失败
gpio_get_value
用于获取某个GPIO的输入状态,原型如下
static inline int gpio_get_value(unsigned gpio) { /* GPIO can never have been requested or set as {in,out}put */ WARN_ON(1); return 0; }
这个函数使用方法有些疑问没搞懂,待定
gpio_set_value
用来设置指定GPIO的输出值
static inline void gpio_set_value(unsigned gpio, int value) { /* GPIO can never have been requested or set as output */ WARN_ON(1); }
参数gpio为要设置的gpio标号
参数value为输出状态,可以为0或1
上面几个函数就是最常用的gpio子系统函数,另外还有很多,都在gpio.h文件中声明了。