在嵌入式系統中,平台驅動程式橋接硬體與作業系統,扮演著重要的角色。本文以 Raspberry Pi 為例,說明如何開發一個簡單的平台驅動程式,並探討 Pinctrl 子系統的應用。首先,我們需要在裝置樹中新增節點描述裝置,並編寫驅動程式碼,包含 probe 和 remove 函式,用於裝置的初始化和資源清理。接著,探討 Pinctrl 子系統如何透過 pinctrl_ops、pinconf_ops 和 pinmux_ops 等結構與操作,管理和組態 SoC 的 Pin 腳,例如設定上拉/下拉電阻、驅動強度和多工功能。最後,簡述 GPIO 控制器驅動程式如何與 Pinctrl 子系統整合,實作對 GPIO 的控制。
平台驅動程式(Platform Drivers)實作與解析
在嵌入式系統開發中,平台驅動程式扮演著至關重要的角色。它們負責將硬體裝置與作業系統進行橋接,使得系統能夠有效地控制和管理硬體資源。本文將探討平台驅動程式的基本架構,並以 Raspberry Pi 為例,展示如何開發一個簡單的平台驅動程式。
平台驅動程式基礎
平台驅動程式是 Linux 核心的一部分,用於管理那些不依賴於特定匯流排(如 PCI 或 USB)的裝置。這些驅動程式通常與 SoC(System on Chip)緊密相關,因為它們需要直接與晶片上的硬體進行互動。
關鍵元件
裝置樹(Device Tree):裝置樹是一種描述硬體裝置及其屬性的資料結構。它使得驅動程式能夠與硬體進行解耦,從而提高程式碼的可移植性。
平台驅動程式註冊:驅動程式需要向核心註冊,以便在裝置樹中的相應節點被匹配時獲得通知。
probe() 和 remove() 函式:當裝置被檢測到時,
probe()函式被呼叫,用於初始化裝置。相反,當裝置被移除時,remove()函式被呼叫,用於清理資源。
範例:Raspberry Pi 平台驅動程式開發
接下來,我們將開發一個簡單的平台驅動程式,該驅動程式將在 Raspberry Pi 上執行。
步驟一:建立裝置樹節點
首先,我們需要在裝置樹中新增一個新的節點,以描述我們的裝置。
soc {
hellokeys {
compatible = "arrow,hellokeys";
};
};
步驟二:編寫驅動程式
接下來,我們將編寫驅動程式的程式碼。
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
// 開啟裝置時的處理函式
static int my_dev_open(struct inode *inode, struct file *file)
{
pr_info("my_dev_open() is called.\n");
return 0;
}
// 關閉裝置時的處理函式
static int my_dev_close(struct inode *inode, struct file *file)
{
pr_info("my_dev_close() is called.\n");
return 0;
}
// ioctl 命令處理函式
static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);
return 0;
}
// 定義檔案操作結構
static const struct file_operations my_dev_fops = {
.owner = THIS_MODULE,
.open = my_dev_open,
.release = my_dev_close,
.unlocked_ioctl = my_dev_ioctl,
};
// 定義 miscdevice 結構
static struct miscdevice helloworld_miscdevice = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mydev",
.fops = &my_dev_fops,
};
// probe 函式,在裝置被檢測到時呼叫
static int __init my_probe(struct platform_device *pdev)
{
int ret_val;
pr_info("my_probe() function is called.\n");
ret_val = misc_register(&helloworld_miscdevice);
if (ret_val != 0) {
pr_err("could not register the misc device mydev");
return ret_val;
}
pr_info("mydev: got minor %i\n", helloworld_miscdevice.minor);
return 0;
}
// remove 函式,在裝置被移除時呼叫
static int __exit my_remove(struct platform_device *pdev)
{
pr_info("my_remove() function is called.\n");
misc_deregister(&helloworld_miscdevice);
return 0;
}
// 定義支援的裝置列表
static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,hellokeys"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);
// 定義平台驅動程式結構
static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "hellokeys",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
// 註冊平台驅動程式
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <aliberal@arroweurope.com>");
MODULE_DESCRIPTION("This is the simplest platform driver");
#### 內容解密:
my_dev_open、my_dev_close和my_dev_ioctl函式:這些函式定義了使用者空間應用程式如何與我們的裝置互動。當應用程式開啟、關閉裝置或傳送 ioctl 命令時,這些函式將被呼叫。my_probe和my_remove函式:這兩個函式是平台驅動程式的核心。my_probe在裝置被檢測到時初始化裝置,而my_remove在裝置被移除時清理資源。of_device_id結構:這個結構定義了驅動程式支援的裝置。我們使用compatible欄位來匹配裝置樹中的節點。platform_driver結構:這個結構定義了平台驅動程式的屬性,包括probe和remove函式,以及支援的裝置列表。
編譯與測試
編譯和測試驅動程式的步驟包括:
- 編譯核心模組。
- 將模組複製到 Raspberry Pi。
- 載入模組並檢查日誌輸出。
- 使用
ioctl_test應用程式測試裝置。
平台驅動程式(Platform Drivers)中的Pin控制子系統(Pinctrl Subsystem)
在舊版的Linux Pin多工程式碼中,每個架構都有其特定的Pin多工程式碼和API。許多相似的功能以不同的方式實作。Pin多工必須在SoC層級進行,且無法由裝置驅動程式請求。
Pinctrl子系統的目標與功能
Pinctrl子系統旨在解決這些問題。它在核心原始碼樹中的drivers/pinctrl/中實作,提供:
- 一個API來註冊Pinctrl驅動程式,例如,知道Pin列表、其功能和如何組態它們的實體。此API由SoC的特定驅動程式使用,例如
pinctrl-bcm2835.c(位於核心原始碼樹中的drivers/pinctrl/bcm/),以暴露Pin多工能力。 - 一個API供裝置驅動程式請求多工特定的一組Pin。
- 與SoC的GPIO驅動程式的互動。
大多數Pinctrl驅動程式提供Device Tree繫結,並且Pin多工必須在Device Tree中描述。確切的Device Tree繫結取決於每個Pinctrl驅動程式。對於Broadcom BCM2835 GPIO(和Pin多工)控制器,Device Tree繫結在核心原始碼樹中的Documentation/devicetree/bindings/pinctrl/brcm,bcm2835-gpio.txt中描述。
Pinctrl子系統處理的事務
Linux中的Pinctrl子系統處理以下事務:
- 列舉和命名可控的Pin。
- Pin的多工。在SoC中,幾個Pin也可以形成一個Pin群組,以實作特定的功能。Pin控制子系統必須管理所有Pin群組。
- Pin的組態,例如軟體控制的偏置和驅動模式特定的Pin,例如上拉/下拉、開漏、負載電容等。
程式碼範例:BCM2835 GPIO Pin的定義
/* pins are just named GPIO0..GPIO53 */
#define BCM2835_GPIO_PIN(a) PINCTRL_PIN(a, "gpio" #a)
static struct pinctrl_pin_desc bcm2835_gpio_pins[] = {
BCM2835_GPIO_PIN(0),
BCM2835_GPIO_PIN(1),
BCM2835_GPIO_PIN(2),
// ...
BCM2835_GPIO_PIN(53),
};
內容解密:
此段程式碼定義了BCM2835 SoC中的GPIO Pin。BCM2835_GPIO_PIN巨集用於簡化每個Pin的定義,建立一個struct pinctrl_pin_desc陣列來描述每個Pin的名稱和索引。
Pinctrl子系統的核心資料結構
struct pinctrl_desc用於描述一個Pin控制器的資訊,包括Pin的定義、Pin組態操作介面、Pin多工操作介面和Pin群組操作介面。
struct pinctrl_desc {
const char *name;
const struct pinctrl_pin_desc *pins;
unsigned int npins;
const struct pinctrl_ops *pctlops;
const struct pinmux_ops *pmxops;
const struct pinconf_ops *confops;
// ...
};
內容解密:
此結構體包含了描述一個Pin控制器所需的所有資訊。name欄位是Pin控制器的名稱,pins和npins描述了Pin控制器管理的Pin,pctlops、pmxops和confops則是操作這些Pin的函式指標。
Pin的多工與群組管理
每個實體Pin透過struct pin_desc描述,用於記錄Pin的使用次數和目前的組態資訊。
struct pin_desc {
struct pinctrl_dev *pctldev;
const char *name;
// ...
unsigned mux_usecount;
const char *mux_owner;
const struct pinctrl_setting_mux *mux_setting;
const char *gpio_owner;
};
內容解密:
此結構體記錄了每個Pin的詳細資訊,包括其目前的多工設定和使用情況。mux_usecount和mux_owner用於追蹤Pin的多工使用情況,而gpio_owner則記錄了請求此Pin的GPIO裝置。
平台驅動程式中的Pinctrl子系統詳解
Pinctrl(Pin Control)子系統是Linux核心中用於管理SoC(System on Chip)上引腳多路復用和組態的核心元件。這些引腳可以用於多種功能,例如GPIO、I2C、SPI、UART等。Pinctrl子系統提供了一種統一的方式來管理和組態這些引腳,以滿足不同裝置和驅動程式的需求。
Pinctrl子系統的核心結構
Pinctrl子系統主要涉及三個關鍵結構:struct pinctrl_ops、struct pinconf_ops和struct pinmux_ops。這些結構定義了Pinctrl驅動程式需要實作的操作,以支援引腳的組態和多路復用。
1. struct pinctrl_ops
struct pinctrl_ops提供了用於管理引腳群組(pin group)的操作介面,包括取得群組數量、取得群組名稱、取得群組中的引腳等。
struct pinctrl_ops {
int (*get_groups_count) (struct pinctrl_dev *pctldev);
const char *(*get_group_name) (struct pinctrl_dev *pctldev, unsigned selector);
int (*get_group_pins) (struct pinctrl_dev *pctldev, unsigned selector, const unsigned **pins, unsigned *num_pins);
// ...
};
內容解密:
get_groups_count:傳回該Pinctrl裝置支援的引腳群組數量。get_group_name:根據選擇器傳回對應的引腳群組名稱。get_group_pins:根據選擇器傳回對應的引腳群組中的引腳列表。
2. struct pinconf_ops
struct pinconf_ops定義了用於組態引腳電氣特性的操作,例如設定引腳的上拉/下拉電阻、驅動強度等。
struct pinconf_ops {
int (*pin_config_get) (struct pinctrl_dev *pctldev, unsigned pin, unsigned long *config);
int (*pin_config_set) (struct pinctrl_dev *pctldev, unsigned pin, unsigned long *configs, unsigned num_configs);
// ...
};
內容解密:
pin_config_get:取得指定引腳的組態。pin_config_set:設定指定引腳的組態。
3. struct pinmux_ops
struct pinmux_ops提供了用於管理引腳多路復用的操作介面,包括請求和釋放引腳、設定引腳的多路復用功能等。
struct pinmux_ops {
int (*request) (struct pinctrl_dev *pctldev, unsigned offset);
int (*free) (struct pinctrl_dev *pctldev, unsigned offset);
int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector, unsigned group_selector);
// ...
};
內容解密:
request:請求使用指定的引腳。free:釋放指定的引腳。set_mux:設定指定引腳的多路復用功能。
Pinctrl子系統的關鍵功能
Pinctrl子系統透過上述結構和操作,提供了對SoC引腳的統一管理和組態。它允許驅動程式根據需要請求和組態引腳,而無需關心底層的硬體細節。同時,Pinctrl子系統也支援Device Tree,使得引腳組態可以在不修改驅動程式的情況下進行調整。
平台驅動程式 Chapter 5
Pinctrl 驅動程式實作
Pinctrl 驅動程式需要實作特定於 SoC 的 Pin Controller 的回呼操作。例如,pinctrl-bcm2835.c Pinctrl 驅動程式宣告了以下回呼操作:
static struct pinctrl_desc bcm2835_pinctrl_desc = {
.name = MODULE_NAME,
.pins = bcm2835_gpio_pins,
.npins = ARRAY_SIZE(bcm2835_gpio_pins),
.pctlops = &bcm2835_pctl_ops,
.pmxops = &bcm2835_pmx_ops,
.confops = &bcm2835_pinconf_ops,
.owner = THIS_MODULE,
};
static const struct pinctrl_ops bcm2835_pctl_ops = {
.get_groups_count = bcm2835_pctl_get_groups_count,
.get_group_name = bcm2835_pctl_get_group_name,
.get_group_pins = bcm2835_pctl_get_group_pins,
.pin_dbg_show = bcm2835_pctl_pin_dbg_show,
.dt_node_to_map = bcm2835_pctl_dt_node_to_map,
.dt_free_map = bcm2835_pctl_dt_free_map,
};
static const struct pinconf_ops bcm2835_pinconf_ops = {
.is_generic = true,
.pin_config_get = bcm2835_pinconf_get,
.pin_config_set = bcm2835_pinconf_set,
};
static const struct pinmux_ops bcm2835_pmx_ops = {
.free = bcm2835_pmx_free,
.get_functions_count = bcm2835_pmx_get_functions_count,
.get_function_name = bcm2835_pmx_get_function_name,
.get_function_groups = bcm2835_pmx_get_function_groups,
.set_mux = bcm2835_pmx_set,
.gpio_disable_free = bcm2835_pmx_gpio_disable_free,
.gpio_set_direction = bcm2835_pmx_gpio_set_direction,
};
內容解密:
上述程式碼定義了 BCM2835 SoC 的 Pinctrl 驅動程式的回呼操作,分別對應於 pinctrl_ops、pinconf_ops 和 pinmux_ops 三種結構。這些回呼操作提供了 Pin Controller 的功能,如取得群組數量、設定 Pin 的多工功能等。
Pinctrl 驅動程式註冊
Pinctrl 驅動程式透過呼叫 devm_pinctrl_register() 函式將 struct pinctrl_desc 註冊到 Pinctrl 子系統中。
pc->pctl_dev = devm_pinctrl_register(dev, &bcm2835_pinctrl_desc, pc);
if (IS_ERR(pc->pctl_dev)) {
gpiochip_remove(&pc->gpio_chip);
return PTR_ERR(pc->pctl_dev);
}
內容解密:
devm_pinctrl_register() 函式內部呼叫了 pinctrl_register(),後者主要分為兩個步驟:pinctrl_init_controller() 和 pinctrl_enable()。前者用於初始化 Pin Controller,描述其支援的 Pin、Pin 多工操作介面和群組相關的 Pin 組態介面;後者則將 Pinctrl 裝置新增到 pinctrldev_list 中。
GPIO 控制器驅動程式
Linux 核心中的 GPIO 子系統由 GPIOlib 實作,提供了一個內部 API 用於管理和組態 GPIOs。GPIO 控制器驅動程式需要包含 linux/gpio/driver.h 標頭檔,並實作 struct gpio_chip 結構。
static const struct gpio_chip bcm2835_gpio_chip = {
.label = MODULE_NAME,
.owner = THIS_MODULE,
.request = gpiochip_generic_request,
};
內容解密:
上述程式碼定義了 BCM2837 SoC 的 GPIO 操作,具體實作了 GPIO 線路的方向建立、值存取等方法。這些操作是特定於每個 GPIO 控制器的。