在本文中,将介绍如何在Rockchip RK3568平台上使用LED状态灯,以及如何通过设备树和内核驱动来控制它们。LED状态灯是一种常见的硬件设备,可以用来显示系统的运行状态、故障提示、用户交互等功能。硬件平台搞了9组LED状态灯,每组有红色和绿色两种颜色,可以通过GPIO引脚来控制。
我准备做个关于LED的小系列 , 开干之前我们先确定硬件对应的GPIO 确定好关系之后我们开干。
设备树配置
要使用LED状态灯,首先需要在设备树中配置它们的相关信息。以xxx-rk3568.dts为例(自定义的具体看自己软件是用哪个dts , 定义到(/ {的下面)),看看如何定义一个名为leds的节点:
leds { compatible = "rockchip,rk3568-led-controller"; red-gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>, <&gpio0 19 GPIO_ACTIVE_HIGH>,// <&gpio0 19 GPIO_ACTIVE_HIGH>, <&gpio0 13 GPIO_ACTIVE_HIGH>, <&gpio2 27 GPIO_ACTIVE_HIGH>, <&gpio1 27 GPIO_ACTIVE_HIGH>, <&gpio1 25 GPIO_ACTIVE_HIGH>, <&gpio3 3 GPIO_ACTIVE_HIGH>, <&gpio2 30 GPIO_ACTIVE_HIGH>, <&gpio3 5 GPIO_ACTIVE_HIGH>, <&gpio0 5 GPIO_ACTIVE_HIGH>; green-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>, <&gpio0 18 GPIO_ACTIVE_HIGH>, <&gpio3 1 GPIO_ACTIVE_HIGH>, <&gpio1 26 GPIO_ACTIVE_HIGH>, <&gpio1 24 GPIO_ACTIVE_HIGH>, <&gpio3 2 GPIO_ACTIVE_HIGH>, <&gpio2 26 GPIO_ACTIVE_HIGH>, <&gpio3 4 GPIO_ACTIVE_HIGH>, <&gpio0 0 GPIO_ACTIVE_HIGH>; };
这里需要注意以下几点:
- compatible属性用来指定该节点对应的驱动名称,这里是"rockchip,rk3568-led-controller"。
- red-gpios和green-gpios属性用来指定每组LED状态灯的红色和绿色GPIO引脚,格式为,其中phandle是GPIO控制器的节点引用,pin是GPIO引脚编号,flags是GPIO引脚的激活方式,可以是GPIO_ACTIVE_LOW或GPIO_ACTIVE_HIGH。这里定义了9组LED状态灯,每组有两个GPIO引脚。
- 这些GPIO引脚必须在GPIO控制器节点中定义,并且不能与其他设备冲突。例如,在xxx-rk3568.dts中,可以看到以下代码:
这里定义了一个名为gpio0的GPIO控制器节点,它有以下属性:
- compatible属性用来指定该节点对应的驱动名称,这里是"rockchip,gpio-bank"。
- reg属性用来指定该节点对应的寄存器地址和大小,这里是<0x0 0xfdd60000 0x0 0x100>,表示从0xfdd60000开始,大小为0x100。
- interrupts属性用来指定该节点对应的中断号和触发方式,这里是<GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>,表示使用GIC的SPI模式,中断号为32,触发方式为高电平。
- clocks属性用来指定该节点对应的时钟源,这里是<&pmucru PCLK_GPIO0>,表示使用pmucru节点下的PCLK_GPIO0时钟。
- gpio-controller属性用来标识该节点是一个GPIO控制器。
- #gpio-cells属性用来指定该节点提供的GPIO引脚的数量,这里是<2>,表示每个GPIO引脚需要两个参数来描述。
- status属性用来指定该节点的状态,这里是"okay",表示启用该节点。
内核驱动实现
要控制LED状态灯,还需要在内核中实现一个对应的驱动。以kernel/drivers/leds/led_control.c为例,看看如何实现一个名为rk3568-led-controller的驱动:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/gpio.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_gpio.h> MODULE_LICENSE("GPL"); // Define the maximum number of LEDs #define LED_MAX 9 // Define the maximum number of states for each LED #define LED_STATE_MAX 2 // Declare the required variables static int led_states[LED_MAX][LED_STATE_MAX] = {0}; static int gpio_pins[LED_MAX][LED_STATE_MAX]; static char *led_names[LED_MAX] = {"LED 0", "LED 1", "LED 2", "LED 3", "LED 4", "LED 5", "LED 6", "LED 7", "LED 8"}; static bool gpio_requested[LED_MAX][LED_STATE_MAX] = {{false}}; /*int led_control_set_leds_by_str(const char *buf) { int led, color, state; if (sscanf(buf, "%d%d%d", &led, &color, &state) != 3) { return -EINVAL; } if (led < 0 || led >= LED_MAX || color < 0 || color >= LED_STATE_MAX || (state != 0 && state != 1)) { return -EINVAL; } led_set_state(led, color, state); return 0; }*/ // Define the functions void led_set_state(int led, int color, int state) { if (gpio_requested[led][color] == false) { //printk(KERN_WARNING "GPIO pin %d for LED %d color %d is not available\n", gpio_pins[led][color], led, color); return; } //printk("leon>led_set_state %d,%d,%d\n",led,color,state); gpio_set_value(gpio_pins[led][color], state); // low or high led_states[led][color] = state; } int led_control_set_leds_by_str(const char *buf) { int led, color, state; if (sscanf(buf, "%d%d%d", &led, &color, &state) != 3) { return -EINVAL; } if (led < 0 || led >= LED_MAX || color < 0 || color >= LED_STATE_MAX || (state != 0 && state != 1)) { return -EINVAL; } led_set_state(led, color, state); return 0; } // This structure holds the information from the device tree struct led_controller_data { int led_count; struct { int red; int green; } gpios[LED_MAX]; }; static ssize_t led_show(struct device *dev __attribute__((unused)), struct device_attribute *attr, char *buf) //static ssize_t led_show(struct device *dev, struct device_attribute *attr, char *buf) { int i, j; ssize_t len = 0; char *color_names[] = {"红灯", "绿灯"}; char *state_names[] = {"打开", "关闭"}; for (i = 0; i < LED_MAX; i++) { for (j = 0; j < LED_STATE_MAX; j++) { len += scnprintf(buf + len, PAGE_SIZE - len, "LED %d组 %s GPIO_%d为[%s] STATE: %d%s\n", i, color_names[j], gpio_pins[i][j], state_names[led_states[i][j]], led_states[i][j], gpio_requested[i][j] ? "" : " (被占用)"); } } return len; } static ssize_t led_store(struct device *dev __attribute__((unused)), struct device_attribute *attr, const char *buf, size_t count) //static ssize_t led_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int led, color, state; if (sscanf(buf, "%d%d%d", &led, &color, &state) != 3) { return -EINVAL; } if (led < 0 || led >= LED_MAX || color < 0 || color >= LED_STATE_MAX || (state != 0 && state != 1)) { return -EINVAL; } led_set_state(led, color, state); return count; } static DEVICE_ATTR_RW(led); static void led_init_state(void) { int i, j; for (i = 0; i < LED_MAX; i++) { for (j = 0; j < LED_STATE_MAX; j++) { gpio_requested[i][j] = false; led_set_state(i, j, 1); // turn off LED //printk("led_init %d,%d,%d\n",i,j,0); } } } static struct attribute *led_attrs[] = { &dev_attr_led.attr, NULL, }; static struct attribute_group led_attr_group = { .attrs = led_attrs, }; static struct kobject *led_kobj; static int led_probe(struct platform_device *pdev) { int i, j; int res = 0; struct led_controller_data *data; printk("11 led_probe 55\n"); data = devm_kzalloc(&pdev->dev, sizeof(struct led_controller_data), GFP_KERNEL); if (!data) return -ENOMEM; // Get the GPIO pins from the device tree for (i = 0; i < LED_MAX; i++) { data->gpios[i].red = of_get_named_gpio(pdev->dev.of_node, "red-gpios", i); data->gpios[i].green = of_get_named_gpio(pdev->dev.of_node, "green-gpios", i); gpio_pins[i][0] = data->gpios[i].red; gpio_pins[i][1] = data->gpios[i].green; } // Initialize the LED states // // Request the GPIO pins for each LED for (i = 0; i < LED_MAX; i++) { for (j = 0; j < LED_STATE_MAX; j++) { res = gpio_request(gpio_pins[i][j], led_names[i]); if (res < 0) { printk("Failed to request GPIO pin %d for LED %d color %d\n", gpio_pins[i][j], i, j); gpio_requested[i][j] = false; continue; } if (i == 1 && j == 0) { // Check if the current GPIO is the one you want to modify gpio_direction_output(gpio_pins[i][j], 0); // Set it to low } else { gpio_direction_output(gpio_pins[i][j], 1); // Set others to high } printk("led_init_state to request GPIO pin %d for LED %d color %d\n", gpio_pins[i][j], i, j); gpio_requested[i][j] = true; } } // Create a kobject and sysfs group for the LED attributes led_kobj = kobject_create_and_add("leds", &pdev->dev.kobj); if (!led_kobj) { //printk(KERN_ERR "Failed to create kobject for LEDs\n"); res = -ENOMEM; goto free_gpio; } res = sysfs_create_group(led_kobj, &led_attr_group); if (res < 0) { //printk(KERN_ERR "Failed to create sysfs entries for LEDs\n"); kobject_put(led_kobj); goto free_gpio; } platform_set_drvdata(pdev, data); //printk(KERN_INFO "LED kernel module initialized\n"); return 0;//-EPROBE_DEFER; free_gpio: for (i = 0; i < LED_MAX; i++) { for (j = 0; j < LED_STATE_MAX; j++) { if (gpio_requested[i][j]) { gpio_free(gpio_pins[i][j]); } } } return res; } static int led_remove(struct platform_device *pdev) { int i, j; struct led_controller_data *data = platform_get_drvdata(pdev); for (i = 0; i < LED_MAX; i++) { for (j = 0; j < LED_STATE_MAX; j++) { if (gpio_requested[i][j]) { gpio_free(gpio_pins[i][j]); } } } // Remove the sysfs group and kobject for the LED attributes sysfs_remove_group(led_kobj, &led_attr_group); kobject_put(led_kobj); // printk(KERN_INFO "LED kernel module removed\n"); // Free the allocated memory devm_kfree(&pdev->dev, data); //printk(KERN_INFO "LED kernel module removed\n"); return 0; } static const struct of_device_id led_dt_ids[] = { {.compatible = "rockchip,rk3568-led-controller"}, {} }; MODULE_DEVICE_TABLE(of, led_dt_ids); static struct platform_driver led_driver = { .driver = { .name = "rk3568-led-controller", .of_match_table = of_match_ptr(led_dt_ids), }, .probe = led_probe, .remove = led_remove, }; module_platform_driver(led_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("28"); MODULE_DESCRIPTION("LED controller driver for RK3568"); MODULE_VERSION("1.0");
下面详细解释下函数的作用:
- led_set_state函数用来设置某个LED的某种颜色的状态,参数为LED编号、颜色编号和状态值(0或1)。该函数会检查GPIO引脚是否已经被请求,如果是,则调用gpio_set_value函数来设置GPIO引脚的电平,同时更新led_states数组中的状态值。
- led_control_set_leds_by_str函数用来解析用户输入的字符串,格式为"led color state",例如"1 0 1"表示设置第1组LED的红色为打开。该函数会检查输入参数的合法性,如果合法,则调用led_set_state函数来设置LED状态-----(这个接口我们留着后面有用)。
- led_show函数用来显示所有LED的状态信息,格式为"LED group color GPIO_pin [state] STATE: value (occupied)",例如"LED 1组 红灯 GPIO_19 [打开] STATE: 1"表示第1组LED的红色GPIO引脚为19,当前状态为打开,值为1。该函数会遍历所有LED和颜色,使用scnprintf函数来格式化输出到buf中,并返回输出长度。
- led_store函数用来接收用户输入的字符串,格式同led_control_set_leds_by_str函数,该函数会调用led_control_set_leds_by_str函数来设置LED状态,并返回输入长度。
- DEVICE_ATTR_RW宏用来定义一个名为led的设备属性,该属性有读写权限,对应的读写函数为led_show和led_store。
- led_init_state函数用来初始化所有LED的状态,将它们都设置为关闭。该函数会遍历所有LED和颜色,将gpio_requested数组中的值设为false,然后调用led_set_state函数来设置LED状态。
- led_attrs数组用来存放所有的设备属性,这里只有一个led属性。
- led_attr_group结构体用来定义一个属性组,该属性组包含了led_attrs数组中的所有属性。
- led_kobj指针用来指向一个kobject对象,该对象用来在sysfs中创建一个名为leds的目录。
- led_probe函数用来在驱动被加载时执行一些初始化操作,参数为一个platform_device结构体指针,该结构体包含了设备树中的相关信息。该函数会执行以下步骤:
- 分配一个led_controller_data结构体的内存空间,并将其赋值给data指针。
- 从设备树中获取每组LED的红色和绿色GPIO引脚,并将其赋值给data->gpios数组和gpio_pins数组。
- 调用led_init_state函数来初始化所有LED的状态。
- 请求每个GPIO引脚,并设置其方向为输出。如果请求失败,则打印警告信息,并将gpio_requested数组中的对应值设为false。如果请求成功,则将gpio_requested数组中的对应值设为true。如果当前GPIO引脚是第1组LED的红色,则将其电平设为低,否则设为高。
- 调用kobject_create_and_add函数来创建一个名为leds的kobject对象,并将其赋值给led_kobj指针。如果创建失败,则返回-ENOMEM错误码,并跳转到free_gpio标签处。
- 调用sysfs_create_group函数来在sysfs中创建一个名为leds的目录,并将led_attr_group属性组中的所有属性添加到该目录下。如果创建失败,则返回错误码,并调用kobject_put函数来释放led_kobj指针,然后跳转到free_gpio标签处。
- 调用platform_set_drvdata函数来将data指针存储到pdev结构体中,以便在其他地方使用。
- 返回0表示成功。
- free_gpio标签处用来释放所有已经请求的GPIO引脚。遍历所有LED和颜色,如果gpio_requested数组中的对应值为true,则调用gpio_free函数来释放GPIO引脚。
sysfs接口使用
要控制LED状态灯,可以通过sysfs接口来操作。在/sys/devices/platform/目录下,可以找到一个名为rk3568-led-controller.0的目录,这是驱动对应的设备目录。在该目录下,可以看到一个名为leds的子目录,这是在驱动中创建的kobject对象对应的目录。在该目录下,可以看到一个名为led的文件,这是在驱动中定义的设备属性对应的文件。
要查看所有LED的状态信息,可以使用cat命令来读取该文件:
这里可以看到每组LED的红色和绿色GPIO引脚的编号、状态和值。注意,这里的状态是指GPIO引脚的电平,而不是LED的亮灭。如果GPIO引脚是高电平,则状态为[打开],值为1;如果GPIO引脚是低电平,则状态为[关闭],值为0。但是,由于的LED是低电平有效,也就是说,当GPIO引脚为低电平时,LED才会亮起,所以这里的状态和值与LED的亮灭是相反的。例如,第1组LED的红色GPIO引脚为19,状态为[打开],值为0,表示GPIO引脚为低电平,所以该LED是亮的;而第0组LED的红色GPIO引脚为14,状态为[关闭],值为1,表示GPIO引脚为高电平,所以该LED是灭的。
要设置某个LED的某种颜色的状态,可以使用echo命令来写入该文件:
$ echo "0 0 0" > /sys/devices/platform/leds/leds/led
这里输入了"0 0 0",表示设置第0组LED的红色为关闭。注意,这里的关闭是指GPIO引脚的电平,而不是LED的亮灭。如果想让该LED灭掉,应该输入"0 0 1",表示设置第0组LED的红色为打开。同理,如果想让第2组LED的绿色亮起,应该输入"2 1 0",表示设置第2组LED的绿色为关闭。
总结
本文介绍了如何在Rockchip RK3568平台上使用LED状态灯,以及如何通过设备树和内核驱动来控制它们。首先在设备树中定义了一个名为leds的节点,用来指定每组LED状态灯的红色和绿色GPIO引脚。然后在内核中实现了一个名为rk3568-led-controller的驱动,用来请求GPIO引脚、设置GPIO方向、设置和获取GPIO电平、创建sysfs接口等功能。最后通过sysfs接口来查看和控制所有LED状态灯的状态信息。
希望本文对你有所帮助。如果你有任何问题,欢迎在评论区留言。3Q!