在 Linux 系統中,LED 類別模組提供了一個簡化的介面來控制 LED 裝置,讓驅動程式開發更有效率。本文將探討 LED 子系統的核心結構 led_classdev,並講解如何使用 devm_led_classdev_register 函式註冊 LED 裝置。同時,我們會以 RGB LED 為例,詳細說明如何實作一個 RGB LED 類別模組,包含 Device Tree 的組態以及驅動程式的撰寫,其中包含關鍵函式 probe()led_control() 的實作細節,以控制 LED 的亮度和閃爍。

Linux LED 類別

LED 類別簡化了控制 LED 的驅動程式開發。“類別"是裝置和裝置本身的實作。類別可以被視為更廣義的驅動程式。裝置模型具有稱為"驅動程式"的特定物件,但"類別"不是其中之一。

特定類別中的所有裝置往往會暴露出相同的介面,無論是對其他裝置還是對使用者空間(透過 sysfs 或其他方式)。所包含的裝置的一致性程度實際上取決於類別。不過,有些介面是可選的,並非所有裝置都實作它們。同一個類別中的某些裝置與其他裝置完全不同並不罕見。

LED 類別支援實體 LED 的閃爍、閃光和亮度控制功能。此類別需要底層裝置可用(/sys/class/leds/<device>/)。此底層裝置必須能夠開啟或關閉 LED,可能能夠設定亮度,甚至可能提供計時器功能,以自主地以給定的週期和佔空比閃爍 LED。

使用每個裝置子目錄下的 brightness 檔案,可以將適當的 LED 設定為不同的亮度級別,例如,不僅可以開啟和關閉,還可以調暗。傳遞亮度級別的資料型別 enum led_brightness 只定義了 LED_OFFLED_HALFLED_FULL 級別:

enum led_brightness {
    LED_OFF = 0,
    LED_HALF = 127,
    LED_FULL = 255,
};

LED 觸發器

LED 類別引入了可選的 LED 觸發器概念。觸發器是根據核心的 LED 事件來源。計時器觸發器是一個例子,它會定期在 LED_OFF 和目前亮度設定之間更改 LED 亮度。“開啟"和"關閉"時間可以透過 /sys/class/leds/<device>/delay_{on,off} sysfs 條目以毫秒為單位指定。您可以獨立於計時器觸發器更改 LED 的亮度值。但是,如果將亮度值設定為 LED_OFF,它也會停用計時器觸發器。

註冊 LED 類別裝置的驅動程式首先會分配並填寫一個 led_classdev 結構(在核心原始碼樹中的 include/linux/leds.h 中宣告),然後呼叫 devm_led_classdev_register(),它會註冊新的 LED 類別物件。struct led_classdev 的宣告如下:

struct led_classdev {
    const char *name;
    enum led_brightness brightness;
    enum led_brightness max_brightness;
    [...]
    /*
     * 設定 LED 亮度級別。對於可以在設定亮度時睡眠的驅動程式,請使用 brightness_set_blocking。
     */
};

使用範例

static struct led_classdev my_led = {
    .name = "my_led",
    .brightness = LED_OFF,
    .max_brightness = LED_FULL,
    .brightness_set_blocking = my_led_set_brightness,
};

static int my_led_probe(struct platform_device *pdev)
{
    [...]
    return devm_led_classdev_register(&pdev->dev, &my_led);
}

#### 內容解密:
*   上述範例展示瞭如何註冊一個名為 "my_led"  LED 裝置。
*   定義了一個 `led_classdev` 結構例項 `my_led`,其中包含必要的欄位,如名稱、初始亮度和最大亮度,以及設定亮度的回呼函式。
*    `my_led_probe()` 函式中,呼叫 `devm_led_classdev_register()`  LED 裝置註冊到核心。
*   這樣,使用者空間就可以透過 sysfs 存取和控制該 LED 裝置。

Platform Drivers:LED 子系統與 RGB LED 類別模組實作

在 Linux 核心中,Platform Drivers 扮演著重要的角色,用於管理 SoC(System on Chip)上的各種內建裝置。LED 子系統提供了一個標準化的介面來控制 LED 裝置,使得驅動程式的開發更加簡潔和高效。本章將探討如何使用 LED 子系統來實作一個 RGB LED 類別模組。

LED 子系統核心結構

LED 子系統的核心是 struct led_classdev 結構,它代表了一個 LED 裝置。該結構包含多個函式指標,用於設定 LED 的亮度、取得亮度狀態、以及控制閃爍等。

