1. 程式人生 > 其它 >Linux 核心:裝置驅動模型 平臺裝置驅動

Linux 核心:裝置驅動模型 平臺裝置驅動

介紹

Linux系統的驅動框架主要就是三個主要部分組成,驅動、匯流排、裝置。

隨著電子行業的發展,控制器越來越強大,SOC(片上系統)出現了:在片內的CPU外圍整合很多外設電路,這些外設都掛接在SOC內部的總線上。

不同於IIC、SPI和USB等這一類實際存在外部PCB走線匯流排,片內外設從Chip之外是看不到的。

為了統一驅動架構抽象,所以Linux從2.6版本開始引入了platform bus這個虛擬的匯流排模型。

組成

platform平臺裝置驅動是基於裝置匯流排驅動模型的,機制本身並不複雜,由兩部分組成:platform_deviceplatfrom_driver

  • platform_device
    :基於device的封裝
  • platform_device_driver:基於device_driver的封裝

整體的架構是這樣子的。

前面已經分析過裝置匯流排驅動模型,關於device 與 device_driver 的註冊過程以及它們在sysfs檔案系統中的層次關係就不在分析,本文重點分析platform平臺裝置驅動與裝置匯流排驅動模型相比較新增添的那些東西。

platform裝置

platform裝置在device的基礎上,增加了一些平臺裝置需要的資料。

platform_device原型

c
// include/linux/platform_device.h
struct platform_device {
    /* 分配id的方式,決定了name的值 */
    const char  *name;    
    int     id;
    bool        id_auto;
    
    //真正的裝置,通過 container_of ,就能找到整個platform_device ,訪問其它成員,如後面要提到的 resource  
    struct device   dev;
    /*
    num_resources、resource,該裝置的資源描述,由struct resource(include/linux/ioport.h)結構抽象。 
    在Linux中, 系統資源包括I/O、Memory、Register、IRQ、DMA、Bus等多種型別。這些資源大多具有獨佔性,不允許多個裝置同時使用,因此Linux核心提供了一些API,用於分配、管理這些資源。 
     當某個裝置需要使用某些資源時,只需利用struct resource組織這些資源(如名稱、型別、起始、結束地址等),並儲存在該裝置的resource指標中即可。然後在裝置probe時,裝置需求會呼叫資源管理介面,分配、使用這些資源。而核心的資源管理邏輯,可以判斷這些資源是否已被使用、是否可被使用等等。

    */
    u32     num_resources;
    struct resource *resource;

    /*記錄和驅動的匹配表id_table中匹配的哪一個表項指標*/
    const struct platform_device_id *id_entry;

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    // 這個引數一般都指向這個結構體實體本身地址
    struct pdev_archdata    archdata;
};

一般註冊平臺裝置需要初始化的內容主要有name、 resource;

有時還需要指定dev.platform_data,這一部分資料常常被驅動使用,這也是Linux 驅動和裝置分離的一部分體現。

c
// include/linux/device.h
/**
 * struct device - The basic device structure
 ...
 * @platform_data: Platform data specific to the device.
 *      Example: For devices on custom boards, as typical of embedded
 *      and SOC based hardware, Linux often uses platform_data to point
 *      to board-specific structures describing devices and how they
 *      are wired.  That can include what ports are available, chip
 *      variants, which GPIO pins act in what additional roles, and so
 *      on.  This shrinks the "Board Support Packages" (BSPs) and
 *      minimizes board-specific #ifdefs in drivers.
 ...
 */
struct device {
    // ...

    void        *platform_data; /* Platform specific data, device
                       core doesn't touch it */
    // ...
};

註冊新增device

這裡只是簡單羅列函式呼叫過程,這一部分實際上是Device註冊的過程的一個封裝,具體內部操作可以參考Linux設備註冊。

流程:

platform_device_register
    device_initialize(&pdev->dev);
    arch_setup_pdev_archdata(空函式)
    platform_device_add
        pdev->dev.parent = &platform_bus;指定父裝置為platform裝置匯流排
        pdev->dev.bus = &platform_bus_type;
        設定裝置名稱三種情況(-1 -2(申請ID) other)
    裝置資源管理 
    呼叫device_add(pdev->dev)

