文章目录
一、GPIO 介绍
GPIO全称为 General Purpose Input/Output,即通用输入输出端口。它是一种可以通过软件控制的数字输入输出端口,在嵌入式系统中应用十分广泛。
在单片机和嵌入式系统中,GPIO 被用作与外部设备进行通讯、控制外部硬件或者采集外部硬件数据的方式之一。通常情况下,GPIO 引脚被连接到外部器件的控制、数据或电源电路上。
GPIO的引脚数量和位置根据不同的芯片而异,比如 STM32 芯片有多个 GPIO 引脚,每个引脚都有一些与之相关的寄存器,可以用来控制该引脚的输入输出状态。
除了作为数字输入输出的方式,GPIO也可以通过相关的协议实现其他的功能,比如I2C、SPI、PWM等,这一部分通常由芯片的特定硬件模块实现。
二、RK3568 GPIO 状况
RK3568 拥有 152 个 GPIO:
RK3568 GPIO 特点如下:
- 可以让 CPU 产生中断
- 支持电平与边沿触发中断
- 支持配置触发极性
- 支持下降沿、上升沿与双边沿触发
三、GPIO 引脚计算
RK3568 有 5 组 GPIO bank:GPIO0
~GPIO4
,每组又以 A0
-A7
、B0
-B7
、 C0
-C7
、 D0
-D7
作为编号区分,常用以下公式计算引脚:
- GPIO 引脚计算公式:pin = bank * 32 + number
- GPIO 小组编号计算公式:number = group * 8 + X
下面演示 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
导出该引脚去使用:
echo 157 > /sys/class/gpio/export
导出后可看到 gpio157
:
# 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
配置为输出:
echo out > /sys/class/gpio/gpio157/direction
输出高电平:
echo 1 > /sys/class/gpio/gpio157/value
输出低电平:
echo 0 > /sys/class/gpio/gpio157/value
四、ITX-3568JQ LED
4.1 LED 原理图
IXT-3568JQ 拥有两个 LED 灯,分别是工作灯(WORK_LED
)与 DIY 灯(DIY_LED
),两个 LED 灯都是通过 NPN 型三级管来进行开关控制:
工作灯 WORK_LED
使用 RK3568 的 GPIO0_B6
端口:
DIY 灯 DIY_LED
使用 RK3568 的 GPIO4_C4
端口:
4.2 LED 设备树
IXT-3568JQ 的 LED 灯设备树配置在 kernel/arm64/boot/dts/rockchip/rk3568-firefly-itx-3568q.dtsi
:
firefly_leds: leds {
status = "okay";
compatible = "gpio-leds";
power_led: power {
label = "firefly:blue:power";
linux,default-trigger = "ir-power-click";
default-state = "on";
gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&led_power>;
};
user_led: user {
label = "firefly:yellow:user";
linux,default-trigger = "ir-user-click";
default-state = "off";
gpios = <&gpio4 RK_PC4 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&led_user>;
};
...
};
相关属性含义如下:
- label:标签,它会在
/sys/class/leds/
下的生成相应节点 - linux,default-trigger:默认触发方式
- default-state:默认状态,
on
或者off
- gpios:GPIO 属性配置,
GPIO_ACTIVE_HIGH
代表高电平有效(点亮) - pinctrl-*:配置 Pinctrl 控制器
Pinctrl 的配置如下:
&pinctrl {
leds {
led_power: led-power {
rockchip,pins = <0 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>;
};
led_user: led-user {
rockchip,pins = <4 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
...
我们知道在许多 soc 内部包含有多个 pin 控制器,通过 pin 控制器的寄存器,我们可以配置一个或者一组引脚的功能和特性。
在设备树中配置 GPIO,需要配置引脚的功能复用与电气属性。
对于 rockchip 引脚,配置如下:
rockchip,pins = <PIN_BANK PIN_BANK_IDX MUX &phandle>
其中:
- PIN_BANK:引脚所在的 bank
- PIN_BANK_IDX:引脚所在 bank 的引脚号
- MUX:功能复用配置,0 表示普通 GPIO,1-N 表示特殊的功能复用
- phandle:引脚一般配置,例如内部上拉、电流强度等
在这里,对于 led_power
来说:
- PIN_BANK 等于 0
- PIN_BANK_IDX 等于 RK_PB6
- RK_FUNC_GPIO 代表使用普通 GPIO 功能
- pcfg_pull_none 代表普通配置
如果希望 LED 具有闪烁效果,可以修改 linux,default-trigger
属性实现:
linux,default-trigger = "timer";
配置该属性后,LED 默认每 500ms 间隔闪烁。
更多相关属性的介绍可以参考:
kernel/Documentation/devicetree/bindings/leds/leds-gpio.txt
kernel/Documentation/devicetree/bindings/leds/common.txt
kernel/Documentation/devicetree/bindings/pinctrl/rockchip,pinctrl.txt
kernel/Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
4.3 LED 使用
前面 DTS 中配置了 label 属性,会在 sysfs 目录下生成相应的名称的设备节点:
# ls /sys/class/leds/
firefly:blue:power firefly:yellow:user
# ls /sys/class/leds/firefly\:blue\:power
brightness device max_brightness power subsystem trigger uevent
点亮 LED:
echo 1 > /sys/class/leds/firefly\:blue\:power/brightness
熄灭 LED:
echo 0 > /sys/class/leds/firefly\:blue\:power/brightness
五、gpio-leds驱动
5.1 介绍
ITX-3568JQ LED DTS 中用到的驱动是 gpio-leds:
compatible = "gpio-leds";
驱动文件为 drivers/leds/leds-gpio.c
:
static const struct of_device_id of_gpio_leds_match[] = {
{
.compatible = "gpio-leds", },
{
},
};
...
static struct platform_driver gpio_led_driver = {
.probe = gpio_led_probe,
.shutdown = gpio_led_shutdown,
.driver = {
.name = "leds-gpio",
.of_match_table = of_gpio_leds_match,
},
};
内核配置打开:
CONFIG_LEDS_GPIO=y
Linux的 leds-gpio 驱动是一种通用的 GPIO LED 驱动程序,它可以让开发者利用 GPIO 引脚来控制 LED 灯的亮灭状态。该驱动程序主要提供了以下功能:
-
初始化 GPIO:在 leds-gpio 驱动初始化时,可以将 LED 连接的 GPIO 引脚配置为输出模式,并设置输出电平,使得 LED 灯处于初始化状态。
-
控制 LED 灯的亮灭状态:通过向 LED 连接的 GPIO 引脚写入不同的电平,即可实现控制 LED 灯的亮度。例如,输出高电平使 LED 灯亮起,输出低电平则 LED 灯关闭。
-
提供 LED 灯的默认触发器:leds-gpio 驱动还提供了一些预定义的 LED 触发器,例如 heartbeat、none、default-on 等,当用户不设置特定的触发器时,这些预定义的触发器将作为默认触发器。
-
支持用户自定义触发器:除了预定义的 LED 触发器之外,leds-gpio 驱动还支持用户自定义触发器。用户可以通过 sysfs 接口来指定LED灯受哪个触发器驱动,或者创建自己的触发器,并将其加载到系统中。
5.2 数据结构
gpio_led 结构体定义如下:
/* For the leds-gpio driver */
struct gpio_led {
const char *name; // 名称,dts label 属性赋值,在 `/sys/class/leds/` 下的生成相应节点
const char *default_trigger; // 默认触发器,dts linux,default-trigger 属性赋值
unsigned gpio; // gpio 引脚号
unsigned active_low : 1; // 极性,low 时为默认 off
unsigned retain_state_suspended : 1; // 休眠时是否保存状态,等到唤醒后恢复
unsigned panic_indicator : 1;
unsigned default_state : 2; // 默认状态, 0:开, 1:关, 2:保持
unsigned retain_state_shutdown : 1; // 关机是否保留状态
/* default_state should be one of LEDS_GPIO_DEFSTATE_(ON|OFF|KEEP) */
struct gpio_desc *gpiod; // GPIO 描述符
};
#define LEDS_GPIO_DEFSTATE_OFF 0
#define LEDS_GPIO_DEFSTATE_ON 1
#define LEDS_GPIO_DEFSTATE_KEEP 2
gpio_led_platform_data 结构体定义如下:
struct gpio_led_platform_data {
int num_leds; // led 数量
const struct gpio_led *leds;
#define GPIO_LED_NO_BLINK_LOW 0 /* No blink GPIO state low */
#define GPIO_LED_NO_BLINK_HIGH 1 /* No blink GPIO state high */
#define GPIO_LED_BLINK 2 /* Please, blink */
gpio_blink_set_t gpio_blink_set;
};
led_classdev 结构体定义如下:
struct led_classdev {
const char *name;
enum led_brightness brightness; // 当前亮度
enum led_brightness max_brightness; // 最大亮度
int flags; // 反映 led 状态
/* Lower 16 bits reflect status */
#define LED_SUSPENDED BIT(0)
#define LED_UNREGISTERING BIT(1)
/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME BIT(16)
#define LED_SYSFS_DISABLE BIT(17)
#define LED_DEV_CAP_FLASH BIT(18)
#define LED_HW_PLUGGABLE BIT(19)
#define LED_PANIC_INDICATOR BIT(20)
#define LED_BRIGHT_HW_CHANGED BIT(21)
#define LED_RETAIN_AT_SHUTDOWN BIT(22)
/* set_brightness_work / blink_timer flags, atomic, private. */
unsigned long work_flags;
#define LED_BLINK_SW 0
#define LED_BLINK_ONESHOT 1
#define LED_BLINK_ONESHOT_STOP 2
#define LED_BLINK_INVERT 3
#define LED_BLINK_BRIGHTNESS_CHANGE 4
#define LED_BLINK_DISABLE 5
/* Set LED brightness level
* Must not sleep. Use brightness_set_blocking for drivers
* that can sleep while setting brightness.
*/
void (*brightness_set)(struct led_classdev *led_cdev,
enum led_brightness brightness); // 亮度设置回调
/*
* Set LED brightness level immediately - it can block the caller for
* the time required for accessing a LED device register.
*/
int (*brightness_set_blocking)(struct led_classdev *led_cdev,
enum led_brightness brightness);
/* Get LED brightness level */
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); // 亮度设置回调
/*
* Activate hardware accelerated blink, delays are in milliseconds
* and if both are zero then a sensible default should be chosen.
* The call should adjust the timings in that case and if it can't
* match the values specified exactly.
* Deactivate blinking again when the brightness is set to LED_OFF
* via the brightness_set() callback.
*/
int (*blink_set)(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off); // 硬件加速闪烁回调, 毫秒级别
struct device *dev; // 设备
const struct attribute_group **groups; // 组属性
struct list_head node; /* LED Device list */ // 每个led驱动加入双向循环链表管理
const char *default_trigger; /* Trigger to use */ // 默认trigger, 一般设置为 dummy
unsigned long blink_delay_on, blink_delay_off;
struct timer_list blink_timer; // 定时器实现 blink 时长控制
int blink_brightness; // 闪烁亮度
int new_blink_brightness;
void (*flash_resume)(struct led_classdev *led_cdev);
struct work_struct set_brightness_work;
int delayed_set_value;
#ifdef CONFIG_LEDS_TRIGGERS
/* Protects the trigger data below */
struct rw_semaphore trigger_lock; // 读写信号量处理竞态
struct led_trigger *trigger; // 事件触发结构体
struct list_head trig_list; // 事件触发结构体
void *trigger_data; // 数据指针
/* true if activated - deactivate routine uses it to do cleanup */
bool activated;
#endif
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
int brightness_hw_changed;
struct kernfs_node *brightness_hw_changed_kn;
#endif
/* Ensures consistent access to the LED Flash Class device */
struct mutex led_access;
};
led_trigger 结构体定义如下:
struct led_trigger {
/* Trigger Properties */
const char *name; // 触发源
int (*activate)(struct led_classdev *led_cdev); // 亮灯回调
void (*deactivate)(struct led_classdev *led_cdev);// 灭灯回调
/* LEDs under control by this trigger (for simple triggers) */
rwlock_t leddev_list_lock; // 读写锁,防止竟争态
struct list_head led_cdevs; // 双向循环链表控制每个 led 的触发处理 handler
/* Link to next registered trigger */
struct list_head next_trig; // 管理同个led的不同触发处理handler
const struct attribute_group **groups;
};
gpio_led_data 结构体定义如下:
struct gpio_led_data {
struct led_classdev cdev; // led 类设备
struct gpio_desc *gpiod; // gpio 描述符
u8 can_sleep; // 是否可以休眠
u8 blinking; // 闪烁
gpio_blink_set_t platform_gpio_blink_set;
};
5.3 驱动分析
gpio_led_probe 函数如下:
static int gpio_led_probe(struct platform_device *pdev)
{
struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct gpio_leds_priv *priv;
int i, ret = 0;
if (pdata && pdata->num_leds) {
// 非设备树方式
// platform_device 信息
...
} else {
// 设备树方式
priv = gpio_leds_create(pdev); // 调用 gpio_leds_create 函数
if (IS_ERR(priv))
return PTR_ERR(priv);
}
platform_set_drvdata(pdev, priv); // 把私有数据配置到平台设备
return 0;
}
gpio_leds_create 函数:
static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct fwnode_handle *child; // 定义设备树子节点的 handle
struct gpio_leds_priv *priv; // 定义 gpio_leds 私有数据
int count, ret;
count = device_get_child_node_count(dev); // 获取子节点数量,实际上是设备树定义的 led 或者 gpio 的数量
if (!count)
return ERR_PTR(-ENODEV);
priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
if (!priv)
return ERR_PTR(-ENOMEM);
device_for_each_child_node(dev, child) {
// 遍历每个子节点
struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
struct gpio_led led = {
};
const char *state = NULL;
struct device_node *np = to_of_node(child); // 获取设备节点
ret = fwnode_property_read_string(child, "label", &led.name); // 获取 label 属性保存到 name
if (ret && IS_ENABLED(CONFIG_OF) && np)
led.name = np->name;
if (!led.name) {
fwnode_handle_put(child);
return ERR_PTR(-EINVAL);
}
led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
GPIOD_ASIS,
led.name); // 获取 gpio 引脚信息
if (IS_ERR(led.gpiod)) {
fwnode_handle_put(child);
return ERR_CAST(led.gpiod);
}
fwnode_property_read_string(child, "linux,default-trigger",
&led.default_trigger); // 获取 linux,default-trigger 属性保存到 default_trigger
if (!fwnode_property_read_string(child, "default-state",
&state)) {
// 获取 default-state 属性保存到 state,根据具体属性赋值相应的宏到 default_state
if (!strcmp(state, "keep"))
led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
else if (!strcmp(state, "on"))
led.default_state = LEDS_GPIO_DEFSTATE_ON;
else
led.default_state = LEDS_GPIO_DEFSTATE_OFF;
}
if (fwnode_property_present(child, "retain-state-suspended")) // 获取 retain-state-suspended 属性
led.retain_state_suspended = 1;
if (fwnode_property_present(child, "retain-state-shutdown")) // 获取 retain-state-shutdown 属性
led.retain_state_shutdown = 1;
if (fwnode_property_present(child, "panic-indicator")) // 获取 panic-indicator 属性
led.panic_indicator = 1;
ret = create_gpio_led(&led, led_dat, dev, np, NULL); // 调用 create_gpio_led 函数,用于创建相应的设备
if (ret < 0) {
fwnode_handle_put(child);
return ERR_PTR(ret);
}
led_dat->cdev.dev->of_node = np;
priv->num_leds++;
}
return priv;
}
create_gpio_led 函数:
static int create_gpio_led(const struct gpio_led *template,
struct gpio_led_data *led_dat, struct device *parent,
struct device_node *np, gpio_blink_set_t blink_set)
{
int ret, state;
led_dat->gpiod = template->gpiod;
if (!led_dat->gpiod) {
// 跳过
...
}
// 根据前面获取到的属性添加到 cdev
led_dat->cdev.name = template->name;
led_dat->cdev.default_trigger = template->default_trigger;
led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);
if (!led_dat->can_sleep)
led_dat->cdev.brightness_set = gpio_led_set;
else
led_dat->cdev.brightness_set_blocking = gpio_led_set_blocking;
led_dat->blinking = 0;
if (blink_set) {
led_dat->platform_gpio_blink_set = blink_set;
led_dat->cdev.blink_set = gpio_blink_set;
}
if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) {
state = gpiod_get_value_cansleep(led_dat->gpiod);
if (state < 0)
return state;
} else {
state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
}
led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
if (!template->retain_state_suspended)
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
if (template->panic_indicator)
led_dat->cdev.flags |= LED_PANIC_INDICATOR;
if (template->retain_state_shutdown)
led_dat->cdev.flags |= LED_RETAIN_AT_SHUTDOWN;
ret = gpiod_direction_output(led_dat->gpiod, state); // 配置引脚输出状态
if (ret < 0)
return ret;
return devm_of_led_classdev_register(parent, np, &led_dat->cdev); // 往内核的 LED 子系统注册一个设备
}
整个过程总结:
gpio_led_probe(pdev)
-> gpio_leds_create(pdev)
-> create_gpio_led(&led, led_dat, dev, np, NULL)
-> gpiod_direction_output(led_dat->gpiod, state)
-> devm_of_led_classdev_register(parent, np, &led_dat->cdev)
-> platform_set_drvdata(pdev, priv);