Linux platform平臺總線、平臺設備、平臺驅動

平臺總線(platform_bus)的需求來源?
隨着soc的升級,S3C2440->S3C6410->S5PV210->4412,之前的程序就得從新寫一遍,作着大量的重複工做,
人們爲了提升效率,發現控制器的操做邏輯(方法)是同樣的,只有寄存器地址不同,若是將與硬件有關的
代碼(platform_device)和驅動代碼(platform_driver)分開,升級soc後,由於驅動方式同樣,
只須要修改與硬件有關的代碼就能夠,實現一個驅動控制多個設備。

平臺(platform)總線是一種虛擬的總線,在 /sys/bus/platform 目錄能夠看到。
平臺總線三要素:平臺總線、平臺設備、平臺驅動
平臺總線原則:先分離,後合併

分離:
將設備信息封裝成 platform_device,將驅動信息封裝成 platform_driver,併爲各自起名稱,
而後將 platform_device 中的 struct device 和 platform_driver 中的 struct device_driver 分別註冊到設備鏈表和驅動鏈表中。node

int platform_device_register(struct platform_device *pdev)linux

  return platform_device_add(pdev);函數

    ret = device_add(&pdev->dev);spa

int platform_driver_register(struct platform_driver *drv)code

  return driver_register(&drv->driver);orm

    ret = bus_add_driver(drv);blog


合併:
在系統每註冊一個設備(驅動)時,平臺總線會找與之匹配的驅動(設備),匹配原則是名稱相同。排序

裝載(insmod)時設備和驅動沒有順序,卸載(rmmod)時必須先卸載設備文件,由於卸載設備會調用驅動中的 remove 函數接口

// 下面的「|」表示包含於上一個之中
// 描述設備的信息
struct platform_device {
    const char    * name;                    // 用於和platform_driver進行匹配的名字--自定義
    int        id;                      // 通常直接填-1,區分不一樣的控制組
    struct device    dev;                    // 父類
        |
        void    (*release)(struct device *dev);        // 設備卸載時調用的函數
        void    *platform_data;                // 匹配後傳遞的自定義數據
        ... ...
    u32        num_resources;                // 資源的個數
    struct resource    * resource;                 // 描述資源信息
        |    
        resource_size_t start;                // 起始位置
        resource_size_t end;                // 結束位置
        const char *name;                 // 自定義
        unsigned long flags;                 // 區分不一樣的資源,通常是 內存或者中斷資源
        ... ...
 };
// 描述設備的操做方法
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 char        *name;                // 該名字能夠用於匹配,但比id_table中的name優先級低
        // 此名稱在 /sys/bus/platform/drivers/xxx
        ... ...
    const struct platform_device_id *id_table;
        |
        char name[PLATFORM_NAME_SIZE];                // 用於和platform_device的名字進行匹配,優先級高
};
// 平臺總線
struct bus_type platform_bus_type = {
    .name        = "platform",
    .dev_attrs    = platform_dev_attrs,
    .match        = platform_match,                // 用於匹配,此函數能夠看出匹配名稱的優先級
    .uevent        = platform_uevent,
    .pm        = &platform_dev_pm_ops,
};

// 註冊 platform_devive內存

int platform_device_register(struct platform_device *pdev);

// 註銷 platform_devive

void platform_device_unregister(struct platform_device *pdev);


// 註冊 platform_driver

int platform_driver_register(struct platform_driver *drv);

// 註銷 platform_driver

void platform_driver_unregister(struct platform_driver *drv);


// 批量註冊pdev

int platform_add_devices(struct platform_device **devs, int num);


獲取資源的接口:

// 經過類型和編號獲取資源

// 參數1:pdev

// 參數2:獲取的資源類型

// 參數3:獲取的資源編號

struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);

// 經過類型和名稱獲取資源

// 參數3:獲取資源的名稱

struct resource * platform_get_resource_byname(struct platform_device * dev,unsigned int type,const char * name);

// 經過編號獲取中斷資源

int platform_get_irq(struct platform_device * dev,unsigned int num);

// 經過名稱獲取中斷資源

int platform_get_irq_byname(struct platform_device * dev,const char * name);

// 重點:資源編號必定是按照同種類型來排序的
struct resource led_res[] = {
    [0] = { // 獲取內存資源時,此內存資源的編號爲0
        ... ...
        .flags = IORESOURCE_MEM,
    },
    [1] = { // 獲取中斷資源時,此中斷資源的編號爲0
        ... ...
        .flags = IORESOURCE_IRQ,
    },
    [2] = { // 獲取內存資源時,此內存資源的編號爲1
        ... ...
        .flags = IORESOURCE_MEM,
    },
};


 

plat_led_dev.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>

#include "plat_led.h"

#define GPL2_0        0x11000100
#define GP_SIZE        8

// 平臺自定義數據,與硬件相關
struct regled led_reg = {
    .ctl_clr = 0x0f,
    .ctl_set = 0x01,
    .dat_clr = 0x01,
    .dat_set = 0x01,
}; 