platform_device_register

c
// drivers/base/platform.c
/**
 * platform_device_register - add a platform-level device
 * @pdev: platform device we're adding
 */
int platform_device_register(struct platform_device *pdev)
{
    // device 初始化(之前說過了,略)
    device_initialize(&pdev->dev);
    // 空函式
    arch_setup_pdev_archdata(pdev);
    return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);


void __weak arch_setup_pdev_archdata(struct platform_device *pdev)
{
}

platform_device_add

c
/**
 * platform_device_add - add a platform device to device hierarchy
 * @pdev: platform device we're adding
 *
 * This is part 2 of platform_device_register(), though may be called
 * separately _iff_ pdev was allocated by platform_device_alloc().
 */
int platform_device_add(struct platform_device *pdev)
{
    int i, ret;
    
    // ...
    
    // 指定父裝置 即 依託的匯流排 為 platform_bus
    if (!pdev->dev.parent)
        pdev->dev.parent = &platform_bus;
    
    // 指定bus 涉及到後面的驅動匹配
    // (因為裝置新增過程會拿這個裝置所屬的匯流排總線上由註冊的驅動list)
    pdev->dev.bus = &platform_bus_type;

    // 根據ID的不同值以不同的策略初始化裝置name欄位
    switch (pdev->id) {
    default: // 預設
        dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);
        break;
    case PLATFORM_DEVID_NONE: // platform_device.name 的 值
        dev_set_name(&pdev->dev, "%s", pdev->name);
        break;
    case PLATFORM_DEVID_AUTO: // 自動分配platform裝置ID
        /*
         * Automatically allocated device ID. We mark it as such so
         * that we remember it must be freed, and we append a suffix
         * to avoid namespace collision with explicit IDs.
         */
        ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);

        pdev->id = ret;
        pdev->id_auto = true;
        dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
        break;
    }

    /* 資源的儲存新增 */
    for (i = 0; i < pdev->num_resources; i++) {
        struct resource *p, *r = &pdev->resource[i];

        if (r->name == NULL)
            r->name = dev_name(&pdev->dev);

        p = r->parent;
        if (!p) {
            if (resource_type(r) == IORESOURCE_MEM)
                p = &iomem_resource;
            else if (resource_type(r) == IORESOURCE_IO)
                p = &ioport_resource;
        }

        if (p && insert_resource(p, r)) {
            dev_err(&pdev->dev, "failed to claim resource %d\n", i);
            ret = -EBUSY;
            goto failed;
        }
    }

    pr_debug("Registering platform device '%s'. Parent at %s\n",
         dev_name(&pdev->dev), dev_name(pdev->dev.parent));

    // 最關鍵的就是device_add,把裝置新增到總線上。
    ret = device_add(&pdev->dev);
    if (ret == 0)
        return ret;

 failed:
    if (pdev->id_auto) {
        ida_simple_remove(&platform_devid_ida, pdev->id);
        pdev->id = PLATFORM_DEVID_AUTO;
    }

    while (--i >= 0) {
        struct resource *r = &pdev->resource[i];
        unsigned long type = resource_type(r);

        if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
            release_resource(r);
    }

 err_out:
    return ret;
}
EXPORT_SYMBOL_GPL(platform_device_add);

device解除安裝過程

這一部分內容也是上面的操作的一個逆向操作,其實核心的內容還是裝置刪除的操作,同樣可以參考上面給出的聯接檢視裝置的登出過程,就能明白platform框架只是在原有的驅動和裝置驅動模型上的更高一層的封裝。所以這裡還是簡單的羅列一下呼叫過程。

platform_device_unregister
    platform_device_del
        釋放platform_device id
        device_del(pdev->dev)
    platform_device_put
      put_device

platform驅動

platform_driver原型

同理樣platform_driver 也是一個包含了device_driver 的結構。

c
//include/linux/platform_device.h
struct platform_driver {
    int (*probe)(struct platform_device *);
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
    const struct platform_device_id *id_table;
    bool prevent_deferred_probe;
};

從結構體可以看出,平臺裝置驅動提供了一些操作介面和一個platform_device_id型別的相容性匹配表(後面分析)。

