对RV1109及瑞芯微平台感兴趣的读者,请自行到文末下载SDK:
以下是我对荣品开发板官方资料的补充,本人用的是淘宝购买的荣品RV1109 开发板,那么如何使用设备树来配置一个 GPIO 呢?
1、设备树文件
kernel/arch/arm/boot/dts/rongpin/rv1126_1109_common.dtsi
2、GPIO 设备树节点及描述
//rpgpio init rp_gpio { status = "okay"; compatible = "rp_gpio"; 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 }; otg_host{ gpio_num = <&gpio0 RK_PA2 GPIO_ACTIVE_LOW>; //0:otg,1:host gpio_function = <0>; }; vdd5v_3v3{ gpio_num = <&gpio0 RK_PA0 GPIO_ACTIVE_HIGH>;//vdd5v and 3v3 gpio_function = <0>; }; spk_on{ gpio_num = <&gpio2 RK_PC5 GPIO_ACTIVE_HIGH>;//spk_on gpio_function = <0>; }; spk_mute{ gpio_num = <&gpio2 RK_PC7 GPIO_ACTIVE_HIGH>; S05rp_init.sh gpio_function = <0>; }; };
status 是一个属性,可以用来描述设备的状态:
compatible 表示兼容的意思,它的作用就是定义一系列的字符串,用来指定硬件是否兼容相应的驱动,其它的部分接下来分析。
3、GPIO节点配置原理
3.1、查看 GPIO 硬件原理图
以配置 bl_led 为例,首先打开官方的底板硬件原理图,可以看到:
可以看到,该LED接在 GPIO2_B6 这个位置。
3.2、 GPIO 驱动实现
3.2.1、设备树节点配置
管脚绑定编号位于:
/kernel/include/dt-bindings/pinctrl/rockchip.h
以下是 bl_led 的设备树节点:
bl_led{ gpio_num = <&gpio2 RK_PB6 GPIO_ACTIVE_LOW>; gpio_function = <0>; //0:output 1:input };
3.2.2、 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, };
驱动实现也很简单,使用的是 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; }
4、GPIO 节点配置
只需要在 2、GPIO设备树节点及描述
中继续添加你想要的节点即可,假设配置 GPIO2 PB7 为输出功能:
rp_gpio{ status = "okay"; compatible = "rp_gpio"; gpio2b7 { gpio_num = <&gpio2 RK_PB7 GPIO_ACTIVE_HIGH>; //默认输出高电平 gpio_function = <0>; }; };
5、如何操作控制 GPIO
以控制摄像头补光灯亮灭为例,摄像头的补光灯对应的 proc 设备节点为:bl_led
开启摄像头补光灯:
在/proc/rp_gpio目录下: echo 1 > bl_led
关闭摄像头补光灯:
在/proc/rp_gpio目录下: echo 0 > bl_led
RV1109 SDK源码及文档:
链接:https://pan.baidu.com/s/1QKkQ3SKwuaTImnH3CbpYkg 提取码:5sg0
往期精彩
如何添加APP到Buildroot里(以瑞芯微rv1126为例)