RK3288的gpio设置

本文详细介绍了RK3288 GPIO引脚的配置方式,包括输入输出操作和复用功能。通过设备树描述和内核驱动代码示例,展示了如何控制电源LED灯,并介绍了如何将GPIO口复用为其他功能如I2C。重点讨论了GPIO的驱动原理、使用方法及复用机制。

转http://www.360doc.com/content/14/1227/18/14351252_436191812.shtml

目录

 [ 隐藏

简介

GPIO, 全称 General-Purpose Input/Output(通用输入输出),是一种软件运行期间能够动态配置和控制的通用引脚。

RK3288 有 9 组 GPIO bank: GPIO0,GPIO1, ..., GPIO8。每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分(不是所有 bank 都有全部编号,例如 GPIO5 就只有 B0~B7, C0~C3)。

每个 GPIO 口除了通用输入输出功能外,还可能有其它复用功能,例如 GPIO5_B4,可以复用成以下功能之一:

  • spi0_clk
  • ts0_data4
  • uart4exp_ctsn

每个 GPIO 口的驱动电流、上下拉和重置后的初始状态都不尽相同,详细情况请参考《RK3288 规格书》中的 "RK3288 function IO description" 一章。

RK3288 的 GPIO 驱动是在以下 pinctrl 文件中实现的:

kernel/drivers/pinctrl/pinctrl-rockchip.c

其核心是填充 GPIO bank 的方法和参数,并调用 gpiochip_add 注册到内核中。

使用

开发板有两个电源 LED 灯是 GPIO 口控制的,分别是:

Rk3288 leds schematic.png

从电路图上看,GPIO 口输出低电平时灯亮,高电平时灯灭。

另外,扩展槽上引出了几个空闲的 GPIO 口,分别是:

Rk3288 gpios in extension.png

这几个 GPIO 口可以自定义作输入、输出使用。

输入输出

下面以电源 LED 灯的驱动为例,讲述如何在内核编写代码控制 GPIO 口的输出。

首先需要在 dts (Device Tree) 文件 firefly-rk3288.dts (0930版) 或 firefly-rk3288_beta.dts (0809版) 中增加驱动的资源描述:

  1. 	firefly-led{
  2. 		compatible = "firefly,led";
  3. 		led-work = <&gpio8 GPIO_A2 GPIO_ACTIVE_LOW>;
  4. 		led-power = <&gpio8 GPIO_A1 GPIO_ACTIVE_LOW>;
  5. 		status = "okay";
  6. 	};

这里定义了两颗 LED 灯的 GPIO 设置:

led-work  GPIO8_A2  GPIO_ACTIVE_LOW
led-power GPIO8_A1  GPIO_ACTIVE_LOW

GPIO_ACTIVE_LOW 表示低电平有效(灯亮),如果是高电平有效,需要替换为 GPIO_ACTIVE_HIGH 。

之后在驱动程序中加入对 GPIO 口的申请和控制则可:

#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/of_gpio.h>
#endif
 
static int firefly_led_probe(struct platform_device *pdev)
{
    int ret = -1;
    int gpio, flag;
	struct device_node *led_node = pdev->dev.of_node;
 
	gpio = of_get_named_gpio_flags(led_node, "led-power", 0, &flag);
	if (!gpio_is_valid(gpio)){
		printk("invalid led-power: %d\n",gpio);
		return -1;
	} 
    if (gpio_request(gpio, "led_power")) {
		printk("gpio %d request failed!\n",gpio);
        return ret;
	}
	led_info.power_gpio = gpio;
	led_info.power_enable_value = (flag == OF_GPIO_ACTIVE_LOW) ? 0 : 1;
	gpio_direction_output(led_info.power_gpio, !(led_info.power_enable_value));
...
on_error:
    gpio_free(gpio);
}

of_get_named_gpio_flags 从设备树中读取 led-power 的 GPIO 配置编号和标志,gpio_is_valid 判断该 GPIO 编号是否有效,gpio_request 则申请占用该 GPIO。如果初始化过程出错,需要调用 gpio_free 来释放之前申请过且成功的 GPIO 。

调用 gpio_direction_output 就可以设置输出高还是低电平,因为是 GPIO_ACTIVE_LOW ,如果要灯亮,需要写入 0 。

实际中如果要读出 GPIO,需要先设置成输入模式,然后再读取值:

int val;
gpio_direction_input(your_gpio);
val = gpio_get_value(your_gpio);

下面是常用的 GPIO API 定义:

#include <linux/gpio.h>
#include <linux/of_gpio.h>
 
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)

