平臺總線(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