RK3399—pinctrl和gpio子系统

1. 前言

  记得以前在学习51单片机时,要控制一个GPIO输出高、低电平,就得根据对应IO寄存器每一bit的作用和含义进行配置,时钟、速率、方向、上下拉等等。51单片机是一款8位MCU,在配置寄存器相对容易,如果是32位甚至64位的处理器呢,会大大增加配置的复杂度。后来ST推出了STM32,并迅速收获一大批开发者和市场占有份额,至今依然是岿然不动。原因之一就是ST推出了一个“BSP标准库”(当然后来有推出了HAL、LL库等),BSP库屏蔽了底层寄存,大大释放了开发者的工作量,让开发者眼前一亮——原来MCU也可以这样开发。确实,BSP层应该由原厂实现,驱动工程师只做驱动,应用工程师专注做应用。


  linux下的pintcrl和gpio子系统就类似于ST的“BSP库”,但是linux的pinctrl和gpio系统实现的功能和过程要远远比STM32的“BSP库”复杂。linux下引入pincrtl和gpio子系统,大大释放了驱动工程师的工作量,特别是引入“设备树”之后,使用一个外设时,对于pin引脚的初始化和管理,只需通过设备树描述即可,然后由pin子系统管理;对于gpio则由gpio子系统管理。


  因此,与CPU引脚“关联”的设备驱动,最终都会调用pincrtl和gpio子系统。二者是设备驱动的基础,这二者也是一个设备驱动。


2. pinctrl子系统

2.1 pinctrl子系统功能

  CPU的gpio引脚除了的方向、速度、上下拉、驱动能力等基本的电气特性外,一般会包括复用功能,即该引脚既可以作为普通gpio,还可能复位为i2c引脚、uart引脚等。如果采用直接配置寄存器的方式进行驱动开发,会非常繁琐,每更改一个功能就得重新翻阅手册配一遍寄存器,另一方面还可能存在“冲突”问题,比如该引脚已被复用为i2c在使用,但被驱动工程师忽略了,再去使用该gpio时会导致未知预期的问题。引入pintctrl子系统就可以解决诸如此类问题,结合设备树的使用,只需把pin信息在设备树描述清楚,即由pinctrl子系统介入管理。

pinctrl对于pin管理功能:

  • .关联设备树,根据设备树pin信息在内核起来后进行配置pin引脚
  • pin复用功能管理
  • pin电气特性设置

2.2 pin设备树描述