struct led_classdev {
    const char *name;
    enum led_brightness brightness;
    enum led_brightness max_brightness;
    int flags;
    void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness);
    int (*brightness_set_blocking)(struct led_classdev *led_cdev, enum led_brightness brightness);
    enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
    int (*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off);
    [...]
};

內容解密:

  1. brightness_set 函式用於立即設定 LED 的亮度,可能會阻塞呼叫者直到完成對 LED 裝置暫存器的存取。
  2. brightness_set_blocking 函式與 brightness_set 類別似,但明確指出它可能會阻塞。
  3. brightness_get 函式用於取得 LED 的當前亮度。
  4. blink_set 函式啟動硬體加速的閃爍功能,引數 delay_ondelay_off 指定了閃爍的間隔。

使用 devm_led_classdev_register 註冊 LED 裝置

devm_led_classdev_register 函式用於註冊一個 LED 裝置,並將其與父裝置繫結。當父裝置被移除時,相關的 LED 裝置也會被自動解除註冊。

int devm_led_classdev_register(struct device *parent, struct led_classdev *led_cdev) {
    struct led_classdev **dr;
    int rc;
    dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL);
    if (!dr)
        return -ENOMEM;

    rc = led_classdev_register(parent, led_cdev);
    if (rc) {
        devres_free(dr);
        return rc;
    }
    *dr = led_cdev;
    devres_add(parent, dr);
    return 0;
}

內容解密:

  1. 使用 devres_alloc 分配記憶體來儲存 led_cdev 指標,以確保資源被正確管理。
  2. 呼叫 led_classdev_register 註冊 LED 裝置,如果註冊失敗,則釋放分配的記憶體。
  3. led_cdev 指標儲存並新增到父裝置的資源列表中。

LAB 5.3:RGB LED 類別模組實作

在 LAB 5.3 中,我們將使用 LED 子系統來控制 RGB LED。相較於 LAB 5.2,我們簡化了程式碼並增加了閃爍功能。

Device Tree 描述

在 Device Tree 中,我們定義了一個名為 ledclassRGB 的節點,它包含三個子節點,分別代表紅、綠、藍三個 LED。

&soc {
    ledclassRGB {
        compatible = "arrow,RGBclassleds";
        reg = <0x7e200000 0xb4>;
        pinctrl-names = "default";
        pinctrl-0 = <&led_pins>;
        red {
            label = "red";
        };
        green {
            label = "green";
        };
        blue {
            label = "blue";
            linux,default-trigger = "heartbeat";
        };
    };
};

內容解密:

  1. compatible 屬性指定了驅動程式與裝置的匹配條件。
  2. reg 屬性提供了 GPIO 暫存器的基地址和大小。
  3. 子節點中的 label 屬性定義了每個 LED 的標籤。

程式碼描述

驅動程式的主要部分包括:

  1. 包含必要的標頭檔。
  2. 定義 GPIO 的相關設定和操作函式。
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/leds.h>

#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26

內容解密:

  1. 引入必要的標頭檔以支援模組開發、檔案操作、平台裝置管理、I/O 操作、Device Tree 操作以及 LED 子系統。
  2. 定義了三個 GPIO 編號,分別對應紅、綠、藍三個 LED。這些定義將用於設定 GPIO 暫存器,以控制對應的 LED。

Platform Drivers Chapter 5:RGB LED 驅動程式開發詳解

1. 前言

本文將探討 Linux 平台驅動程式的開發,特別是針對 RGB LED 驅動程式的實作。我們將逐步分析驅動程式的架構、關鍵程式碼以及相關的裝置樹組態。

2. 硬體基礎與需求分析

在開始驅動程式開發之前,我們需要了解 RGB LED 的硬體基礎。RGB LED 通常連線到樹莓派的 GPIO 引腳,透過控制這些引腳的高低電平來控制 LED 的亮滅。

2.1 GPIO 引腳組態

  • 紅色 LED 連線到 GPIO 27
  • 綠色 LED 連線到 GPIO 22
  • 藍色 LED 連線到 GPIO 26

3. 驅動程式開發關鍵步驟

3.1 定義 GPIO 暫存器偏移量與掩碼

首先,我們需要定義 GPIO 暫存器的偏移量以及用於控制 LED 的掩碼。

#define GPFSEL2_offset 0x08
#define GPSET0_offset 0x1C
#define GPCLR0_offset 0x28

