在 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_OFF、LED_HALF 和 LED_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);
[...]
};
內容解密:
brightness_set函式用於立即設定 LED 的亮度,可能會阻塞呼叫者直到完成對 LED 裝置暫存器的存取。brightness_set_blocking函式與brightness_set類別似,但明確指出它可能會阻塞。brightness_get函式用於取得 LED 的當前亮度。blink_set函式啟動硬體加速的閃爍功能,引數delay_on和delay_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;
}
內容解密:
- 使用
devres_alloc分配記憶體來儲存led_cdev指標,以確保資源被正確管理。 - 呼叫
led_classdev_register註冊 LED 裝置,如果註冊失敗,則釋放分配的記憶體。 - 將
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";
};
};
};
內容解密:
compatible屬性指定了驅動程式與裝置的匹配條件。reg屬性提供了 GPIO 暫存器的基地址和大小。- 子節點中的
label屬性定義了每個 LED 的標籤。
程式碼描述
驅動程式的主要部分包括:
- 包含必要的標頭檔。
- 定義 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
內容解密:
- 引入必要的標頭檔以支援模組開發、檔案操作、平台裝置管理、I/O 操作、Device Tree 操作以及 LED 子系統。
- 定義了三個 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_offset、GPSET0_offset和GPCLR0_offset分別對應 GPIO 功能選擇暫存器2、設定暫存器0和清除暫存器0的偏移量。GPIO_27_INDEX、GPIO_22_INDEX和GPIO_26_INDEX用於表示對應 GPIO 引腳的掩碼,用於設定或清除對應引腳的電平。FSEL_27_MASK、FSEL_22_MASK和FSEL_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 相關暫存器。cdev是led_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_classdev的struct 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 的亮滅。