[RK356x Linux] 开发之GPIO使用以及gpio-leds驱动讲解
创始人
2025-05-31 15:02:51

文章目录

    • 一、GPIO 介绍
    • 二、RK3568 GPIO 状况
    • 三、GPIO 引脚计算
    • 四、ITX-3568JQ LED
      • 4.1 LED 原理图
      • 4.2 LED 设备树
      • 4.3 LED 使用
    • 五、gpio-leds驱动
      • 5.1 介绍
      • 5.2 数据结构
      • 5.3 驱动分析

一、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-A7B0-B7C0-C7D0-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:引脚所在的 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 灯的亮灭状态。该驱动程序主要提供了以下功能:

  1. 初始化 GPIO:在 leds-gpio 驱动初始化时,可以将 LED 连接的 GPIO 引脚配置为输出模式,并设置输出电平,使得 LED 灯处于初始化状态。

  2. 控制 LED 灯的亮灭状态:通过向 LED 连接的 GPIO 引脚写入不同的电平,即可实现控制 LED 灯的亮度。例如,输出高电平使 LED 灯亮起,输出低电平则 LED 灯关闭。

  3. 提供 LED 灯的默认触发器:leds-gpio 驱动还提供了一些预定义的 LED 触发器,例如 heartbeat、none、default-on 等,当用户不设置特定的触发器时,这些预定义的触发器将作为默认触发器。

  4. 支持用户自定义触发器:除了预定义的 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 时为默认 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;
};

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; // 定义设备树子节点的 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);

相关内容

热门资讯

玩家必看“聚乐麻将确实可以开挂... 亲.聚乐麻将这款游戏是可以开挂的,确实是有挂的,通过添加客服【3671900】很多玩家在这款游戏中怀...
推荐一款.“[顺欣棋牌]究竟可... 您好:【顺欣棋牌】这款游戏可以开挂,确实是有挂的,需要了解加客服微信【7482525】很多玩家在这款...
盘点一款「新大圣牛牛」到底有挂... 盘点一款「新大圣牛牛」到底有挂吗!2025火爆一款亲.新大圣牛牛这款游戏是可以开挂的,确实是有挂的,...
盘点一款“悟空大厅到底有没有挂... 您好:悟空大厅这款游戏可以开挂,确实是有挂的,需要软件加微信【3671900】很多玩家在这款游戏中打...
重大通报“中至吉安麻将到底是不... 您好:中至吉安麻将这款游戏可以开挂,确实是有挂的,需要软件加微信【69174242】,很多玩家在中至...