#define GPIO_27_INDEX (1 << (GPIO_27 % 32))
#define GPIO_22_INDEX (1 << (GPIO_22 % 32))
#define GPIO_26_INDEX (1 << (GPIO_26 % 32))

#define FSEL_27_MASK (0b111 << ((GPIO_27 % 10) * 3))
#define FSEL_22_MASK (0b111 << ((GPIO_22 % 10) * 3))
#define FSEL_26_MASK (0b111 << ((GPIO_26 % 10) * 3))

#### 內容解密:

  • GPFSEL2_offsetGPSET0_offsetGPCLR0_offset 分別對應 GPIO 功能選擇暫存器2、設定暫存器0和清除暫存器0的偏移量。
  • GPIO_27_INDEXGPIO_22_INDEXGPIO_26_INDEX 用於表示對應 GPIO 引腳的掩碼,用於設定或清除對應引腳的電平。
  • FSEL_27_MASKFSEL_22_MASKFSEL_26_MASK 用於設定對應 GPIO 引腳的功能為輸出模式。

3.2 定義私有資料結構

接下來,我們定義一個私有資料結構 struct led_dev,用於儲存每個 LED 裝置的相關資訊。

struct led_dev {
    u32 led_mask; /* 不同 LED(紅、綠、藍)的掩碼 */
    void __iomem *base; /* GPIO 暫存器的基地址 */
    struct led_classdev cdev; /* LED class 裝置 */
};

#### 內容解密:

  • led_mask 儲存了對應 LED 的掩碼,用於控制 LED 的亮滅。
  • base 是 GPIO 暫存器的基地址,用於存取 GPIO 相關暫存器。
  • cdevled_classdev 結構,用於與 LED 子系統互動。

3.3 probe() 函式實作

probe() 函式中,我們會取得裝置樹中的資源,對映暫存器地址,並為每個子節點分配私有資料結構,最後註冊 LED class 裝置。

static int ledclass_probe(struct platform_device *pdev) {
    // ...
    for_each_child_of_node(dev->of_node, child) {
        struct led_dev *led_device;
        // ...
        led_device = devm_kzalloc(dev, sizeof(*led_device), GFP_KERNEL);
        // ...
        ret = devm_led_classdev_register(dev, &led_device->cdev);
    }
    // ...
}

#### 內容解密:

  • 使用 for_each_child_of_node 遍歷裝置樹中的每個子節點,並為每個子節點分配一個 struct led_dev 結構。
  • 使用 devm_led_classdev_register 將每個 LED 裝置註冊到 LED 子系統。

3.4 led_control() 函式實作

當寫入 /sys/class/leds/<device>/brightness 時,led_control() 函式會被呼叫,用於控制 LED 的亮滅。

static void led_control(struct led_classdev *led_cdev, enum led_brightness b) {
    struct led_dev *led = container_of(led_cdev, struct led_dev, cdev);
    iowrite32(GPIO_SET_ALL_LEDS, led->base + GPCLR0_offset);
    if (b != LED_OFF) /* 開啟 LED */
        iowrite32(led->led_mask, led->base + GPSET0_offset);
    else
        iowrite32(led->led_mask, led->base + GPCLR0_offset); /* 熄滅 LED */
}

#### 內容解密:

  • 使用 container_of 取得包含 led_classdevstruct led_dev 指標。
  • 使用 iowrite32 向 GPIO 暫存器寫入資料,控制 LED 的亮滅。

RGB LED 控制流程圖

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Linux LED 類別模組開發與實作

package "物件導向程式設計" {
    package "核心概念" {
        component [類別 Class] as class
        component [物件 Object] as object
        component [屬性 Attribute] as attr
        component [方法 Method] as method
    }

    package "三大特性" {
        component [封裝
Encapsulation] as encap
        component [繼承
Inheritance] as inherit
        component [多型
Polymorphism] as poly
    }

    package "設計原則" {
        component [SOLID] as solid
        component [DRY] as dry
        component [KISS] as kiss
    }
}

class --> object : 實例化
object --> attr : 資料
object --> method : 行為
class --> encap : 隱藏內部
class --> inherit : 擴展功能
inherit --> poly : 覆寫方法
solid --> dry : 設計模式

note right of solid
  S: 單一職責
  O: 開放封閉
  L: 里氏替換
  I: 介面隔離
  D: 依賴反轉
end note

@enduml

此圖示展示了當寫入 brightness 時,驅動程式如何控制 RGB LED 的亮滅。