复用

如何定义 GPIO 有哪些功能可以复用,在运行时又如何切换功能呢?以 I2C4 为例作简单的介绍。

查规格表可知,I2C4_SDA 与 GPIO7C1 的功能定义如下:

Pad#func0func1
I2C4_SDA/GPIO7_C1gpio7c1i2c4tp_sda
I2C4_SCL/GPIO7_C2gpio7c2i2c4tp_scl

在 /kernel/arch/arm/boot/dts/rk3288.dtsi 里有:

	i2c4: i2c@ff160000 {
		compatible = "rockchip,rk30-i2c";
		reg = <0xff160000 0x1000>;
		interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>;
		#address-cells = <1>;
		#size-cells = <0>;
		pinctrl-names = "default", "gpio";
		pinctrl-0 = <&i2c4_sda &i2c4_scl>;
		pinctrl-1 = <&i2c4_gpio>;
		gpios = <&gpio7 GPIO_C1 GPIO_ACTIVE_LOW>, <&gpio7 GPIO_C2 GPIO_ACTIVE_LOW>;
		clocks = <&clk_gates6 15>;
		rockchip,check-idle = <1>;
		status = "disabled";
	};

此处,跟复用控制相关的是 pinctrl- 开头的属性:

  • pinctrl-names 定义了状态名称列表: default (i2c 功能) 和 gpio 两种状态。
  • pinctrl-0 定义了状态 0 (即 default)时需要设置的 pinctrl: i2c4_sda 和 i2c4_scl
  • pinctrl-1 定义了状态 1 (即 gpio)时需要设置的 pinctrl: i2c4_gpio

这些 pinctrl 在 /kernel/arch/arm/boot/dts/rk3288-pinctrl.dtsi 中定义:

/ { 
	pinctrl: pinctrl@ff770000 {
		compatible = "rockchip,rk3288-pinctrl";
        ...
		gpio7_i2c4 {
			i2c4_sda:i2c4-sda {
				rockchip,pins = <I2C4TP_SDA>;
				rockchip,pull = <VALUE_PULL_DISABLE>;
				rockchip,drive = <VALUE_DRV_DEFAULT>;
				//rockchip,tristate = <VALUE_TRI_DEFAULT>;
			};
 
			i2c4_scl:i2c4-scl {
				rockchip,pins = <I2C4TP_SCL>;
				rockchip,pull = <VALUE_PULL_DISABLE>;
				rockchip,drive = <VALUE_DRV_DEFAULT>;
				//rockchip,tristate = <VALUE_TRI_DEFAULT>;
			};
 
			i2c4_gpio: i2c4-gpio {
				rockchip,pins = <FUNC_TO_GPIO(I2C4TP_SDA)>, <FUNC_TO_GPIO(I2C4TP_SCL)>;
				rockchip,drive = <VALUE_DRV_DEFAULT>;
			};
		};
        ...
    }
  }

I2C4TP_SDA, I2C4TP_SCL 的定义在 /kernel/arch/arm/boot/dts/include/dt-bindings/pinctrl/rockchip-rk3288.h 中:

#define GPIO7_C1 0x7c10
#define I2C4TP_SDA 0x7c11
 
#define GPIO7_C2 0x7c20
#define I2C4TP_SCL 0x7c21

FUN_TO_GPIO 的定义在 /kernel/arch/arm/boot/dts/include/dt-bindings/pinctrl/rockchip.h 中:

#define FUNC_TO_GPIO(m)		((m) & 0xfff0)

也就是说 FUNC_TO_GPIO(I2C4TP_SDA) == GPIO7_C1, FUNC_TO_GPIO(I2C4TP_SCL) == GPIO7_C2 。

像 0x7c11 这样的值是有编码规则的:

7 c1 1
| |  `- func
| `---- offset
`------ bank
0x7c11 就表示 GPIO7_C1 func1, 即 i2c4tp_sda 。

在复用时,如果选择了 "default" (即 i2c 功能),系统会应用 i2c4_sda 和 i2c4_scl 这两个 pinctrl,最终得将 GPIO7_C1 和 GPIO7_C2 两个针脚切换成对应的 i2c 功能;而如果选择了 "gpio" ,系统会应用 i2c4_gpio 这个 pinctrl,将 GPIO7_C1 和 GPIO7_C2 两个针脚还原为 GPIO 功能。

我们看看 i2c 的驱动程序 /kernel/drivers/i2c/busses/i2c-rockchip.c 是如何切换复用功能的:

static int rockchip_i2c_probe(struct platform_device *pdev)
{
	struct rockchip_i2c *i2c = NULL;
	struct resource *res;
	struct device_node *np = pdev->dev.of_node;
	int ret;
// ...
		i2c->sda_gpio = of_get_gpio(np, 0);
		if (!gpio_is_valid(i2c->sda_gpio)) {
			dev_err(&pdev->dev, "sda gpio is invalid\n");
			return -EINVAL;
		}
		ret = devm_gpio_request(&pdev->dev, i2c->sda_gpio, dev_name(&i2c->adap.dev));
		if (ret) {
			dev_err(&pdev->dev, "failed to request sda gpio\n");
			return ret;
		}
		i2c->scl_gpio = of_get_gpio(np, 1);
		if (!gpio_is_valid(i2c->scl_gpio)) {
			dev_err(&pdev->dev, "scl gpio is invalid\n");
			return -EINVAL;
		}
		ret = devm_gpio_request(&pdev->dev, i2c->scl_gpio, dev_name(&i2c->adap.dev));
		if (ret) {
			dev_err(&pdev->dev, "failed to request scl gpio\n");
			return ret;
		}
		i2c->gpio_state = pinctrl_lookup_state(i2c->dev->pins->p, "gpio");
		if (IS_ERR(i2c->gpio_state)) {
			dev_err(&pdev->dev, "no gpio pinctrl state\n");
			return PTR_ERR(i2c->gpio_state);
		}
		pinctrl_select_state(i2c->dev->pins->p, i2c->gpio_state);
		gpio_direction_input(i2c->sda_gpio);
		gpio_direction_input(i2c->scl_gpio);
		pinctrl_select_state(i2c->dev->pins->p, i2c->dev->pins->default_state);
// ...
}

首先是调用 of_get_gpio 取出设备树中 i2c4 结点的 gpios 属于所定义的两个 gpio:

gpios = <&gpio7 GPIO_C1 GPIO_ACTIVE_LOW>, <&gpio7 GPIO_C2 GPIO_ACTIVE_LOW>;

然后是调用 devm_gpio_request 来申请 gpio,接着是调用 pinctrl_lookup_state 来查找 “gpio” 状态,而默认状态 "default" 已经由框架保存到 i2c->dev-pins->default_state 中了。

最后调用 pinctrl_select_state 来选择是 "default" 还是 "gpio" 功能。

下面是常用的复用 API 定义:

#include <linux/pinctrl/consumer.h>
 
struct device {
//...
#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif
//...
};
 
struct dev_pin_info {
	struct pinctrl *p;
	struct pinctrl_state *default_state;
#ifdef CONFIG_PM
	struct pinctrl_state *sleep_state;
	struct pinctrl_state *idle_state;
#endif
};
 
struct pinctrl_state * pinctrl_lookup_state(struct pinctrl *p, const char *name);
 
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *s);
<think>我们正在处理一个关于RK3288GPIO配置为红外中断输入的问题。根据用户的问题,我们需要将RK3288的某个GPIO引脚配置为红外中断(IR_INT)功能。虽然引用内容主要涉及STM32,但我们可以参考其配置思路,并针对RK3288进行相应的配置。 RK3288是一款基于ARM Cortex-A17的处理器,通常运行Linux操作系统。因此,配置GPIO中断通常涉及到内核驱动或用户空间的sysfs等方式。这里我们主要讨论在内核驱动中配置GPIO中断的方法,因为红外中断通常需要实时响应,在内核驱动中处理更为合适。 步骤概述: 1. 确定要使用的GPIO引脚编号。 2. 在设备树(Device Tree)中配置该GPIO引脚为中断功能。 3. 编写驱动程序,注册中断处理函数。 4. 在中断处理函数中处理红外信号。 由于RK3288GPIO配置需要通过设备树进行描述,下面我们详细说明。 ### 步骤1: 确定GPIO引脚 首先,需要确定用于红外接收的GPIO引脚。假设我们选择GPIO7_A7(具体引脚需根据硬件原理图确定),其对应的GPIO编号可以通过公式计算:对于RK3288GPIO编号 = bank*32 + index。GPIO7_A7的bank为7,A7表示index为7(因为A表示0,B表示1,...,但实际计算中,A组从0开始,所以A7就是7号)。因此,GPIO编号 = 7*32 + 7 = 231。 ### 步骤2: 设备树配置 在设备树中配置该GPIO为中断输入,并指定中断触发方式(上升沿、下降沿或双边沿)。红外接收头通常使用下降沿或双边沿触发(具体取决于红外接收头的输出信号)。 例如,在设备树节点中(可以在某个设备节点下添加,或者新建一个节点): ```dts / { ir_receiver: ir-receiver { compatible = "gpio-ir-receiver"; gpios = <&gpio7 7 GPIO_ACTIVE_LOW>; // 使用GPIO7_A7,低电平有效 pinctrl-names = "default"; pinctrl-0 = <&ir_int_pin>; interrupts-extended = <&gpio7 7 IRQ_TYPE_EDGE_FALLING>; // 下降沿触发 status = "okay"; }; }; &pinctrl { ir { ir_int_pin: ir-int-pin { rockchip,pins = <7 7 RK_FUNC_GPIO &pcfg_pull_up>; // 配置为GPIO功能,上拉 }; }; }; ``` ### 步骤3: 编写驱动程序 在驱动程序中,我们需要注册一个平台驱动,并在probe函数中申请中断并注册中断处理函数。 示例代码(简化版): ```c #include <linux/module.h> #include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/gpio/consumer.h> static irqreturn_t ir_interrupt(int irq, void *dev_id) { // 处理红外中断,读取GPIO状态,解码红外信号等 // 注意:在中断处理函数中,应尽量短,可以通过工作队列或tasklet处理复杂任务 return IRQ_HANDLED; } static int ir_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; int irq, ret; // 获取中断号,设备树中已经定义,这里可以直接获取 irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(dev, "Failed to get IRQ\n"); return irq; } // 申请中断 ret = devm_request_irq(dev, irq, ir_interrupt, IRQF_TRIGGER_FALLING, "ir-receiver", NULL); if (ret) { dev_err(dev, "Failed to request IRQ\n"); return ret; } return 0; } static const struct of_device_id ir_receiver_match[] = { { .compatible = "gpio-ir-receiver", }, { } }; MODULE_DEVICE_TABLE(of, ir_receiver_match); static struct platform_driver ir_receiver_driver = { .probe = ir_probe, .driver = { .name = "ir-receiver", .of_match_table = ir_receiver_match, }, }; module_platform_driver(ir_receiver_driver); ``` ### 步骤4: 红外信号解码 在中断处理函数中,我们需要记录红外信号的时间间隔(通常使用定时器)。常见的红外协议有NEC、RC5等。以NEC协议为例,它使用脉冲宽度编码,我们需要记录每个边沿之间的时间间隔,然后解码。 这部分代码相对复杂,需要根据具体的红外协议实现。可以使用内核中已有的红外解码驱动(如`drivers/media/rc/gpio-ir-recv.c`)作为参考,或者直接使用现有的驱动。 ### 注意事项 1. 在设备树中配置GPIO时,需要确保该GPIO没有被其他功能占用。 2. 中断处理函数中不能有长时间的操作,应该将数据读取和协议解析放在下半部(如tasklet或工作队列)中执行。 3. 如果使用现有的红外接收驱动(如`gpio-ir-recv`),则无需自己编写驱动,只需在设备树中配置好即可。 ### 用户空间配置 如果不想编写内核驱动,也可以考虑使用用户空间中断(通过sysfs)。但这种方式实时性较差,可能无法满足红外信号的高实时性要求。 用户空间配置步骤: 1. 导出GPIO:`echo 231 > /sys/class/gpio/export` 2. 设置方向:`echo in > /sys/class/gpio/gpio231/direction` 3. 设置中断触发方式:`echo falling > /sys/class/gpio/gpio231/edge` 4. 然后通过poll或select监控`/sys/class/gpio/gpio231/value`的变化。 但这种方法通常不推荐用于红外接收,因为红外信号需要微秒级的时间精度。 ### 总结 配置RK3288GPIO为红外中断输入需要: - 确定GPIO引脚并计算编号 - 在设备树中配置该引脚为中断功能,并指定中断触发方式 - 编写内核驱动(或使用现有驱动)来注册中断处理函数,并在其中解码红外信号 如果使用现有的`gpio-ir-recv`驱动,则设备树配置后,内核会自动处理红外解码,并将解码结果通过输入子系统上报,用户空间可以通过`evtest`等工具测试。 §§相关问题§§ 1. RK3288GPIO编号如何计算? 2. 如何在Linux设备树中配置GPIO中断? 3. 如何编写Linux内核中断处理函数? 4. 红外遥控信号(如NEC协议)是如何解码的? 5. 如何验证RK3288上的GPIO中断是否正常工作?</think>要将RK3288GPIO配置为红外中断(IR_INT)输入功能,需要完成以下关键步骤。以下是基于嵌入式Linux开发的通用流程(设备树配置 + 驱动实现): ### 1. **确定GPIO引脚** - 根据硬件原理图确认红外接收器连接的GPIO引脚(例如假设使用`GPIO7_A7`) - RK3288 GPIO编号计算公式:`全局编号 = bank × 32 + pin` - 例如:GPIO7_A7 = `7×32 + 7 = 231` ### 2. **设备树配置** 在设备树文件(`.dts`)中添加红外中断节点: ```dts / { ir_receiver: ir-receiver { compatible = "gpio-ir-receiver"; // 标准红外驱动兼容标识 gpios = <&gpio7 7 GPIO_ACTIVE_LOW>; // GPIO7_A7 pinctrl-names = "default"; pinctrl-0 = <&ir_int_pin>; interrupt-parent = <&gpio7>; // 中断控制器 interrupts = <7 IRQ_TYPE_EDGE_BOTH>; // 双边沿触发 }; }; &pinctrl { ir_int_pin: ir-int-pin { rockchip,pins = <7 7 RK_FUNC_GPIO &pcfg_pull_up>; // 配置为上拉模式 }; }; ``` **关键参数说明**: - `GPIO_ACTIVE_LOW`:红外接收头通常低电平有效 - `IRQ_TYPE_EDGE_BOTH`:捕获上升沿和下降沿(NEC协议需要) - `&pcfg_pull_up`:启用内部上拉电阻 ### 3. **驱动层中断注册** 在内核驱动中注册中断处理函数: ```c #include <linux/interrupt.h> static irqreturn_t ir_handler(int irq, void *dev_id) { // 1. 获取GPIO状态 int val = gpio_get_value(ir_gpio); // 2. 记录高/低电平持续时间 ktime_t now = ktime_get(); s64 duration = ktime_us_delta(now, last_time); // 3. NEC协议解码逻辑 if (duration > 9000) { // 9ms引导码判断 start_decode(); } last_time = now; return IRQ_HANDLED; } // 在probe函数中注册中断 int ir_probe(struct platform_device *pdev) { int irq = gpio_to_irq(ir_gpio); ret = request_irq(irq, ir_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "ir-receiver", NULL); } ``` ### 4. **中断处理要点** 1. **时间测量**:使用`ktime_get()`记录边沿间隔 ```c static ktime_t last_time; ``` 2. **协议解码**(以NEC为例): - 引导码:9ms低电平 + 4.5ms高电平 - 数据位:560µs低电平 + 逻辑0/1(560µs/1690µs高电平) 3. **消抖处理**:添加5-10µs延时过滤干扰 ```c udelay(10); ``` ### 5. **用户空间调试** ```bash # 查看GPIO状态 cat /sys/kernel/debug/gpio # 监控中断触发次数 grep "ir-receiver" /proc/interrupts # 使用evtest测试输入事件 evtest /dev/input/eventX ``` ### 注意事项 1. **电气特性**:红外接收头电压需匹配RK3288的1.8V/3.3V电平 2. **时钟配置**:确保Pinctrl子系统时钟已使能 ```dts &cru { assigned-clocks = <&cru PCLK_GPIO7>; assigned-clock-rates = <100000000>; }; ``` 3. **中断冲突**:确认GPIO未用于其他功能(如I2C、SPI) > 完整实现可参考Linux内核的`drivers/media/rc/gpio-ir-recv.c`驱动模块[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值