其次是結構體中的操作介面函式,其在內部的device_driver結構體內也是有一套類似的操作介面;其他的電源管理現在已經很少用平臺裝置驅動中的介面了,而是轉而使用內涵的device_driver驅動中的dev_pm_ops結構體中的介面來實現。

註冊新增driver

__platform_driver_register(drv, THIS_MODULE)
  drv->driver.bus = platform_bus_type;
  設定probe、remove、shutdown。
  driver_register
c
/**
 * __platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 * @owner: owning module/driver
 */
int __platform_driver_register(struct platform_driver *drv,
                struct module *owner)
{
    drv->driver.owner = owner;
    // 指定為 platform_bus_type,也是為了到時候的probe
    drv->driver.bus = &platform_bus_type;
    /*
      如果platform驅動中的xx_probe或xx_remove等為空則指定drv->driver.
      同名介面指標為platform驅動預設行為(僅支援acpi方式匹配)
    */
    if (drv->probe)
        drv->driver.probe = platform_drv_probe;
    if (drv->remove)
        drv->driver.remove = platform_drv_remove;
    if (drv->shutdown)
        drv->driver.shutdown = platform_drv_shutdown;

    return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__platform_driver_register);


// include/linux/platform_device.h
/*
 * use a macro to avoid include chaining to get THIS_MODULE
 */
#define platform_driver_register(drv) \
    __platform_driver_register(drv, THIS_MODULE)

通過上面的程式碼我們很清楚的看到platform_driver實際上也是對通用驅動的註冊流程的一個高層級的封裝,具體的驅動註冊過程參考之前的文章就可以了

driver註冊移除

移除過程同樣很簡單就是裝置驅動的刪除操作同上參考裝置驅動的登出過程,這裡也是僅僅簡單的羅列API的呼叫層級和過程。

platform_device_unregister
  platform_driver_unregister
    driver_unregister

platform匯流排

剛剛可能有人會好奇,為什麼沒有platform_bus,實際上platform_bus_type是一個bus_type的例項;而不是基於bus_type的封裝。

因為在一般情況下,核心中已經初始化並掛載了一條platform匯流排在sysfs檔案系統中。

platform_bus_type原型

c
// drivers/base/platform.c
struct bus_type platform_bus_type = {
    .name       = "platform",
    .dev_groups = platform_dev_groups,// sysfs屬性
    .match      = platform_match,
    .uevent     = platform_uevent,
    .pm     = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

結合前面的分析,Linux下的裝置都應該(但不是必須)有所屬的bus_type(dev.bus)這個bus_type就抽象了他們共通的一些“屬性”和“方法”。

platform驅動和裝置的匹配

無論上面的註冊裝置還是註冊驅動,最後都是要呼叫匯流排型別的mach函式進行驅動和裝置的匹配,這也是platform 驅動框架中比較重要核心的部分。

再看driver_match_device

還記得嗎?無論是driver_register還是device_register,都會呼叫到driver_match_device。例如driver_register的呼叫過程:

c
driver_register(drv) [core.c]
  bus_add_driver(drv) [bus.c]
    if (drv->bus->p->drivers_autoprobe)
      driver_attach(dev)[dd.c]
        bus_for_each_dev(dev->bus, NULL, drv,__driver_attach)
        __driver_attach(dev, drv) [dd.c]
          driver_match_device(drv, dev) [base.h]
            // 執行 bus->match
            drv-bus->match ? drv->bus->match(dev, drv) : 1
            if false, return;
          driver_probe_device(drv, dev) [dd.c]
            really_probe(dev, drv) [dd.c]
              dev-driver = drv;
              if (dev-bus->probe)
                dev->bus->probe(dev);
              else if (drv->probe)
                drv-aprobe(dev);
              probe_failed:
                dev->-driver = NULL;

結合剛剛介紹的platform_bus_type,我們知道,待會就會呼叫platform_match進行裝置與驅動的匹配。

c
// drivers/base/platform.c
struct bus_type platform_bus_type = {
    .name       = "platform",
    .dev_groups = platform_dev_groups,// sysfs屬性
    .match      = platform_match,
    .uevent     = platform_uevent,
    .pm     = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

platform_match

c
/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 *
 * Platform device IDs are assumed to be encoded like this:
 * "<name><instance>", where <name> is a short description of the type of
 * device, like "pci" or "floppy", and <instance> is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "<name>".  So, extract the <name> from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
 */
static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    /* Attempt an OF style match first */
    /* 採用裝置樹的相容方式匹配驅動和裝置 (略)*/
    if (of_driver_match_device(dev, drv))
        return 1;