static struct resource led_res[] = {
    [0] = {
        .start = GPL2_0,
        .end = GPL2_0 + GP_SIZE - 1,
        .name = "led0",
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = 888,
        .end = 888,
        .name = "virt_irq",
        .flags = IORESOURCE_IRQ,
    },
};

void plat_led_release(struct device *dev)
{
    // 爲了卸載模塊時不報錯誤
}

struct platform_device led_pdev = {
    .name = "plat_led",
    .id = -1,
    .dev = {
        .platform_data = &led_reg,
        .release = plat_led_release,
    },
    .num_resources = ARRAY_SIZE(led_res),
    .resource = led_res,
};

static int __init plat_led_dev_init(void)
{
    platform_device_register(&led_pdev);

    return 0;
}

static void __exit plat_led_dev_exit(void)
{
    platform_device_unregister(&led_pdev);
}

module_init(plat_led_dev_init);
module_exit(plat_led_dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Aaron Lee");

plat_led_drv.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/platform_device.h>

#include <asm/io.h>
#include <asm/uaccess.h>

#include "plat_led.h"

struct samsung *platled;

static int platled_open(struct inode *inode, struct file *fops)
{
    writeb(readb(platled->reg_base+4) & (~platled->reg->dat_clr), platled->reg_base+4);
    
    return 0;
}

static int platled_close(struct inode *inode, struct file *fops)
{
    return 0;
}

static ssize_t platled_write(struct file *fops, const char __user *buf, size_t size, loff_t *fpos)
{
    int value;
//暫時忽略返回值
    copy_from_user(&value, buf, size);

    if (value)
        writeb((readb(platled->reg_base+4) & (~platled->reg->dat_clr)) | platled->reg->dat_set, platled->reg_base+4);
    else
        writeb(readb(platled->reg_base+4) & (~platled->reg->dat_clr), platled->reg_base+4);
    
    return 0;
}

const struct file_operations platled_fops = {
    .open = platled_open,
    .release = platled_close,
    .write = platled_write,
};

static int led_register(void)
{
    int ret;

    platled = kmalloc(sizeof(struct samsung), GFP_KERNEL);
    if (platled == NULL)
    {
        printk("kmalloc fail!\n");
        return -ENOMEM;
    }

    platled->major = register_chrdev(0, "plat_led", &platled_fops);
    if (platled->major < 0)
    {
        printk("register_chrdev fail!\n");
        ret = -EFAULT;
        goto chrdev_err;
    }

    platled->cls = class_create(THIS_MODULE, "plat_led");
    if (platled->cls < 0)
    {
        printk("class_create fail!\n");
        ret = -EFAULT;
        goto class_err;
    }

    platled->dev = device_create(platled->cls, NULL, MKDEV(platled->major, 0), NULL, "plat_led");
    if (platled->dev < 0)
    {
        printk("device_create fail!\n");
        ret = -EFAULT;
        goto device_err;
    }

    return 0;

device_err:
    class_destroy(platled->cls);

class_err:
    unregister_chrdev(platled->major, "plat_led");

chrdev_err:
    kfree(platled);

    return ret;
}

static void led_unregister(void)
{
    device_destroy(platled->cls, MKDEV(platled->major, 0));
    class_destroy(platled->cls);
    unregister_chrdev(platled->major, "plat_led");
    kfree(platled);
}


int plat_led_probe(struct platform_device *pdev)
{
    int ret;
    struct resource *res;

    ret = led_register();
    if (ret < 0)
    {
        printk("plat_led_probe fail\n");
        return -EFAULT;
    }

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (res == NULL)
    {
        printk("platform_get_resource fail\n");
        return -ENODEV;
    }
// 獲取平臺自定義數據
    platled->reg = pdev->dev.platform_data;
        
    platled->reg_base = ioremap(res->start, resource_size(res));
    writel((readl(platled->reg_base) & (~platled->reg->ctl_clr)) | platled->reg->ctl_set, platled->reg_base);

    return 0;
}

int plat_led_remove(struct platform_device *pdev)
{
    iounmap(platled->reg_base);
    led_unregister();
    
    return 0;
}

const struct platform_device_id led_id_table[]  = {
        {"plat_led", 0x1234}, //第二個整數是自定義
};

struct platform_driver led_pdrv = {
    .probe = plat_led_probe,
    .remove = plat_led_remove,
    .driver = {
        .name    = "red_led",
    },
    .id_table = led_id_table,// name用於匹配
};

static int __init plat_led_drv_init(void)
{
    platform_driver_register(&led_pdrv);

    return 0;
}

static void __exit plat_led_drv_exit(void)
{
    platform_driver_unregister(&led_pdrv);
}

module_init(plat_led_drv_init);
module_exit(plat_led_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Aaron Lee");

plat_led.h

#ifndef __PLAT_LED_H_
#define __PLAT_LED_H_

struct regled {
    unsigned long ctl_clr;
    unsigned long ctl_set;
    unsigned long dat_clr;
    unsigned long dat_set;
};

struct samsung {
    int major;
    struct class *cls;
    struct device *dev;
    struct regled *reg;
    void *reg_base;
};

#endif
相關文章
相關標籤/搜索