博主主页:Systemcall小酒屋
博主简介:Neutionwei,C站嵌入式领域新星创作者之一,一枚热爱开源技术、喜欢分享技术心得的极客,注重简约风格,热衷于用简单的案例讲述复杂的技术,“假传万卷书,真传一案例”,这是厦大一位教数学的院士说过的一句话,另外“成就是最好的老师”,技术既要沉淀,也得分享,成就感的正反馈是支持我持续耕耘的动力!
专栏目录:Firefly-RK356x板卡
专栏说明:本专栏主要基于Firefly旗下的RK356x板卡进行入门篇讲述,欢迎订阅,博主会持续更新!
GPIO,全称 General-Purpose Input/Output(通用输入输出),是一种软件运行期间能够动态配置和控制的通用引脚。 所有的 GPIO 在上电后的初始状态都是输入模式,可以通过软件设为上拉或下拉,也可以设置为中断脚,驱动强度都是可编程的,其核心是填充 GPIO bank 的方法和参数,并调用 gpiochip_add
注册到内核中。
本文以 GPIO0_B4
和 GPIO4_D5
这两个 GPIO 口为例写了一份简单操作 GPIO 口的驱动,在 SDK 的路径为:kernel/drivers/gpio/gpio-firefly.c
,以下就以该驱动为例介绍 GPIO 的操作。
ROC-RK3568-PC 有 5 组 GPIO bank:GPIO0
~GPIO4
,每组又以 A0
-A7
、B0
-B7
、 C0
-C7
、 D0
-D7
作为编号区分,常用以下公式计算引脚:
下面演示GPIO4_D5
引脚计算方法:
bank = 4; // GPIO4_D5 => 4, bank ∈ [0,4]
group = 3; // GPIO4_D5 => 3, group ∈ {(A=0), (B=1), (C=2), (D=3)}
X = 5; // GPIO4_D5 => 5, X ∈ [0,7]
number = group * 8 + X = 3 * 8 + 5 = 29
pin = bank * 32 + number = 4 * 32 + 29 = 157;
GPIO4_D5
对应的设备树属性描述为:<&gpio4 29 IRQ_TYPE_EDGE_RISING>
,由kernel/include/dt-bindings/pinctrl/rockchip.h
的宏定义可知,也可以将GPIO4_D5
描述为<&gpio4 RK_PD5 IRQ_TYPE_EDGE_RISING>
。
kernel/include/dt-bindings/pinctrl/rockchip.h
内容如下:
#ifndef __DT_BINDINGS_ROCKCHIP_PINCTRL_H__
#define __DT_BINDINGS_ROCKCHIP_PINCTRL_H__
#define RK_GPIO0 0
#define RK_GPIO1 1
#define RK_GPIO2 2
#define RK_GPIO3 3
#define RK_GPIO4 4
#define RK_GPIO6 6
#define RK_PA0 0
#define RK_PA1 1
#define RK_PA2 2
#define RK_PA3 3
#define RK_PA4 4
#define RK_PA5 5
#define RK_PA6 6
#define RK_PA7 7
#define RK_PB0 8
#define RK_PB1 9
#define RK_PB2 10
#define RK_PB3 11
#define RK_PB4 12
#define RK_PB5 13
#define RK_PB6 14
#define RK_PB7 15
#define RK_PC0 16
#define RK_PC1 17
#define RK_PC2 18
#define RK_PC3 19
#define RK_PC4 20
#define RK_PC5 21
#define RK_PC6 22
#define RK_PC7 23
#define RK_PD0 24
#define RK_PD1 25
#define RK_PD2 26
#define RK_PD3 27
#define RK_PD4 28
#define RK_PD5 29
#define RK_PD6 30
#define RK_PD7 31
#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
#endif
当GPIO4_D5
引脚没有被其它外设复用时, 我们可以通过export
导出该引脚去使用:
:/ # ls /sys/class/gpio/
export gpiochip128 gpiochip32 gpiochip64 unexport
gpiochip0 gpiochip255 gpiochip500 gpiochip96
:/ # echo 157 > /sys/class/gpio/export
导出后可看到gpio157
:
:/ # ls /sys/class/gpio/
export gpiochip0 gpiochip255 gpiochip500 gpiochip96
gpio157 gpiochip128 gpiochip32 gpiochip64 unexport
:/ # ls /sys/class/gpio/gpio157
active_low device direction edge power subsystem uevent value
:/ # cat /sys/class/gpio/gpio157/direction
in
:/ # cat /sys/class/gpio/gpio157/value
0
首先在kernel/arch/arm64/boot/dts/rockchip/rk356x-firefly-demo.dtsi
中增加驱动的资源描述:
gpio_demo: gpio_demo {
status = "okay";
compatible = "firefly,rk356x-gpio";
firefly-gpio = <&gpio0 12 GPIO_ACTIVE_HIGH>; /* GPIO0_B4 */
//firefly-irq-gpio = <&gpio4 29 IRQ_TYPE_EDGE_RISING>; /* GPIO4_D5 */
};
配置含义如下:
firefly-gpio
配置为GPIO0_B4
,GPIO_ACTIVE_HIGH
表示高电平有效如果配置为低电平有效则为GPIO_ACTIVE_LOW
。
然后在 probe
函数中对 DTS 所添加的资源进行解析,代码如下:
static int firefly_gpio_probe(struct platform_device *pdev)
{
int ret;
int gpio;
enum of_gpio_flags flag;
struct firefly_gpio_info *gpio_info;
struct device_node *firefly_gpio_node = pdev->dev.of_node;
printk("Firefly GPIO Test Program Probe\n");
gpio_info = devm_kzalloc(&pdev->dev, sizeof(struct firefly_gpio_info), GFP_KERNEL);
if (!gpio_info) {
return -ENOMEM;
}
gpio = of_get_named_gpio_flags(firefly_gpio_node, "firefly-gpio", 0, &flag); // 从设备树中读取 firefly-gpio 和 firefly-irq-gpio 的 GPIO 配置编号和标志
if (!gpio_is_valid(gpio)) { // 判断该 GPIO 编号是否有效
printk("firefly-gpio: %d is invalid\n", gpio); return -ENODEV;
}
if (gpio_request(gpio, "firefly-gpio")) { // 申请占用该 GPIO
printk("gpio %d request failed!\n", gpio);
gpio_free(gpio); // 释放之前申请过且成功的 GPIO
return -ENODEV;
}
gpio_info->firefly_gpio = gpio;
gpio_info->gpio_enable_value = (flag == OF_GPIO_ACTIVE_LOW) ? 0:1;
gpio_direction_output(gpio_info->firefly_gpio, gpio_info->gpio_enable_value);
printk("Firefly gpio putout\n");
...
}
在驱动中调用 gpio_direction_output
就可以设置输出高还是低电平,这里默认输出从 DTS 获取得到的有效电平 GPIO_ACTIVE_HIGH
,如果驱动正常工作,可以用万用表测得对应的引脚应该为高电平。如果要读出 GPIO,需要先设置成输入模式,然后再读取值:
int val;
gpio_direction_input(your_gpio);
val = gpio_get_value(your_gpio);
以下是常用的 GPIO API 定义:
#include
#include
enum of_gpio_flags {
OF_GPIO_ACTIVE_LOW = 0x1,
};
int of_get_named_gpio_flags(struct device_node *np, const char *propname,
int index, enum of_gpio_flags *flags);
int gpio_is_valid(int gpio);
int gpio_request(unsigned gpio, const char *label);
void gpio_free(unsigned gpio);
int gpio_direction_input(int gpio);
int gpio_direction_output(int gpio, int v);