pinctrl和gpio子系统学习(摘自正点原子)

1 pinctrl 子系统

1.1 pinctrl 子系统简介

Linux驱动讲究驱动分离与分层,pinctrl 和 gpio子系统就是驱动分离与分层思想下的产物,驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架。
大多数的 soc 的 pin 都是支持复用的,某一个pin 引脚既可以作为普通的gpio,也可以作为某个功能引脚,我们需要配置 pin 的电器特性, 比如上/下拉、速度、驱动能力等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐,而且容易出问题(比如 pin 功能冲突)。pinctrl子系统就是为了解决这个问题而引入的,pinctrl 子系统主要工作内容如下:
①、获取设备树中的pin信息。
②、根据获取到的pin信息来设置pin的复用功能。
③、根据获取到的pin信息来设置pin的电气特性,比如上下拉、速度、驱动能力等。
这样我们就只需要在设备树里面设置好某个pin的相关属性即可,其他的初始化工作均由pinctrl子系统来完成,pinctrl子系统源码目录为 drivers/pinctrl。

1.2 pin配置信息

要使用pinctrl 子系统,需要在设备树里面设置pin的配置信息。一般会在设备树里面创建一个节点来描述 PIN 的配置信息。例如如下节点内容:

iomuxc: iomuxc@020e0000 {
				compatible = "fsl,imx6ul-iomuxc";
				reg = <0x020e0000 0x4000>;
			};


&iomuxc {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_hog_1>;
	imx6ul-evk {
		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 */
				MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID	0x13058	/* USB_OTG1_ID */
			>;
		};
        /* zuozhongkai LED */
        pinctrl_led: ledgrp {
            fsl,pins = <
            	MX6UL_PAD_GPIO1_IO03__GPIO1_IO03        0x10B0 /* LED0 */
            >;
        };
        ....
        pinctrl_wdog: wdoggrp {
			fsl,pins = <
				MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY    0x30b0
			>;
		};
	};
};

&iomuxc 子节点就是向 iomuxc 节点追加数据,不同的外设使用的PIN不同、其配置也不同,所以会将某个外设使用的所有 pin 都组织在一个子结点里面。如果需要添加自定义外设的 pin ,那就需要新建一个子结点。
关于 pinctrl 设备树绑定信息可以参考文档 Documentation\devicetree\bindings\pinctrl 。
pinctrl 节点添加的过程为:
1、创建对应的节点
同一个外设的 PIN 都放在一个节点下面,在 dts 里的 iomuxc 节点中的 “imx6ull-evk ”子节点下添加“ pinctrl_test ”节点,前缀要注意是“pinctrl_”。添加如下:

pinctrl_test: testgrp {
/* 具体的pin信息 */
};

2、添加“ fsl,pins ”属性
设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为“ fsl,pins “,这是对于I.MX 系列的soc而言,其他的soc会有不同的pin配置信息。

pinctrl_test: testgrp {
	fsl,pins = <
	/* 设备所使用的 PIN 配置信息 */
	>;
};

3、在 ” fsl,pins “属性中添加具体的pin配置信息

pinctrl_test: testgrp {
	fsl,pins = <
	MIX6ULL_PAD_GPIO1_IO00__GPIO1_IO00	config /* config是具体设置值 */
	>;
};

不同的soc的配置会不一样,例如rockchip的 pinctrl 配置
Iomux的pin的mux值,分别对应相应的寄存器值:

#define RK_FUNC_GPIO 0
#define RK_FUNC_1 1
#define RK_FUNC_2 2
#define RK_FUNC_3 3
#define RK_FUNC_4 4

举例 hdmii2c_xfer;首先,我们在 3399 的 trm 的 GRF 章节里面找到了
i2c3hdmi_scl 和 i2c3hdmi_sda 两个 pin 脚对应的是 gpio4c1 和 GPIO4c0,两个功能
脚都是二进制‘11’作为功能值, 即使用 RK_FUNC_3;因为 GPIOA 有 8 个 pin,
GPIOB 也有 8 个 pin,以此计算可得 GPIO4c1 和 GPIO4c0 为 “4 17”和 “4 16”;

