在嵌入式系統中,平台驅動程式橋接硬體與作業系統,扮演著重要的角色。本文以 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)緊密相關,因為它們需要直接與晶片上的硬體進行互動。

關鍵元件

  1. 裝置樹(Device Tree):裝置樹是一種描述硬體裝置及其屬性的資料結構。它使得驅動程式能夠與硬體進行解耦,從而提高程式碼的可移植性。

  2. 平台驅動程式註冊:驅動程式需要向核心註冊,以便在裝置樹中的相應節點被匹配時獲得通知。

  3. 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");

#### 內容解密:

  1. my_dev_openmy_dev_closemy_dev_ioctl 函式:這些函式定義了使用者空間應用程式如何與我們的裝置互動。當應用程式開啟、關閉裝置或傳送 ioctl 命令時,這些函式將被呼叫。
  2. my_probemy_remove 函式:這兩個函式是平台驅動程式的核心。my_probe 在裝置被檢測到時初始化裝置,而 my_remove 在裝置被移除時清理資源。
  3. of_device_id 結構:這個結構定義了驅動程式支援的裝置。我們使用 compatible 欄位來匹配裝置樹中的節點。
  4. platform_driver 結構:這個結構定義了平台驅動程式的屬性,包括 proberemove 函式,以及支援的裝置列表。

編譯與測試

編譯和測試驅動程式的步驟包括:

  1. 編譯核心模組。
  2. 將模組複製到 Raspberry Pi。
  3. 載入模組並檢查日誌輸出。
  4. 使用 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控制器的名稱,pinsnpins描述了Pin控制器管理的Pin,pctlopspmxopsconfops則是操作這些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_usecountmux_owner用於追蹤Pin的多工使用情況,而gpio_owner則記錄了請求此Pin的GPIO裝置。

平台驅動程式中的Pinctrl子系統詳解

Pinctrl(Pin Control)子系統是Linux核心中用於管理SoC(System on Chip)上引腳多路復用和組態的核心元件。這些引腳可以用於多種功能,例如GPIO、I2C、SPI、UART等。Pinctrl子系統提供了一種統一的方式來管理和組態這些引腳,以滿足不同裝置和驅動程式的需求。

Pinctrl子系統的核心結構

Pinctrl子系統主要涉及三個關鍵結構:struct pinctrl_opsstruct pinconf_opsstruct 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_opspinconf_opspinmux_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 控制器的。