    /* Then try ACPI style match */
     /* 採用ACPI的方式匹配驅動和裝置 略*/
    if (acpi_driver_match_device(dev, drv))
        return 1;

    /* Then try to match against the id table */
    /* 通過驅動和裝置的match表來匹配驅動和裝置 */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;

    /* fall-back to driver name match */
    /* 最後就是按驅動和裝置名稱是否相同來判斷當前驅動是否支援這個裝置 */
    return (strcmp(pdev->name, drv->name) == 0);
}

從這個函式我們可以知道platform的driver和device的匹配就是通過四種規則來進行匹配的,按優先順序劃分:

  • 通過裝置樹匹配
  • 通過ACPI匹配
  • 通過match表匹配
  • 通過兩者的名稱來匹配

前兩種方式暫時不深究學到再來看,通過名字匹配也比較簡單。我們看看通過match表匹配是如何匹配的。

通過match表匹配

其中相容ID的匹配表格式是:

c
// include/linux/mod_devicetable.h
#define PLATFORM_NAME_SIZE  20
struct platform_device_id {
    char name[PLATFORM_NAME_SIZE];
    kernel_ulong_t driver_data;
};

具體的匹配規則也很簡單,就是使用ID表內的名稱來和裝置名比較。

c
static const struct platform_device_id *platform_match_id(
            const struct platform_device_id *id,
            struct platform_device *pdev)
{
    while (id->name[0]) {
        if (strcmp(pdev->name, id->name) == 0) {
            pdev->id_entry = id;
            return id;
        }
        id++;
    }
    return NULL;
}

需要注意的是,這裡還將匹配的id 表的控制代碼儲存在platform device的id_entry項上,id_table裡常常帶一個long型的driver_data資料儲存驅動資料。

顯然,platform_match_id 的作用就是遍歷整個 Id_table 陣列,尋找是否有與 platform_device->name 同名的,如果有,則返回這個 Platform_device_id ,使用Id_table 打破了原本裝置匯流排驅動模型,一個 device 只能用與一個 device_driver 配對的侷限性。

現在一個platform_device_driver可以與多個platform_device配對。

具體例項分析

下面通過自己實現一個platform device 來匹配核心的一個三星的led驅動,核心程式碼是3-16-57版本驅動在drivers\leds\leds-s3c24xx.c。

c
static const struct platform_device_id s3c24xx_led_id[] = {
    { "s3c24xx_led", 0 },
    {},
};

static struct platform_driver s3c24xx_led_driver = {
    .probe        = s3c24xx_led_probe,
    .remove        = s3c24xx_led_remove,
    .driver        = {
        .name        = "s3c24xx_led",
        .owner        = THIS_MODULE,
    },
    .id_table   = s3c24xx_led_id,
};

主要分析其s3c24xx_led_probe函式的執行過程就能明白對應的裝置應該如何新增。

通過驅動的宣告我得出結論,這個驅動除了裝置樹和ACPI的方式匹配裝置外,使用到了platform_device_id來匹配裝置,所以先定義裝置如下然後慢慢填充。

c
static struct platform_device tiny210_device_led []= {
    .name    = "s3c24xx_led",
    .id        = 0,
};

然後在看s3c24xx_led_probe函式都是怎樣處理的

c
static int s3c24xx_led_probe(struct platform_device *dev)
{
    struct s3c24xx_led_platdata *pdata = dev_get_platdata(&dev->dev);
    /* 首先獲取 platform_data 這個我還沒定義所以後面需要定義 */
    struct s3c24xx_gpio_led *led;
    int ret;
    /* 申請驅動私有資料結構體 */
    led = devm_kzalloc(&dev->dev, sizeof(struct s3c24xx_gpio_led),
                       GFP_KERNEL);
    if (!led)
        return -ENOMEM;
    /* 將私有資料結構體繫結到device的driver_data成員上方便使用 */
    platform_set_drvdata(dev, led);
    /* 這裡涉及LED class 子系統的內容 可以暫時當作黑盒 */
    led->cdev.brightness_set = s3c24xx_led_set;
    led->cdev.default_trigger = pdata->def_trigger;
    led->cdev.name = pdata->name;
    led->cdev.flags |= LED_CORE_SUSPENDRESUME;
    /* 繫結platform_data 到私有資料結構 */
    led->pdata = pdata;

    ret = devm_gpio_request(&dev->dev, pdata->gpio, "S3C24XX_LED");
    if (ret < 0)
        return ret;

    /* no point in having a pull-up if we are always driving */
    /* GPIO 子系統內容 配置對應的GPIO */
    s3c_gpio_setpull(pdata->gpio, S3C_GPIO_PULL_NONE);
    /* 如果裝置定義時指定了這個標誌則會執行下面的設定將GPIO配置為輸入方向 */
    if (pdata->flags & S3C24XX_LEDF_TRISTATE)
        /* GPIO 子系統內容 配置對應的GPIO方向為輸入 一般底層由晶片廠商實現 */
        gpio_direction_input(pdata->gpio);
    else
        /* 第二個引數是保證LED在預設狀態下是不點亮的 */
        gpio_direction_output(pdata->gpio,
                              pdata->flags & S3C24XX_LEDF_ACTLOW ? 1 : 0);

    /* register our new led device */
    /* 這裡涉及LED class 子系統的內容 可以暫時當作黑盒 */
    ret = led_classdev_register(&dev->dev, &led->cdev);
    if (ret < 0)
        dev_err(&dev->dev, "led_classdev_register failed\n");

    return ret;
}

到此LED驅動的匹配操作就完了,除了中間涉及Linux 的led class 和 gpio 子系統的內容外還是很簡單的所以接下來完善我的LED裝置
增加驅動所需的資料

c
struct s3c24xx_led_platdata led_data
{
    .gpio  = S5PV210_GPJ2(0), //(gpio 子系統)
    .flags = S3C24XX_LEDF_ACTLOW, //(驅動的私有標誌,指明LED開啟時的IO電平)
    .name  = "led",
    .def_trigger = "",
};
static struct platform_device tiny210_device_led []= {
    .name        = "s3c24xx_led",
    .id        = 0,
    .dev        ={
        .platform_data = &led_data,
    },
};

flags 的內容是後來補上的他的意思就是led在低電平時點亮,不要這個標誌LED預設狀態是開啟的這和具體的硬體有關。

最後將裝置以模組的形式加入。
然後在/sys/class/leds/ 下將看到一個led0檔案他是一個符合連結指向/devices/platform/latform/s3c24xx_led.0/leds/led0
進入 會看到
brightness max_brightness subsystem uevent
device power trigger
這些就是ledclass的內容的,通過向brightness寫資料就可以控制LED的開啟和關閉了。也可以直接使用指令碼

echo 0 > brightness led燈就亮了
echo 1 >brightness led燈就滅了。

綜上Linux下的platform匯流排出現的意義就是統一linux下的驅動模型,即裝置、驅動、匯流排其中匯流排負責裝置和驅動的匹配。一般Linux下的複雜驅動都是基於platform匯流排的
通過驅動的probe函式進行呼叫其他子系統的介面實習更加複雜的驅動模型。而platform_driver和platform_device都是在Linux device和device_driver之上封裝的所以需要在明白
Linux device和device_driver 的相關機制之上來理解就更加容易了。

附裝置新增原始碼,不過原始碼後來我又添加了三個LED。

c
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/delay.h>

#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <asm/setup.h>
#include <asm/mach-types.h>

#include <mach/map.h>
#include <plat/gpio-cfg.h>
#include <plat/devs.h>
#include <plat/cpu.h>
#include <linux/platform_data/leds-s3c24xx.h>



static struct s3c24xx_led_platdata led_data[]  = {
    [0]={
        .gpio  = S5PV210_GPJ2(0),
        .flags = S3C24XX_LEDF_ACTLOW,
        .name  = "led0",
        .def_trigger = "",
    },
    [1]={
        .gpio  = S5PV210_GPJ2(1),
        .name  = "led1",
        .flags = S3C24XX_LEDF_ACTLOW,
        .def_trigger = "",
    },
    [2]={
        .gpio  = S5PV210_GPJ2(2),
        .name  = "led2",
        .flags = S3C24XX_LEDF_ACTLOW,
        .def_trigger = "",
    },
    [3]={
        .gpio  = S5PV210_GPJ2(3),
        .name  = "led3",
        .flags = S3C24XX_LEDF_ACTLOW,
        .def_trigger = "",
    },
    
};

static struct platform_device tiny210_device_led []= {
    [0]={
        .name        = "s3c24xx_led",
        .id        = 0,
        .dev        ={
            .platform_data = &led_data[0],
            .devt = MAJOR(22),
        },
    },
    [1]={
        .name        = "s3c24xx_led",
        .id        = 1,
        .dev        ={
            .platform_data = &led_data[1],
            .devt = MAJOR(22),
        },
    },
    [2]={
        .name        = "s3c24xx_led",
        .id        = 2,
        .dev        ={
            .platform_data = &led_data[2],
            .devt = MAJOR(22),
        },
    },
    [3]={
        .name        = "s3c24xx_led",
        .id        = 3,
        .dev        ={
            .platform_data = &led_data[3],
            .devt = MAJOR(22),
        },
    }
};

static int __init platform_led_init(void)
{
    int i;

    for(i=0;i<ARRAY_SIZE(tiny210_device_led);i++){
        if(platform_device_register(&tiny210_device_led[i])<0)
        {
            printk(KERN_ERR "tiny210_device_led %d Fail\n",i);
            return -1;
        }
    }
    printk(KERN_INFO "tiny210_device_led Succse\n");
    return 0;

}

static void __exit platform_led_exit(void)
{
    int i;
    for(i=0;i<ARRAY_SIZE(tiny210_device_led);i++){
        platform_device_unregister(&tiny210_device_led[i]);
    }
}

module_init(platform_led_init);
module_exit(platform_led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("smile@shanghai");

例子2:

裝置

c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/sched.h> 
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <linux/input.h>
#include <linux/platform_device.h>
// 裝置資源
static struct resource led_resource[] = {	//jz2440的引數,驅動未測試 
    [0] = {
        .start = 0x56000010,
        .end   = 0x56000010 + 8 - 1,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = 5,
        .end   = 5,
        .flags = IORESOURCE_IRQ,
    },
};

static void led_release(struct device *dev){

}

// 建立一個裝置
static struct platform_device led_dev = {
    .name = "myled",	//裝置名字 與 驅動相匹配
    .id	  = -1,
    .num_resources = ARRAY_SIZE(led_resource),
    .resource = led_resource,

    .dev = {
        .release = led_release,
        //.devt = MKDEV(252, 1),
    },
};

static int led_dev_init(void){

    //向bus註冊led_dev match drv連結串列進行配對
    platform_device_register(&led_dev);
    return 0;
}

static void led_dev_exit(void){
    platform_device_unregister(&led_dev);
}

module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

驅動

c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/sched.h> 
#include <linux/irq.h>
#include <asm/uaccess.h>
 
#include <linux/platform_device.h>
#include <linux/io.h>
 
static int major;
 
static struct class *cls;
static struct device *dev;
 
static volatile unsigned long *gpio_con;
static volatile unsigned long *gpio_dat;
static int pin;
 
static int led_open(struct inode *inode, struct file *file){
 
	*gpio_con &= ~(0x03 << (pin*2));
	*gpio_con |=  (0x01 << (pin*2));
	return 0;
}
 
static ssize_t led_write(struct file *file, const char __user *buf,
	size_t count, loff_t *ppos){
 
	int val;
	copy_from_user(&val, buf, count);
 
	if(val == 1){
		
		*gpio_dat &= ~(1<<pin);
	}else{
	
		*gpio_dat &=  (1<<pin);
	}
 
	return 0;
}
 
static struct file_operations led_fops = {
 
	.owner = THIS_MODULE,
	.open  = led_open,
	.write = led_write,
};
 
static int led_probe(struct platform_device *pdev){
 
	struct resource *res;
	// 最後一個引數 0 表示第1個該型別的資源
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	gpio_con = ioremap(res->start, res->end - res->start + 1);
	gpio_dat = gpio_con + 1;
 
	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	pin = res->start;
 
	printk("led_probe, found led\n");
 
	// 註冊裝置驅動 建立裝置節點
	major = register_chrdev(0, "myled", &led_fops);
	// 建立類
	cls = class_create(THIS_MODULE, "myled");
	// 建立裝置節點
	dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "led");
 
	return 0;
}
 
static int led_remove(struct platform_device *pdev){
 
	printk("led_remove, remove led\n");	
	// 刪除裝置節點
	device_unregister(dev);
	// 銷燬類
	class_destroy(cls);
	// 取消註冊裝置驅動
	unregister_chrdev(major, "myled");
	// 取消記憶體對映
	iounmap(gpio_con);
 
	return 0;
}
 
struct platform_driver led_drv = {
 
