27. Глава 5.26: Подключение RGB LED к LED class через DT и кастомный драйвер

27.1. Цель

Подключить RGB-светодиод к подсистеме LED class в Linux без указания GPIO в Device Tree, используя кастомный драйвер, в котором все GPIO определяются внутри кода.

27.2. Исходный код драйвера с комментариями

  1#include <linux/module.h>
  2#include <linux/init.h>
  3#include <linux/of.h>
  4#include <linux/of_device.h>
  5#include <linux/platform_device.h>
  6#include <linux/leds.h>
  7#include <linux/gpio.h>
  8#include <linux/gpio/consumer.h>
  9#include <linux/slab.h>
 10#include <linux/kernel.h>
 11#include <linux/types.h>
 12
 13#define MAX_LEDS 3
 14
 15struct rgb_led {
 16    struct led_classdev cdev;
 17    int gpio;
 18};
 19
 20struct rgb_led_data {
 21    struct rgb_led *leds[MAX_LEDS];
 22    int count;
 23};
 24
 25static void rgb_brightness_set(struct led_classdev *cdev, enum led_brightness brightness)
 26{
 27    struct rgb_led *led = container_of(cdev, struct rgb_led, cdev);
 28    gpio_set_value_cansleep(led->gpio, brightness ? 1 : 0);
 29}
 30
 31static int rgb_led_probe(struct platform_device *pdev)
 32{
 33    struct device_node *np = pdev->dev.of_node;
 34    struct device_node *child;
 35    struct rgb_led_data *data;
 36    const char *label;
 37    int ret, i;
 38    int gpios[] = { 16, 20, 21 }; // RED, GREEN, BLUE
 39
 40    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
 41    if (!data)
 42        return -ENOMEM;
 43
 44    i = 0;
 45    for_each_child_of_node(np, child) {
 46        struct rgb_led *led;
 47
 48        if (i >= MAX_LEDS)
 49            break;
 50
 51        led = kzalloc(sizeof(*led), GFP_KERNEL);
 52        if (!led)
 53            return -ENOMEM;
 54
 55        led->gpio = gpios[i];
 56
 57        ret = gpio_request(led->gpio, "rgb-led");
 58        if (ret) {
 59            dev_err(&pdev->dev, "Failed to request GPIO %d\n", led->gpio);
 60            kfree(led);
 61            return ret;
 62        }
 63
 64        gpio_direction_output(led->gpio, 0);
 65
 66        if (of_property_read_string(child, "label", &label))
 67            label = "rgb:unknown";
 68
 69        led->cdev.name = kstrdup(label, GFP_KERNEL);
 70        led->cdev.brightness = LED_OFF;
 71        led->cdev.max_brightness = 1;
 72        led->cdev.brightness_set = rgb_brightness_set;
 73
 74        ret = led_classdev_register(&pdev->dev, &led->cdev);
 75        if (ret) {
 76            dev_err(&pdev->dev, "Failed to register LED %s\n", label);
 77            gpio_set_value(led->gpio, 0);
 78            gpio_free(led->gpio);
 79            kfree(led->cdev.name);
 80            kfree(led);
 81            return ret;
 82        }
 83
 84        dev_info(&pdev->dev, "Registered LED: %s on GPIO %d\n", label, led->gpio);
 85        data->leds[i++] = led;
 86    }
 87
 88    data->count = i;
 89    platform_set_drvdata(pdev, data);
 90
 91    dev_info(&pdev->dev, "RGB LED driver initialized with %d LED(s)\n", data->count);
 92    return 0;
 93}
 94
 95static int rgb_led_remove(struct platform_device *pdev)
 96{
 97    struct rgb_led_data *data = platform_get_drvdata(pdev);
 98    int i;
 99
100    for (i = 0; i < data->count; i++) {
101        struct rgb_led *led = data->leds[i];
102        if (!led)
103            continue;
104
105        pr_info("Unregistering LED: %s from GPIO %d\n", led->cdev.name, led->gpio);
106        led_classdev_unregister(&led->cdev);
107        gpio_set_value(led->gpio, 0);
108        gpio_free(led->gpio);
109        kfree(led->cdev.name);
110        kfree(led);
111    }
112
113    pr_info("RGB LED driver removed\n");
114    return 0;
115}
116
117static const struct of_device_id rgb_led_of_match[] = {
118    { .compatible = "myvendor,rgb-led" },
119    { /* sentinel */ }
120};
121MODULE_DEVICE_TABLE(of, rgb_led_of_match);
122
123static struct platform_driver rgb_led_driver = {
124    .driver = {
125        .name = "rgb-led",
126        .of_match_table = rgb_led_of_match,
127    },
128    .probe = rgb_led_probe,
129    .remove = rgb_led_remove,
130};
131
132module_platform_driver(rgb_led_driver);
133
134MODULE_LICENSE("GPL");
135MODULE_AUTHOR("Your Name");
136MODULE_DESCRIPTION("Reloadable RGB LED driver with dmesg logging");

—

27.3. Фрагмент Device Tree

rgb-led@0 {
    compatible = "myvendor,rgb-led";
    status = "okay";

    red {
        label = "rgb:red";
    };

    green {
        label = "rgb:green";
    };

    blue {
        label = "rgb:blue";
    };
};

Комментарии:

  • Описывается один узел rgb-led@0 с подузлами red, green, blue

  • Пины GPIO в DT не указываются

  • Все LED автоматически получают label и отображаются в /sys/class/leds/

27.4. Проверка результата

# Проверка устройств LED class
ls /sys/class/leds/
# → должны появиться:
# rgb:red  rgb:green  rgb:blue

# Включение красного светодиода
echo 1 | sudo tee /sys/class/leds/rgb:red/brightness

# Выключение синего
echo 0 | sudo tee /sys/class/leds/rgb:blue/brightness

# Проверка, что узел в DT активен
ls /proc/device-tree/soc/rgb-led@0/
# → red green blue compatible status ...

27.5. Вывод

Мы реализовали минимальный, понятный, переносимый драйвер для RGB LED, где вся логика и GPIO-привязка находятся внутри модуля. Управление выполняется через стандартный LED class интерфейс, без использования устаревшего sysfs GPIO или userspace-демонов.