技术人如何职场晋升?这些步骤你都了解吗?

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

  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 时为默认 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);

猜你喜欢

转载自blog.csdn.net/qq_32727095/article/details/129790769