	.probe 	= led_probe,	//匹配到dev之後呼叫probe
	.remove = led_remove,
	.driver = {
		.name = "myled",
	},
};
 
static int led_drv_init(void){
 
	platform_driver_register(&led_drv);
	return 0;
}
 
static void led_drv_exit(void){
	
	platform_driver_unregister(&led_drv);
}
 
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");

附錄:API介面

Platform Device提供的API

/* include/linux/platform_device.h */
extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *);
 
extern void arch_setup_pdev_archdata(struct platform_device *);
extern struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
extern int platform_get_irq(struct platform_device *, unsigned int);
extern struct resource *platform_get_resource_byname(struct platform_device *, unsigned int, const char *);
extern int platform_get_irq_byname(struct platform_device *, const char *);
extern int platform_add_devices(struct platform_device **, int);
 
extern struct platform_device *platform_device_register_full(const struct platform_device_info *pdevinfo);
 
static inline struct platform_device *platform_device_register_resndata(
                struct device *parent, const char *name, int id,
                const struct resource *res, unsigned int num,
                const void *data, size_t size)
 
static inline struct platform_device *platform_device_register_simple(
                const char *name, int id,
                const struct resource *res, unsigned int num)
 
static inline struct platform_device *platform_device_register_data(
                struct device *parent, const char *name, int id,
                const void *data, size_t size)
 