hdmi_i2c_xfer: hdmi-i2c-xfer {
	rockchip,pins =
		<4 17 RK_FUNC_3 &pcfg_pull_none>,
		<4 16 RK_FUNC_3 &pcfg_pull_none>;
};

假设现在有某个具体的产品是使用 i2c2 来连接 HDMI 作通讯功能,通过在该产品的
rk3399-xxx.dts 引用覆盖来实现,下面为示例:

&hdmi_rk_fb {
	status = "okay";
	pinctrl-names = "default", "gpio";
	pinctrl-0 = <&i2c2_xfer &hdmi_cec>;
	pinctrl-1 = <&i2c2_gpio>;
};

驱动强度配置
驱动强度配置,即配置所对应的驱动强度电流值,分别对应相应的寄存器值,与 mux
用法类似,以下为示例:

pcfg_pull_up_2ma: pcfg-pull-up-2ma {
	bias-pull-up;
	drive-strength = <2>;
};
pcfg_pull_down_12ma: pcfg-pull-down-12ma {
	bias-pull-down;
	drive-strength = <12>;
};
pcfg_pull_none_13ma: pcfg-pull-none-13ma {
	bias-disable;
	drive-strength = <13>;
};
gmac {
	rgmii_pins: rgmii-pins {
	rockchip,pins =
	/* mac_txd1 */
	<3 5 RK_FUNC_1 &pcfg_pull_none_13ma>,
	/* mac_txd0 */
	<3 4 RK_FUNC_1 &pcfg_pull_none_13ma>,
	/* mac_rxd3 */
	<3 3 RK_FUNC_1 &pcfg_pull_none>,
	/* mac_rxd2 */
	<3 2 RK_FUNC_1 &pcfg_pull_none>,
	/* mac_txd3 */
	<3 1 RK_FUNC_1 &pcfg_pull_none_13ma>,
	/* mac_txd2 */
	<3 0 RK_FUNC_1 &pcfg_pull_none_13ma>;
	};
};

如果想增加或减少驱动强度,但是与所给参考代码定义的驱动强度不同时候,如何修改。
同样类似 mux 的修改,在产品的 dts 文件里面引用之后,修改覆盖。
每一个 pin 具有自己所在对应的驱动电流强度范围,所以配置的时候要选择其有效可配
电流值,如果不是该 pin 对应的有效电流值,配置将会出错,无法生效

上下拉配置,即配置芯片 pad 内部上拉,下拉或者都不配置,分别对应相应的寄存器值,
与 mux 用法类似,以下为示例:

pcfg_pull_up: pcfg-pull-up {
	bias-pull-up;
};
pcfg_pull_down: pcfg-pull-down {
	bias-pull-down;
};
pcfg_pull_none: pcfg-pull-none {
	bias-disable;
};
pcfg_pull_up_8ma: pcfg-pull-up-8ma {
	bias-pull-up;
	drive-strength = <8>;
};
uart1 {
	uart1_xfer: uart1-xfer {
		rockchip,pins =
			<3 12 RK_FUNC_2 &pcfg_pull_up>,
			<3 13 RK_FUNC_2 &pcfg_pull_none>;
	};
};

2 GPIO子系统

2.1 GPIO子系统简介

pinctrl 子系统重点是设置 PIN(有的soc叫做PAD)的复用和电气属性,如果 pinctrl 子系统将一个PIN复用为 GPIO 的话,就要用到 GPIO 子系统。gpio子系统用于初始化gpio并且提供相应的 API 函数,比如设置 GPIO 的输入输出,读取gpio值等。 只需要在设备树中添加gpio相关信息,然后就可以在驱动程序中使用gpio子系统提供的API函数来操作 gpio。
1.设备树中的gpio信息
正点原子 alpha开发板上的UART1_RTS_B作为SD卡的检测引脚,UART1_RTS_B 复用为GPIO_IO19,通过读取这个GPIO 的高低电平就可以知道 SD 卡有没有插入。首先就是配置 pinctrl 节点信息。配置如下:

pinctrl_hog_1: hoggrp-1 {
	fsl,pins = <
		MX6UL_PAD_UART1_RTS_B_GPIO1_IO9		0x17059 /* SD1 CD */
......
	>;	
};

