配置寄存器来控制IO的方式太过于原始,Linux内核提供了pinctrl子系统和gpio子系统用于GPIO驱动,当然pinctrl子系统负责的就不仅仅是GPIO的驱动了而是所有pin脚的配置。
pinctrl子系统是随着设备树的加入而加入的,依赖于设备树。GPIO子系统在之前的内核中也是存在的,但是pinctrl子系统的加入GPIO子系统也是有很大的改变,之前的GPIO子系统需要芯片厂商提供的mach文件,而加入设备术后,GPIO子系统使用设备树来实现。
接下来我们就分别来看一下pinctrl子系统和GPIO子系统
大多数SOC的PIN都是支持复用的,所以在配置时要考虑复用的设置,此外还要配置PIN的电气特性,比如上下拉、速度、驱动等
pinctrl子系统的主要工作内容:
对于我们使用者来说,只需要在设备树里面设置好某个pin的相关属性即可,其他的初始化工作均由pinctrl子系统来完成
源码目录drivers/pinctrl
各厂家SOC的属性不一定完全相同,要根据手册来查看语法相关的信息
手册参考\Documentation\devicetree\bindings\pinctrl\fsl,imx-pinctrl.txt
它的语法是这样的
Examples: usdhc@0219c000 { /* uSDHC4 */ non-removable;
vmmc-supply = <®_3p3v>;
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usdhc4_1>; };
iomuxc@020e0000 { compatible = "fsl,imx6q-iomuxc";
reg = <0x020e0000 0x4000>;
/* shared pinctrl settings */
usdhc4 { pinctrl_usdhc4_1: usdhc4grp-1 {
fsl,pins = <
MX6QDL_PAD_SD4_CMD__SD4_CMD 0x17059
MX6QDL_PAD_SD4_CLK__SD4_CLK 0x10059
MX6QDL_PAD_SD4_DAT0__SD4_DATA0 0x17059
MX6QDL_PAD_SD4_DAT1__SD4_DATA1 0x17059
MX6QDL_PAD_SD4_DAT2__SD4_DATA2 0x17059
MX6QDL_PAD_SD4_DAT3__SD4_DATA3 0x17059
MX6QDL_PAD_SD4_DAT4__SD4_DATA4 0x17059
MX6QDL_PAD_SD4_DAT5__SD4_DATA5 0x17059
MX6QDL_PAD_SD4_DAT6__SD4_DATA6 0x17059
MX6QDL_PAD_SD4_DAT7__SD4_DATA7 0x17059
>;
};
.... };
IMX6的pinctrl写在iomuxc节点下面,它的名字“pinctrl_usdhc4_1“会在别的节点中被调用
对于PIN的配置分为两个部分“MX6QDL_PAD_SD4_CMD__SD4_CMD 0x17059”
MX6QDL_PAD_SD4_CMD__SD4_CMD,定义在\arch\arm\boot\dts\imx6ul-pinfunc.h
文件中的每一个宏定义是某一个引脚的某一个复用功能
IMX6实现IO复用的使用IOMUXC来实现的,我们看两个寄存器
我们可以通过配置MUX_MODE来实现IO的复用,一个引脚最多可以配置九种功能
还有一个寄存器用来对IO初始化
GPIO的框图如下:
HYS:迟滞比较器,IO作为输入功能时有效,用于设置输入接收器的施密特触发器是否使能,用于输入波形整型
PUS:设置上下拉电阻
PUE:上下拉还是状态保持,保持就是断电保持以前的状态
PKE:用来使能或者静止上下拉/状态保持器功能
ODE:禁止或使能开路输出
SPEED:速度
DSE:驱动能力
SRE:压摆率,就是IO电平跳变所需要的时间,要过EMC可以使用低压摆率,波形缓和,要进行高速通信可以使用高压摆率
我们以一个为例
#define MX6UL_PAD_GPIO1_IO00__I2C2_SCL 0x005C 0x02E8 0x05AC 0x0 0x1
它的后面有五个值,这五个值分别是
<mux_reg conf_reg input_reg mux_mode input_val>
在使用它的时候后面还会再跟一个值
MX6QDL_PAD_SD4_CMD__SD4_CMD 0x17059
后面的值0x17059就是对IO的初始化
通过以上的操作我们就可以完成对一个引脚的复用配置以及初始化
三星的配置就与恩智浦的配置不太一样
三星的一般有一个xxx-pinctrl.dtsi文件,专门进行引脚的配置
例子如下:
pinctrl_0: pinctrl@11400000 {
gpa0: gpa0 {
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
...
};
uart1_data: uart1-data {
samsung,pins = "gpa0-4", "gpa0-5";
samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
samsung,pin-pud = <EXYNOS_PIN_PULL_NONE>;
samsung,pin-drv = <EXYNOS4_PIN_DRV_LV1>;
};
Pin mux/config
这个是关键,选择复用(pin function mode)以及初始化(上下拉、驱动能力)
调用pinctrl一般是在设备树中进行的
各种板子的语法是差不多的,例子如下
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-gpioled";
pinctrl-names = "default"; //关注
pinctrl-0 = <&pinctrl_gpio_leds>; //关注
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
我们只关注两句,这两句的意思就是引用我们前面配置的pinctrl节点
这样如果你的引脚配置为GPIO的话就可以设置GPIO属性并在驱动中使用了
想要配置某个外设而需要配置某一个引脚为GPIO时,一般的配置流程是这样的
在以前的内核版本中,如果要配置GPIO的话一般要使用IC厂家实现的GPIO配置函数,例如三星的配置函数为
/*设置为输入*/
s3c_gpio_cfgpin(EXYNOS4_GPC0(3),S3C_GPIO_INPUT);
/*不上拉不下拉*/
s3c_gpio_setpull(EXYNOS4_GPC0(3),S3C_GPIO_PULL_NONE);
这样带来的问题就是各家有各家的接口函数与实现方式,不但内核的代码复用率低而且开发者很难记住这么多的函数,如果要使用多种平台的话背函数都是很麻烦的,所以在引入设备树后对GPIO子系统进行了大的改造,使用设备树来实现并提供统一的接口
通过GPIO子系统功能要实现:
经过GPIO子系统,我们可以通过如下的方式来配置GPIO
gpio_request(leddev.led0, "led0");
gpio_direction_output(leddev.led0, 1);
gpio_set_value(leddev.led0, 0);
gpio_direction_input(key.irqkeydesc[0].gpio);
value = gpio_get_value(keydesc->gpio);
pinctrl基于设备树,gpio子系统依赖与pinctrl子系统来实现
我这里总结的是比较基本的pinctrl子系统和gpio子系统的应用,他们的好处就是大大简化了驱动的编写,不用去考虑过多的寄存器,考虑不同芯片的差别,大大提高的代码的复用率