Rockchip系列之浅度分析LED状态灯 Driver篇(1)

简介: Rockchip系列之浅度分析LED状态灯 Driver篇(1)

在本文中,将介绍如何在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!

相关文章
|
4月前
Rockchip系列之LED状态灯 串口收发数据流程以及控制状态显示(3)
Rockchip系列之LED状态灯 串口收发数据流程以及控制状态显示(3)
78 0
|
4月前
|
存储 Android开发
Rockchip系列之客制化GPIO接口Driver部分(2)
Rockchip系列之客制化GPIO接口Driver部分(2)
59 0
|
4月前
Rockchip系列之LED状态灯 CAN收发数据流程以及控制状态显示(4)
Rockchip系列之LED状态灯 CAN收发数据流程以及控制状态显示(4)
79 3
|
计算机视觉
树莓派开发笔记(五):GPIO引脚介绍和GPIO的输入输出使用(驱动LED灯、检测按键)
树莓派开发笔记(五):GPIO引脚介绍和GPIO的输入输出使用(驱动LED灯、检测按键)
树莓派开发笔记(五):GPIO引脚介绍和GPIO的输入输出使用(驱动LED灯、检测按键)
|
10月前
|
供应链 数据可视化 程序员
OpenHarmony:如何使用HDF驱动控制LED灯
OpenHarmony:如何使用HDF驱动控制LED灯
126 0
STM32(HAL库)驱动SHT30温湿度传感器通过串口进行打印
STM32(HAL库)驱动SHT30温湿度传感器通过串口进行打印
|
芯片
SPI+DMA驱动和控制WS2812彩色RGB灯
SPI+DMA驱动和控制WS2812彩色RGB灯
462 0
SPI+DMA驱动和控制WS2812彩色RGB灯
STM32:GPIO--点亮灯(软件部分+操作步骤+解释)
STM32:GPIO--点亮灯(软件部分+操作步骤+解释)
158 0
STM32:GPIO--点亮灯(软件部分+操作步骤+解释)