GPIO全称: General-Purpose Input/Output(通用输入输出),是一种软件运行期间能够动态配置和控制的通用引脚。
Tinker Board R引脚分类
Tinker Board R有四十个对外开放的GPIO口,如下图所示
CPU的gpio引脚除了的方向、速度、上下拉、驱动能力等基本的电气特性外,一般会包括复用功能,即该引脚既可以作为普通gpio,还可能复位为i2c引脚、uart引脚等。如果采用直接配置寄存器的方式进行驱动开发,会非常繁琐,每更改一个功能就得重新翻阅手册配一遍寄存器,另一方面还可能存在“冲突”问题,比如该引脚已被复用为i2c在使用,但被驱动工程师忽略了,再去使用该gpio时会导致未知预期的问题。引入pintctrl子系统就可以解决诸如此类问题,结合设备树的使用,只需把pin信息在设备树描述清楚,即由pinctrl子系统介入管理。
pinctrl对于pin管理功能:
驱动 DTS 节点配置 pinctrl,通过驱动 Probe 的时候,会将“default”对应的这组Pinctrl 配置到寄存器里面,而其他组的配置需要在代码里面解析出来,再选择切换使用。
//kernel/arch/arm64/boot/dts/rockchip/rk3399.dtsi
i2c0: i2c@ff3c0000 {
compatible = "rockchip,rk3399-i2c";
reg = <0x0 0xff3c0000 0x0 0x1000>;
clocks = <&pmucru SCLK_I2C0_PMU>, <&pmucru PCLK_I2C0_PMU>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH 0>;
pinctrl-names = "default"; //默认功能是i2c
pinctrl-0 = <&i2c0_xfer>; //使用i2c复用功能的pin属性
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
i2c0 {
i2c0_xfer: i2c0-xfer {
rockchip,pins =
<&gpio1 RK_PB7 RK_FUNC_2 &pcfg_pull_none>, #复用为i2c属性
<&gpio1 RK_PC0 RK_FUNC_2 &pcfg_pull_none>;
};
};
以上部分摘自rk3399的设备树文件,主要是对于I2C相关GPIO引脚的配置。
我们下面对于pin相关配置(<&gpio1 RK_PB7 RK_FUNC_2 &pcfg_pull_none>)进行讲解。
其中&gpio1 RK_PB7就对应着GPIO1_B7,因为引脚没有对外开放,所以没有引出该IO口。例如GPIO1_D2就可以写成&gpio1 RK_PD2。
其中RK_FUNC_2这些宏定义可以在kernel/include/dt-bindings/pinctrl/rockchip.h中找到。
#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
#define RK_FUNC_5 5
#define RK_FUNC_6 6
#define RK_FUNC_7 7
根据宏定义可知,这里的RK_FUNC2就代表着数值2也就是二进制数10。这个数值是写入IOMUX寄存器的。这里先简单介绍一下IOMUX,在芯片与引脚之间,有IOMUX去管理。也就是这个引脚是作为I2C功能还是普通GPIO功能,是由这个IOMUX决定的,我们可以通过对于IOMUX模块寄存器的配置,可以指定该引脚是作为具体的哪些功能。我们可以参考RK3399的TRM手册的GRF章节(https://opensource.rock-chips.com/images/e/ee/Rockchip_RK3399TRM_V1.4_Part1-20170408.pdf)来看我们需要配置什么样的值,如下图所示
根据上图可知,我们需要配置为10,也就相当于十进制的2。这里使用RK_FUNC_2就是这个含义。
其中的pcfg_pull_none是不使用上拉功能。
pin驱动一般芯片原厂已经提供,rk3399 pin驱动位于源码位于“kernel/drivers/pinctrl/pinctrl-rockship.c”中。“pinctrl-rockchip.c”支持了瑞芯微常用的CPU,如rk3288、rk3399、px30等,使用哪一款CPU,与linux其他驱动一样,可以通过pinctrl节点的设备树进行自动选择匹配。
//rockchip pinctrl驱动匹配表
//kernel/drivers/pinctrl/pinctrl-rockship.c
static const struct of_device_id rockchip_pinctrl_dt_match[] = {
.....
{ .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,
},
};
//pinctrl 节点设备树描述
//kernel/arch/arm64/boot/dts/rockchip/rk3399.dtsi
pinctrl: pinctrl {
compatible = "rockchip,rk3399-pinctrl";
rockchip,grf = <&grf>;
rockchip,pmu = <&pmugrf>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
....
这里设备树中的compatible会与驱动中”rockchip_pinctrl_dt_match里的compatible成员“相匹配,这里都是"rockchip,rk3399-pinctrl",所以可以匹配成功,匹配上了则会进入probe函数。将设备树中的具体参数传入probe函数进行注册。
在学习GPIO驱动前,我们先需要对Linux操作系统中的GPIO子系统具备一定的了解。pinctrl子系统主要是管理pin的电气属性和复用功能,而gpio子系统则是管理gpio的申请释放、控制输入输出、io中断等功能。gpio子系统屏蔽了gpio相关寄存器的配置过程,换而提供了常用的接口函数给驱动工程师使用,方便gpio相关的驱动开发。
在Linux系统中,GPIO子系统大致分为3层:
分层 | 作用 |
---|---|
抽象驱动框架层 | 这一层为了与具体的硬件平台完全剥离开,为了给上层提供统一的接口而设计,方便上层软件的编写 |
GPIO硬件驱动层 | 硬件驱动层主要实现与抽象框架进行交互的接口,为抽象硬件无关的驱动框架提供具体硬件驱动支持,在系统初始化时,GPIO硬件驱动需要向驱动框架进行注册,然后驱动框架可以通过底层驱动接口操作具体的硬件设备 |
GPIO硬件 | 由多个GPIO_Controller组成,每一个Controller管理一组GPIO引脚,由控制电路和对应的寄存器组成 |
整个GPIO子系统从用户态到内核态再到硬件层的大致框图如下
接口 | 描述 |
---|---|
gpio_request | 向内核申请gpio资源,要使用gpio首先就要向内核进行申请,返回0, 代表申请成功 |
gpio_free | 对应gpio_request, 使用完gpio以后可以把gpio释放掉 |
gpio_direction_input | 设置gpio输入 |
gpio_direction_out | 设置gpio输出 |
gpio_get_value | 读取gpio的值 |
gpio_set_value | 设置gpio的值 |
gpio_to_desc | 获取gpio实例 |
gpio_to_irq | 获取gpio中断号 |
//kernel/arch/arm64/boot/dts/rockchip/rk3399pro-tinker_edge_r.dtsi
gpio-leds {
compatible = "gpio-leds";
pwr-led {
gpios = <&gpio0 RK_PA5 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "default-on";
retain-state-suspended = <1>;
};
act-led {
gpios = <&gpio0 RK_PA3 GPIO_ACTIVE_HIGH>;
linux,default-trigger="mmc0";
};
rsv-led {
gpios = <&gpio0 RK_PB1 GPIO_ACTIVE_HIGH>;
linux,default-trigger="none";
};
};
这里主要拿gpios = <&gpio0 RK_PB1 GPIO_ACTIVE_HIGH>来举例
其中&gpio0 RK_PB1就对应着GPIO0_B1。
GPIO_ACTIVE_HIGH就代表初始状态为输出高电平
//kernel/drivers/leds/leds-gpio.c
static const struct of_device_id of_gpio_leds_match[] = {
{ .compatible = "gpio-leds", },
{},
};
mpu6500@68 {
status = "okay";
compatible = "invensense,mpu6500";
reg = <0x68>;
irq-gpio = <&gpio3 RK_PD2 IRQ_TYPE_EDGE_RISING>;
mpu-int_config = <0x10>;
mpu-level_shifter = <0>;
mpu-orientation = <0 1 0 1 0 0 0 0 1>;
orientation-x= <0>;
orientation-y= <0>;
orientation-z= <1>;
mpu-debug = <1>;
};
这里irq-gpio = <&gpio3 RK_PD2 IRQ_TYPE_EDGE_RISING>则代表着中断GPIO的配置
gpio3 RK_PD2代表着GPIO3_D2
IRQ_TYPE_EDGE_RISING代表着中断上升沿触发
下面看驱动代码中的配置流程
//kernel/drivers/staging/iio/imu/inv_mpu/inv_mpu_i2c.c
static int of_inv_parse_platform_data(struct i2c_client *client,
struct mpu_platform_data *pdata)
{
...
gpio_pin = of_get_named_gpio_flags(np, "irq-gpio", 0, (enum of_gpio_flags
*)&irq_flags);
gpio_request(gpio_pin, "mpu6500");
client->irq = gpio_to_irq(gpio_pin);
ret = request_irq(client->irq, inv_irq_handler, irq_flags | IRQF_SHARED,
"inv_irq", st);
...
}
先通过of_get_named_gpio_flags获取设备树中的配置信息指定某一个gpio_pin
然后通过gpio_request接口去获取该pin的控制权限
然后通过gpio_to_irq将该pin作为中断GPIO
最后通过request_irq注册中断
gpio_demo: gpio_demo {
status = "okay";
compatible = "rockchip,rk3399-gpio";
test-gpio = <&gpio0 RK_PA6 GPIO_ACTIVE_HIGH>; /* GPIO0_A6 */
//test-gpio-irq = <&gpio0 RK_PA6 IRQ_TYPE_EDGE_RISING>; /* GPIO0_A6中断功能 */
};
/*
* Driver for pwm demo on tinker Edge R board.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#define GPIO_IRQ 0
struct test_gpio_info {
int test_gpio;
int gpio_enable_value;
int test_irq_gpio;
int test_irq;
int test_irq_mode;
};
static irqreturn_t test_gpio_irq(int irq, void *dev_id)
{
printk("Enter test gpio irq test program!\n");
return IRQ_HANDLED;
}
static int test_gpio_probe(struct platform_device *pdev)
{
int ret;
int gpio;
enum of_gpio_flags flag;
struct test_gpio_info *gpio_info;
struct device_node *test_gpio_node = pdev->dev.of_node;
printk("TinkerBoard GPIO Test Program Probe\n");
gpio_info = devm_kzalloc(&pdev->dev,sizeof(struct test_gpio_info *), GFP_KERNEL);
if (!gpio_info) {
dev_err(&pdev->dev, "devm_kzalloc failed!\n");
return -ENOMEM;
}
#ifndef GPIO_IRQ
/* 不使用中断功能的GPIO案例 */
gpio = of_get_named_gpio_flags(test_gpio_node, "test-gpio", 0, &flag);//这里的flag即设备树中gpio的第三个参数“GPIO_ACTIVE_LOW”
if (!gpio_is_valid(gpio)) {
dev_err(&pdev->dev, "test-gpio: %d is invalid\n", gpio);
return -ENODEV;
}
if (gpio_request(gpio, "test-gpio")) {
dev_err(&pdev->dev, "test-gpio: %d request failed!\n", gpio);
gpio_free(gpio);
return -ENODEV;
}
gpio_info->test_gpio = gpio;
gpio_info->gpio_enable_value = (flag == OF_GPIO_ACTIVE_LOW) ? 0:1;
gpio_direction_output(gpio_info->test_gpio, gpio_info->gpio_enable_value);
printk("TinkerBoard gpio putout\n");
#else
/* 使用中断功能的GPIO案例 */
gpio = of_get_named_gpio_flags(test_gpio_node, "test-irq-gpio", 0, &flag);
printk("test:the gpio:%d\n",gpio);
if (!gpio_is_valid(gpio)) {
dev_err(&pdev->dev, "test-irq-gpio: %d is invalid\n", gpio);
return -ENODEV;
}
gpio_info->test_irq_gpio = gpio;
gpio_info->test_irq_mode = flag;
gpio_info->test_irq = gpio_to_irq(gpio_info->test_irq_gpio);
if (gpio_info->test_irq) {
if (gpio_request(gpio, "test-irq-gpio")) {
dev_err(&pdev->dev, "test-irq-gpio: %d request failed!\n", gpio);
gpio_free(gpio);
return IRQ_NONE;
}
ret = request_irq(gpio_info->test_irq, test_gpio_irq,
flag, "test-gpio", gpio_info);
if (ret != 0) {
free_irq(gpio_info->test_irq, gpio_info);
dev_err(&pdev->dev, "Failed to request IRQ: %d\n", ret);
}
}
#endif
return 0;
}
static struct of_device_id test_match_table[] = {
{ .compatible = "rockchip,rk3399-gpio",},
{},
};
static struct platform_driver test_gpio_driver = {
.driver = {
.name = "test-gpio",
.owner = THIS_MODULE,
.of_match_table = test_match_table,
},
.probe = test_gpio_probe,
};
static int test_gpio_init(void)
{
return platform_driver_register(&test_gpio_driver);
}
module_init(test_gpio_init);
static void test_gpio_exit(void)
{
platform_driver_unregister(&test_gpio_driver);
}
module_exit(test_gpio_exit);
MODULE_AUTHOR("Joey.ji");
MODULE_DESCRIPTION("Tinker Board R GPIO driver");
MODULE_LICENSE("GPL");