可通过在设备树中SD卡节点下添加一个属性来描述SD卡的CD引脚就可以让驱动识别出,SD 卡的 CD 引脚使用的GPIO口,找到 SD 卡的设备节点 ” usdhc1 “,如下:

&usdhc1 {
	pinctrl-names = "default", "state_100mhz", "state_200mhz";
	pinctrl-0 = <&pinctrl_usdhc1>;
	pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
	pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
	cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
	keep-power-in-suspend; 768 enable-sdio-wakeup;
	vmmc-supply = <®_sd1_vmmc>;
	status = "okay"; 771 
};

属性“ cd-gpios”描述了 SD卡的 CD引脚使用的哪个io。属性值一共有三个, ”&gpio1”表示CD引脚使用的 IO 属于 GPIO1 组,“19”表示GPIO1组的第19号 IO ,通过这个两个值SD卡驱动程序就知道CD 引脚使用了 GPIO1_IO19这个GPIO。“GPIO_ACTIVE_LOW”表示低电平有效,如果改为“GPIO_ACTIVE_HIGH”表示高电平有效。
至于gpio口的电气配置信息,在iomuxc节点下的 pinctrl_hog_1这个子节点有配置,内核中的iomuxc驱动就会自动初始化pinctrl_hog_1节点下的所有pin。
打开 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>;
};

gpio1 节点信息描述了gpio1 控制器的所有信息,重点就是GPIO1外设寄存器基地址以及兼容属性。
gpio1节点的compatible属性有两个,分别是"fsl,imx6ul-gpio", “fsl,imx35-gpio”,Linux内核搜索这两个字符串就可以找到相应的GPIO驱动程序。
reg 属性设置了GPIO1控制器基地址为0X0209C000,寄存器表如下
在这里插入图片描述
可以看出基地址就是 0X0209C000.
gpio-controller 表示 gpio1节点是个 GPIO 控制器。
“#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.2 gpio子系统API函数

在设置好设备树后就可以使用gpio子系统提供的API函数来操作指定的GPIO 。
1、gpio_request 函数用于申请一个GPIO管脚,在使用一个GPIO之前一定要使用gpio_request进行申请,函数原型如下:

int gpio_request(unsigned gpio, const char *label)   

其中:
gpio:要申请的 gpio 标号,即为你要申请哪一个管脚
label:给gpio设置个名字。
返回值: 0 ,申请成功,其他值,申请失败。

2、gpio_free 函数
如果不使用某个gpio了,那么就可以调用此函数进行释放

void gpio_free(unsigned gpio)

返回值:无

3、gpio_direction_input 函数
此函数用于设置某个gpio口为输入,函数原型如下:

int gpio_direction_input(unsigned gpio)

返回值:0,设置成功,负值,设置失败

4、gpio_direction_output 函数
此函数用于设置某个gpio口为输出,并且设置默认输出值,函数原型如下:

int gpio_direction_output(unsigned gpio,int value)

函数参数和返回值:
gpio:要设置为输出的GPIO标号。
value:GPIO 默认输出值。
返回值:0,设置成功;负值,设置失败

5、gpio_get_value 函数
此函数用于获取某个gpio的值,此函数是一个宏,定义如下

#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)

返回值:非负值,得到的gpio值;负值,获取失败。

6、gpio_set_value 函数
此函数用于设置某个gpio的值,此函数是个宏

#define gpio_set_value __gpio_set_value 
void __gpio_set_value(unsigned gpio, int value)

函数参数和返回值
gpio:要设置的标号
value:要设置的值
return:无

2.3 设备树中添加gpio 节点模板

1、创建 test 设备节点
在根节点 “/” 下创建test 设备子节点,如下:

test{
	/* 节点内容 */
};

2、添加 pinctrl 信息
在上面已经创建了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 节点,此节点引用了 pinctrl_test 节点,表示 test 设备的所使用的 PIN 信息保存在 pinctrl_test 节点中。

3、添加GPIO 属性信息

1 test { 
2 	pinctrl-names = "default"; 
3 	pinctrl-0 = <&pinctrl_test>; 
4 	gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
5 };

test 设备所使用的 gpio。

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值