文章目录
- 史上最简单:增加GPIO控制功能
- 是如何实现的呢?GPIOLIB框架
- Linux 驱动实现
- 控制引脚输出高低电平
- 综合测试
这一套非常方便!
史上最简单:增加GPIO控制功能
如果是想增加GPIO控制只需要修改设备树就可以做到!
下面这个设备树描述了复用功能,比如你想将UART3的RXD引脚用作GPIO输入中断,你就得在下面的设备树中将UART3节点部分注释掉。
kernel/arch/arm/boot/dts/rongpin/rv1126_1109_common.dtsi
然后在下面的设备树中写入该引脚的相关描述
kernel/arch/arm/boot/dts/pro-rv1126.dts
直接在rp_gpio节点下面添加buzzer
即可:
// GPIO_FUNCTION_OUTPUT 0
// GPIO_FUNCTION_INPUT 1
// GPIO_FUNCTION_IRQ 2
// GPIO_FUNCTION_FLASH 3
// GPIO_FUNCTION_OUTPUT_CTRL 4
//rpgpio init
rp_gpio {
status = "okay";
compatible = "rp_gpio";
/**********************新增GPIO***************************/
buzzer{
gpio_num = <&gpio3 RK_PA1 GPIO_ACTIVE_LOW>;
gpio_function = <0>; //0:output 1:input
};
/********************************************************/
ir_led{
gpio_num = <&gpio2 RK_PB5 GPIO_ACTIVE_LOW>;
gpio_function = <0>; //0:output 1:input
};
bl_led{
gpio_num = <&gpio2 RK_PB6 GPIO_ACTIVE_LOW>;
gpio_function = <0>; //0:output 1:input
};
gpio3c2{
gpio_num = <&gpio3 RK_PC2 GPIO_ACTIVE_LOW>;
gpio_function = <0>;
};
...
};
compatible
: 表示兼容的意思,它的作用就是定义一系列的字符串,用来指定硬件是否兼容相应的驱动,其它的部分接下来分析。
添加成功后又该如何操作控制 GPIO呢?
以控制摄像头补光灯亮灭为例,摄像头的补光灯对应的 proc 设备节点为:bl_led
在/proc/rp_gpio目录下:
echo 0 > bl_led # 该引脚输出低电平,关闭灯光
echo 1 > bl_led # 该引脚输出高电平,打开灯光
# 又以蜂鸣器为例
echo 0 > buzzer # 该引脚输出低电平,关闭蜂鸣器
echo 1 > buzzer # 该引脚输出高电平,打开蜂鸣器
是如何实现的呢?GPIOLIB框架
Linux 驱动实现
内核中 rp_gpio 这个驱动可以用来驱动它,经过 linux grep
命令的查看,可以看到该驱动源代码的位置位于drivers/rongpin/rp_gpio.c :
./drivers/rongpin/rp_gpio.c:275:static int rp_gpio_remove(struct platform_device *pdev)
./drivers/rongpin/rp_gpio.c:281:static const struct of_device_id
rp_gpio_of_match[] = {
./drivers/rongpin/rp_gpio.c:282: { .compatible = "rp_gpio" },
./drivers/rongpin/rp_gpio.c:286:static struct platform_driver rp_gpio_driver = {
./drivers/rongpin/rp_gpio.c:287: .probe = rp_gpio_probe,
./drivers/rongpin/rp_gpio.c:288: .remove = rp_gpio_remove,
./drivers/rongpin/rp_gpio.c:290: .name = "rp_gpio",
./drivers/rongpin/rp_gpio.c:291: .of_match_table =
of_match_ptr(rp_gpio_of_match),
./drivers/rongpin/rp_gpio.c:295:module_platform_driver(rp_gpio_driver);
这部分是厂商自己实现的 gpio 驱动,驱动代码提供了普适的 GPIO 操作接口:open、write、read:
static const struct file_operations gpio_ops = {
.owner = THIS_MODULE,
.open = gpio_open,
.write = gpio_write,
.read = gpio_read,
};
文件目录:kernel/drivers/rongpin/rp_gpio.c
驱动实现也很简单,使用的是 GPIOLIB 框架来控制这些 GPIO ,驱动会去设备树中搜索 compatible 属性:rp_gpio
,找到了这个属性设备树和驱动就匹配上了,具体定义如下:
static const struct of_device_id rp_gpio_of_match[] =
{
{ .compatible = "rp_gpio" },
{ }
};
static struct platform_driver rp_gpio_driver =
{
.probe = rp_gpio_probe,
.remove = rp_gpio_remove,
.driver = {
.name = "rp_gpio",
.of_match_table = of_match_ptr(rp_gpio_of_match),
},
};
module_platform_driver(rp_gpio_driver);
MODULE_LICENSE("GPL");
驱动匹配成功正常工作时最先开始运行的是 probe 函数,也就是最先调用的是 rp_gpio_probe ,实现如下:
static int rp_gpio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *child_np;
struct device *dev = &pdev->dev;
static struct proc_dir_entry *root_entry_gpio;
enum of_gpio_flags gpio_flags;
int ret = 0;
int gpio_cnt = 0;
char gpio_name_num[20];
int gpio_in_cnt = 0;
int cnt = 0;
//向内核申请内容给gpio_data变量
gpio_data = devm_kzalloc(&pdev->dev, sizeof(struct
rp_gpio_data), GFP_KERNEL);
if (!gpio_data)
{
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
//获取子节点的数量
gpio_data->gpio_dts_num = of_get_child_count(np);
printk("rp_gpio prepare build %d gpio\n", gpio_data->gpio_dts_num);
if (gpio_data->gpio_dts_num == 0)
{
dev_info(&pdev->dev, "no gpio defined\n");
}
/* create node */
//在proc目录下创建rp_gpio节点
root_entry_gpio = proc_mkdir("rp_gpio", NULL);
/*遍历所有节点*/
for_each_child_of_node(np, child_np)
{
/* parse dts */
/*从设备树中获取节点对应的GPIO编号以及标志*/
gpio_data->rp_gpio_num[gpio_cnt].gpio_num =
of_get_named_gpio_flags(child_np, "gpio_num", 0, &gpio_flags);
if (!gpio_is_valid(gpio_data->rp_gpio_num[gpio_cnt].gpio_num))
{
return -1;
}
gpio_data->rp_gpio_num[gpio_cnt].gpio_name = (char*)child_np -> name;
gpio_data->rp_gpio_num[gpio_cnt].action = gpio_flags;
gpio_data->rp_gpio_num[gpio_cnt].gpio_ctrl = gpio_cnt;
/*读取节点中的 32 位整数的值*/
of_property_read_u32(child_np, "gpio_function", &(gpio_data->rp_gpio_num[gpio_cnt].gpio_function));
printk("rp_gpio request %s\n", gpio_data->rp_gpio_num[gpio_cnt].gpio_name);
/*通过获取到的gpio_function的数值进行判断*/
switch(gpio_data->rp_gpio_num[gpio_cnt].gpio_function)
{
/*如果配置为输入,则走这个分支*/
case GPIO_FUNCTION_INPUT : /* init input gpio */
/*申请GPIO*/
ret = gpio_request(gpio_data->rp_gpio_num[gpio_cnt].gpio_num, "gpio_num");
if (ret < 0)
{
printk("gpio%d request error\n", gpio_data->rp_gpio_num[gpio_cnt].gpio_num);
}
else
{
printk("success request gpio %d in\n", gpio_data->rp_gpio_num[gpio_cnt].gpio_num);
/*设置GPIO方向为输入*/
gpio_direction_input(gpio_data->rp_gpio_num[gpio_cnt].gpio_num);
//gpio_direction_output(gpio_data->rp_gpio_num[gpio_cnt].gpio_num,!gpio_data->rp_gpio_num[gpio_cnt].action);
event_flag = gpio_flags;
of_property_read_u32(child_np, "send_mode", &(gpio_data->rp_gpio_num[gpio_cnt].send_mode));
of_property_read_u32(child_np, "gpio_event", &(gpio_data->rp_gpio_num[gpio_cnt].gpio_event));
gpio_in_cnt++;
}
break;
/*如果配置为输出,则走这个分支*/
case GPIO_FUNCTION_OUTPUT : /* init output gpio */
/*申请GPIO*/
ret = gpio_request(gpio_data->rp_gpio_num[gpio_cnt].gpio_num, "gpio_num");
if (ret < 0)
{
printk("gpio%d request error\n", gpio_data->rp_gpio_num[gpio_cnt].gpio_num);
return ret;
}
else
{
/*设置GPIO的方向为输出,并且设置具体的数值*/
ret = gpio_direction_output(gpio_data->rp_gpio_num[gpio_cnt].gpio_num, !gpio_data->rp_gpio_num[gpio_cnt].action);
printk("success request gpio%d out\n", gpio_data->rp_gpio_num[gpio_cnt].gpio_num);
}
break;
/*这个是厂商自己定义的,暂时不清楚是什么作用,应该是预留的,和GPIO_FUNCTION_OUTPUT的功
能一致*/
case GPIO_FUNCTION_FLASH :
ret = gpio_request(gpio_data->rp_gpio_num[gpio_cnt].gpio_num, "gpio_num");
if (ret < 0)
{
printk("gpio%d request error\n", gpio_data->rp_gpio_num[gpio_cnt].gpio_num);
return ret;
}
else
{
/*设置GPIO的方向为输出,并且设置具体的数值*/
gpio_direction_output(gpio_data->rp_gpio_num[gpio_cnt].gpio_num, !gpio_data->rp_gpio_num[gpio_cnt].action);
printk("success request gpio%d flash\n", gpio_data->rp_gpio_num[gpio_cnt].gpio_num);
gpio_cnt++;
}
break;
}
/*拼接GPIO名称以及编号*/
sprintf(gpio_name_num, gpio_data -> rp_gpio_num[gpio_cnt].gpio_name, gpio_cnt);
/*在PROC目录下创建设备节点*/
proc_create(gpio_name_num, 0666, root_entry_gpio, &gpio_ops);
gpio_cnt++;
}
/*如果GPIO的功能被定义为输入的作用*/
if (gpio_in_cnt > 0)
{
/*定义一个定时器*/
timer_setup(&(gpio_data->mytimer), send_event, 0);
gpio_data->mytimer.expires = jiffies + msecs_to_jiffies(10000);
add_timer(&(gpio_data->mytimer));
/*注册input子系统,将这个功能描述成一个输入设备*/
/* init struct input_dev */
gpio_data->input = devm_input_allocate_device(dev);
gpio_data->input->name = "gpio_event";
gpio_data->input->phys = "gpio_event/input1";
gpio_data->input->dev.parent = dev;
gpio_data->input->id.bustype = BUS_HOST;
gpio_data->input->id.vendor = 0x0001;
gpio_data->input->id.product = 0x0001;
gpio_data->input->id.version = 0x0100;
for(cnt = 0; cnt < gpio_cnt; cnt++)
{
if (gpio_data->rp_gpio_num[cnt].gpio_function == 1)
{
input_set_capability(gpio_data->input, EV_KEY, gpio_data -> rp_gpio_num[cnt].gpio_event);
}
}
ret = input_register_device(gpio_data->input);
}
/*设置platform驱动数据,将数据挂载到pdev->dev.driver_data*/
platform_set_drvdata(pdev, gpio_data);
return 0;
}
控制引脚输出高低电平
使用UART3的RXD引脚
修改:kernel/arch/arm/boot/dts/rongpin/rv1126_1109_common.dtsi
/*&uart3 {
pinctrl-names = "default";
pinctrl-0 = <&uart3m2_xfer>;
status = "okay";
};*/
修改:kernel/arch/arm/boot/dts/pro-rv1126.dts
//rpgpio init
rp_gpio {
status = "okay";
compatible = "rp_gpio";
buzzer{
gpio_num = <&gpio3 RK_PA1 GPIO_ACTIVE_LOW>;
gpio_function = <0>; //0:output 1:input
};
...
重新编译Kernel和设备树就可以了。
在开发板上看看设备节点
[root@RV1126_RV1109:/]# ls /proc/rp_gpio/
bl_led gpio0b7 gpio2b7 gpio2c2 gpio2c4 gpio3c2 otg_host
buzzer gpio2b4 gpio2c1 gpio2c3 gpio2c6 ir_led
OK,出来了!
可以使用命令进行测试:
- 引脚输出高电平:
echo 1 > /proc/rp_gpio/buzzer
- 引脚输出低电平:
echo 0 > /proc/rp_gpio/buzzer
引脚接LED、蜂鸣器、继电器都可以。
综合测试
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd = 0;
struct input_event buttons_event;
unsigned long cur_ms = 0;
static int flg = 0;
fd = open("/dev/input/event3", O_RDWR);
if (fd < 0)
printf("can't open!\n");
while (1)
{
read(fd, &buttons_event, sizeof(struct input_event));
// if(buttons_event.type == EV_SYN)
// continue;
if((buttons_event.code == 11) && (buttons_event.value == 0x00))
{
if(flg == 0)
{
system("echo 1 > /proc/rp_gpio/buzzer");
printf("echo 1\r\n");
flg = 1;
}
else
{
system("echo 0 > /proc/rp_gpio/buzzer");
printf("echo 0\r\n");
flg = 0;
}
}
cur_ms = (buttons_event.time.tv_sec * 1000) + (buttons_event.time.tv_usec/1000);
//打印时间,事件类型,事件码,事件值
printf("cur_ms:%ld type:0x%x code:%d value:%d\n",
cur_ms,
buttons_event.type,
buttons_event.code,
buttons_event.value);
}
close(fd);
return 0;
}
编译:
arm-linux-gnueabihf-gcc key-buzzer.c