#源自rk3399-firefly-port.dtsi
&pinctrl {
	buttons {
		pwrbtn: pwrbtn {
			rockchip,pins = <0 5 RK_FUNC_GPIO &pcfg_pull_up>;
		};
	};

	leds {
		led_power: led-power {
			rockchip,pins = <2 27 RK_FUNC_GPIO &pcfg_pull_none>;
		};

		led_user: led-user {
			rockchip,pins = <0 13 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};
..........
#源自rk3399.dtsi
i2c3 {
			i2c3_xfer: i2c3-xfer {
				rockchip,pins =
					<4 17 RK_FUNC_1 &pcfg_pull_none>,	#复用为i2c属性
					<4 16 RK_FUNC_1 &pcfg_pull_none>;
			};

			i2c3_gpio: i2c3_gpio {
				rockchip,pins =
					<4 17 RK_FUNC_GPIO &pcfg_pull_none>,#复用为普通gpio属性
					<4 16 RK_FUNC_GPIO &pcfg_pull_none>;
			};

		};

以上设备树摘自部分rk3399的设备树文件:

  • 按键、LED等pin设置为普通GPIO功能
  • 对于i2c3的pin,存在复用功能,在对应的外设节点的设备树下指定使用哪个pin属性,但不能同时用,同时使用pin子系统会返回错误信息
#源自rk3399.dtsi
i2c3: i2c@ff130000 {
   	compatible = "rockchip,rk3399-i2c";
   	reg = <0x0 0xff130000 0x0 0x1000>;
   	clocks = <&cru SCLK_I2C3>, <&cru PCLK_I2C3>;
   	clock-names = "i2c", "pclk";
   	interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH 0>;
   	pinctrl-names = "default";	#默认功能是i2c
   	pinctrl-0 = <&i2c3_xfer>;	#使用i2c复用功能的pin属性
   	#address-cells = <1>;
   	#size-cells = <0>;
   	status = "disabled";
   };
  • 关于pin的宏(RK_FUNC_1、RK_FUNC_GPIO等) 位于 “kernel/include/dt-bindings/pinctrl/rockchip.h” 中声明。

2.3 pin驱动

  pin驱动一般芯片原厂已经提供,rk3399 pin驱动位于源码位于“kernel/drivers/pinctrl/pinctrl-rockship.c”中。“pinctrl-rockchip.c”支持了瑞芯微常用的CPU,如rk3288、rk3399、px30等,使用哪一款CPU,与linux其他驱动一样,可以通过pinctrl节点的设备树进行自动选择匹配。


rockchip pinctrl驱动匹配表

static const struct of_device_id rockchip_pinctrl_dt_match[] = {
	........
	{ .compatible = "rockchip,rk3328-pinctrl",
		.data = &rk3328_pin_ctrl },
	{ .compatible = "rockchip,rk3366-pinctrl",
		.data = &rk3366_pin_ctrl },
	{ .compatible = "rockchip,rk3368-pinctrl",
		.data = &rk3368_pin_ctrl },
	{ .compatible = "rockchip,rk3399-pinctrl",
		.data = &rk3399_pin_ctrl },
	{},
};
static struct platform_driver rockchip_pinctrl_driver = {
	.probe		= rockchip_pinctrl_probe,
	.driver = {
		.name	= "rockchip-pinctrl",
		.pm = &rockchip_pinctrl_dev_pm_ops,
		.of_match_table = rockchip_pinctrl_dt_match,
	},
};

rk3399 pinctrl 节点设备树描述

pinctrl: pinctrl {
		compatible = "rockchip,rk3399-pinctrl";
		rockchip,grf = <&grf>;
		rockchip,pmu = <&pmugrf>;
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;
		.........

3. gpio子系统

3.1 gpio子系统功能

  pinctrl子系统主要是管理pin的电气属性和复用功能,而gpio子系统则是管理gpio的申请释放、控制输入输出、io中断等功能。gpio子系统屏蔽了gpio相关寄存器的配置过程,换而提供了常用的接口函数给驱动工程师使用,方便gpio相关的驱动开发。

常见与gpio相关的驱动:

  • 普通字符驱动,LED、Button
  • 通过gpio触发中断的驱动,touch、mp6050

gpio子系统功能

  • 对于驱动层,屏蔽gpio寄存器配置细节,提供统一gpio操作接口
  • 对于BSP层,统一框架,方便不同CPU接入,只需更换pinctrl子系统的驱动

3.2 gpio子系统常用函数

  gpio子系统对于驱动层的API位于“/kernel/include/linux/gpio.h”中。

【1】检查gpio是否可用

int gpio_is_valid(int number); 
参数/ 含义/含义
number gpio序号
返回 可用返回true,不可用返回false

【2】申请使用一个gpio

  使用一个gpio前,必须向内核申请该gpio。

int gpio_request(unsigned gpio, const char *label)
参数/ 含义
gpio 待申请gpio序号
label gpio命名
返回 成功返回0,失败返回负数

【3】释放已申请gpio

  如果不使用该gpio,则需要释放,否则其他模块申请不到该gpio序号。

int gpio_free(unsigned gpio)
参数/ 含义
gpio 待释放gpio序号
label gpio命名

【4】设置gpio输入模式

int gpio_direction_input(unsigned gpio)
参数 含义
gpio 待设置gpio序号
返回 成功返回0,失败返回负数

【5】设置gpio输出模式

void gpio_set_value(unsigned gpio, int value)
参数 含义
gpio 待设置gpio序号
value 默认输出状态
返回 成功返回0,失败返回负数

【6】读取 gpio状态

int gpio_get_value(unsigned int gpio)
参数 含义
gpio 待读取gpio序号
返回 成功返回gpio状态(1/0),失败返回负数

【7】设置 gpio状态

void gpio_set_value(unsigned int gpio, int value)
参数 含义
gpio 待设置gpio序号
value 待设置值

【8】中断号映射

int gpio_to_irq(unsigned gpio)
参数 含义
gpio 待设置gpio序号
返回 成功返回中断号,失败返回负数

4. 应用

  利用pinctrl和gpio子系统实现一个io翻转控制LED。firefly-rk3399板子上有两个LED,分别是power led和user led。linux自带的“gpio-leds”驱动是在linux led框架的基础上实现的,我们使用user led的gpio口,不使用led框架,直接使用gpio子系统实现io输出状态控制和读取。

4.1 修改设备树

【1】首先屏蔽rk3399原有的“led user”设备树,否则驱动会冲突。

    leds {
                compatible = "gpio-leds";
                ......
                /*user {
                 *      label = "firefly:yellow:user";
                 *      linux,default-trigger = "ir-user-click";
                 *      gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
                 *      pinctrl-names = "default";
                 *      pinctrl-0 = <&led_user>;
                 *      default-state = "off";
                 *};
                 */
        };

【2】添加本次实现的驱动设备树。

  在“kernel/arch/arm64/boot/dts/rockchip/rk3399-firefly.dtsi”

      gpiopin{
                compatible = "gpiopin";	/*驱动兼容属性*/
                gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;/* gpio描述,提供给pinctrl子系统使用*/
                pinctrl-names = "default";	/*pin脚默认状态*/
                pinctrl-0 = <&led_user>;	/*直接使用rk原命名的pin脚设备树,也可以单独重新命名*/
                default-state = "off";		/*默认gpio输出0*/
        };
#源自rk3399-firefly-port.dtsi
&pinctrl {
	leds {
		led_power: led-power {
			rockchip,pins = <2 27 RK_FUNC_GPIO &pcfg_pull_none>;
		};

		led_user: led-user {
			rockchip,pins = <0 13 RK_FUNC_GPIO &pcfg_pull_none>;/*pin脚为普通gpio模式*/
		};
	};

  修改完设备树,可以编译内核,更新板子boot区域(内核和设备树文件)。


4.2 驱动源码

  设备驱动则是套用linux的platform驱动框架了,platform驱动框架结合设备树一起使用,通过设备节点和驱动匹配,然后注册驱动。另外,通过gpio子系统,控制一个gpio是非常简易的事情了,就类似使用STM32的标准库控制gpio。


4.2.1 platform驱动匹配和注册

static struct of_device_id of_gp0_ids[] = {
   {.compatible = "gpiopin"},	/* 与节点设备树“compatible ”属性一致 */
   { }   
 };
 
static struct platform_driver gp0_driver = { 
	.driver   = { 
	.owner    = THIS_MODULE, 
	.name     = DEV_NAME, 
	.of_match_table = of_match_ptr(of_gp0_ids),
	}, 
	.probe 	  = gp0_probe, 
	.remove   = gp0_remove, 
};

module_platform_driver(gp0_driver);		/* platform 驱动注册和注销 */

4.2.2 probe

  probe探测函数,驱动和设备树匹配时,会调用该函数,该函数完成gpio配置及字符驱动的创建过程。

  • 调用gpio子系统函数接口和获取设备树节点属性进行配置gpio
  • 创建字符驱动
static int gp0_probe(struct platform_device *pdev)  
{     
    struct device *dev; 
	int ret = -1;
	dev_t	id = 0;

	//gp0.nd = of_find_node_by_path("/gpiopin");
	gp0.nd =  pdev->dev.of_node;
	if(gp0.nd == NULL)
	{
		printk("get node faileed\n");
		return -1;
	}
	gp0.gpio = of_get_named_gpio(gp0.nd, "gpios", 0);/* 获取gpio序号,"gpios"为设备树的描述信息 */
	if(gp0.gpio < 0)
	{
		printk("get gpio failed\n");
		return -1;
	}
	ret = gpio_request(gp0.gpio, "gp0");		/* 申请GPIO */
	if(ret < 0)
	{
		printk("gpio request failed\n");
		return ret;
	}
	
	ret = gpio_direction_output(gp0.gpio, 0);	/* output,low default */
	if(ret<0)
	{
		printk("gpio set failed\n");
		gpio_free(gp0.gpio);
		return ret;
	}
	
	/*创建字符驱动过程*/
	......
}

4.2.3 read / write

  read/write函数主要调用gpio子系统函数读取或者设置gpio状态。

static ssize_t gp0_read(struct file *pfile, char __user *buf, size_t size, loff_t *offset) 
{ 
	int ret = 0;
	struct gpiopin_dev *p;
	char level = 0;
	
	p = pfile->private_data;

	level = gpio_get_value(p->gpio);	
	ret = copy_to_user(buf, &level, sizeof(level));
	
    return ret; 
} 

static ssize_t gp0_write(struct file *pfile, const char __user *buf, size_t size, loff_t *offset) 
{ 
	int ret = 0;
	struct gpiopin_dev *p;
	char level = 0;
	
	p = pfile->private_data;

	ret = copy_from_user(&level, buf, size);
	gpio_set_value(p->gpio, level);
	
    return ret; 
} 

4.3 Makefile

ifeq ($(KERNELRELEASE),)

KERNELDIR = /opt/rk3399/linux-sdk/linux-sdk/kernel
PWD := $(shell pwd)

modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
	rm -rf *.o *.ko .mod.o *.mod.c *.symvers

else
	obj-m := gpiopin.o
endif

4.4 运行

4.4.1 加载驱动

  执行Makefile把驱动程编译为模块文件,生成gpiopin.ko文件,把文件通过NFS、U盘等传输至板子,系统起来后手动insmod到内核。驱动加载成功,会在“/dev”目录上生成设备文件。如图,“gp0”就是生成的驱动文件。
在这里插入图片描述


4.4.2

  测试应用程序,执行“aarch64-linux-gnu-gcc gpiopin_app.c -o gpiopin_app”,生成可以执行文件“gpiopin_app”,把文件通过NFS、U盘等传输至板子“/home”目录,然后需更改文件属性,增加可执行属性。

int main(int argc, int **argv)
{
	int fd;
	uint8_t w_level = 1, r_level = 0;
	
	fd = open("/dev/gp0", O_RDWR);
	if(-1 == fd)
	{
		perror("open gpiopin failed\n");
		return -1;
	}
	printf("open gpiopin driver succ\n");
	
	for(;;)
	{
		write(fd, &w_level, 1);
		read(fd, &r_level, 1);
		printf("pin = %d\n", r_level);
		w_level = !w_level;
		sleep(1);
	}
	close(fd);
	
	return 0;
}

在这里插入图片描述

5.源码

https://github.com/Prry/rk3399


6. 参考

  本文主要目的是理清pinctrl子系统和gpio子系统的使用,关于pinctrl和gpio内部的原理实现可以参考以下分析文章。pinctrl子系统和gpio子系统的驱动属于原厂“BSP”范畴,芯片原厂已经实现,如果是驱动工程师只需熟悉其接口(当然能够深入理解其实现原理和框架是最好的)即可进行驱动开发工作。


【1】pinctrl 子系统框架 https://blog.csdn.net/ZHONGkunjia/article/details/89873417

【2】pinctrl子系统分析 http://www.luyixian.cn/news_show_10718.aspx

【3】pinctrl子系统 https://blog.csdn.net/u012830148/article/details/80609337

【4】gpio子系统 http://www.wowotech.net/sort/gpio_subsystem

原创文章 128 获赞 147 访问量 36万+

猜你喜欢

转载自blog.csdn.net/qq_20553613/article/details/103931924