extern struct platform_device *platform_device_alloc(const char *name, int id);
extern int platform_device_add_resources(struct platform_device *pdev,
                                         const struct resource *res,
                                         unsigned int num);
extern int platform_device_add_data(struct platform_device *pdev,
                                    const void *data, size_t size);
extern int platform_device_add(struct platform_device *pdev);
extern void platform_device_del(struct platform_device *pdev);
extern void platform_device_put(struct platform_device *pdev);

platform_device_register、platform_device_unregister,Platform裝置的註冊/登出介面,和底層的device_register等介面類似。
arch_setup_pdev_archdata,設定platform_device變數中的archdata指標。
platform_get_resource、platform_get_irq、platform_get_resource_byname、platform_get_irq_byname,通過這些介面,可以獲取platform_device變數中的resource資訊,以及直接獲取IRQ的number等等。
platform_device_register_full、platform_device_register_resndata、platform_device_register_simple、platform_device_register_data,其它形式的設備註冊。呼叫者只需要提供一些必要的資訊,如name、ID、resource等,Platform模組就會自動分配一個struct platform_device變數,填充內容後,註冊到核心中。
platform_device_alloc,以name和id為引數,動態分配一個struct platform_device變數。
platform_device_add_resources,向platform device中增加資源描述。
platform_device_add_data,向platform device中新增自定義的資料(儲存在pdev->dev.platform_data指標中)。
platform_device_add、platform_device_del、platform_device_put,其它操作介面。

Platform Driver提供的API

c
platform_driver_registe、platform_driver_unregister,platform driver的註冊、登出介面。
platform_driver_probe,主動執行probe動作。
platform_set_drvdata、platform_get_drvdata,設定或者獲取driver儲存在device變數中的私有資料。