GPIO全称为 General Purpose Input/Output,即通用输入输出端口。它是一种可以通过软件控制的数字输入输出端口,在嵌入式系统中应用十分广泛。
在单片机和嵌入式系统中,GPIO 被用作与外部设备进行通讯、控制外部硬件或者采集外部硬件数据的方式之一。通常情况下,GPIO 引脚被连接到外部器件的控制、数据或电源电路上。
GPIO的引脚数量和位置根据不同的芯片而异,比如 STM32 芯片有多个 GPIO 引脚,每个引脚都有一些与之相关的寄存器,可以用来控制该引脚的输入输出状态。
除了作为数字输入输出的方式,GPIO也可以通过相关的协议实现其他的功能,比如I2C、SPI、PWM等,这一部分通常由芯片的特定硬件模块实现。
RK3568 拥有 152 个 GPIO:
RK3568 GPIO 特点如下:
RK3568 有 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
导出该引脚去使用:
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
IXT-3568JQ 拥有两个 LED 灯,分别是工作灯(WORK_LED
)与 DIY 灯(DIY_LED
),两个 LED 灯都是通过 NPN 型三级管来进行开关控制:
工作灯 WORK_LED
使用 RK3568 的 GPIO0_B6
端口:
DIY 灯 DIY_LED
使用 RK3568 的 GPIO4_C4
端口:
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>;};...};
相关属性含义如下:
/sys/class/leds/
下的生成相应节点on
或者 off
GPIO_ACTIVE_HIGH
代表高电平有效(点亮)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 =
其中:
在这里,对于 led_power
来说:
如果希望 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
前面 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
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灯受哪个触发器驱动,或者创建自己的触发器,并将其加载到系统中。
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 时为默认 offunsigned 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, 一般设置为 dummyunsigned 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_CHANGEDint 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的不同触发处理handlerconst 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;
};
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; // 定义设备树子节点的 handlestruct 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 属性保存到 nameif (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_triggerif (!fwnode_property_read_string(child, "default-state",&state)) { // 获取 default-state 属性保存到 state,根据具体属性赋值相应的宏到 default_stateif (!strcmp(state, "keep"))led.default_state = LEDS_GPIO_DEFSTATE_KEEP;else if (!strcmp(state, "on"))led.default_state = LEDS_GPIO_DEFSTATE_ON;elseled.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) { // 跳过...}// 根据前面获取到的属性添加到 cdevled_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;elseled_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);