開發板自帶的LCD驅動是基於platform匯流排寫的,所以如果要使其它的LCD能夠在自己的開發板上跑起來,那麼就先瞭解platform驅動的架構,下麵簡單記錄下自己看platform驅動時體會,簡單的說platform是一種虛擬匯流排,那麼它也是一條匯流排,所以它分為3個部分,platform_bus,
開發板自帶的LCD驅動是基於platform匯流排寫的,所以如果要使其它的LCD能夠在自己的開發板上跑起來,那麼就先瞭解platform驅動的架構,下麵簡單記錄下自己看platform驅動時體會,簡單的說platform是一種虛擬匯流排,那麼它也是一條匯流排,所以它分為3個部分,platform_bus,platform_device,platform_driver。在platform_device向platform_bus註冊設備,platform_driver向platform_bus註冊驅動,註冊後在platform_bus中會有一條device鏈表和driver鏈表,platform_bus中match函數將匹配兩者的名字,如果相同那就把驅動和設備進行綁定。Linux platform driver機制和傳統的device driver機制(通過driver_register進行註冊)相比,一個明顯的優勢在於platform機制將設備本身的資源註冊進內核,由內核統一管理,在驅動中使用這些資源時通過platform device提供的標準結構進行申請並使用。這樣提高了驅動和資源的獨立性,並且具有較好的可移植性和安全性(這些標準介面是安全的)。下麵舉一個簡單platform驅動的例子來分析platform_device和platform_driver是怎麼聯繫,platform_driver是怎麼使用platform_device提供硬體信息的。
led_dev.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
/* 分配/設置/註冊一個platform_device */
static struct resource led_resource[] = {
[0] = {
.start = 0x56000010, /* TQ2440的LED是GPB5,6,7,8, GPBCON地址是0x56000010 */
.end = 0x56000010 + 8 - 1,
.flags = IORESOURCE_MEM, /* 標識led控制器io埠*/
},
[1] = {
.start = 5, /* LED1 */
.end = 5,
.flags = IORESOURCE_IRQ, /* 標識LED中斷 */
}
};
/* 必須提供realease函數,可以不實現 */
static void led_release( struct device * dev)
{
}
static struct platform_device led_dev = {
.name = "myled" , /* 設備名 */
.id = -1, /* 一般設為-1,表示同樣名字的設備只有一個 */
.num_resources = ARRAY_SIZE(led_resource), /* 資源數量*/
.resource = led_resource,
.dev = {
.release = led_release, /* 引用上面定義的資源 */
},
};
static int led_dev_init( void )
{
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" );
|
Led_drv.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
#include <linux/fs.h>
#include <linux/interrupt.h
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/io.h>
static int major;
static struct class *cls;
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)
{
//printk("first_drv_open\n");
/* 配置為輸出 */
*gpio_con &= ~(0x3<<(pin*2));
*gpio_con |= (0x1<<(pin*2));
return 0;
}
static ssize_t led_write( struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
//printk("first_drv_write\n");
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1)
{
// 點燈
*gpio_dat &= ~(1<<pin);
}
else
{
// 滅燈
*gpio_dat |= (1<<pin);
}
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE, /* 這是一個巨集,推向編譯模塊時自動創建的__this_module變數 */
.open = led_open,
.write = led_write,
};
static int led_probe( struct platform_device *pdev)
{
struct resource *res;
/* 分配/設置/註冊一個platform_driver */
/* 根據platform_device的資源進行ioremap */
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" );
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led" ); /* /dev/led */
return 0;
}
static int led_remove( struct platform_device *pdev)
{
/* 卸載字元設備驅動程式 */
/* iounmap */
printk( "led_remove, remove led\n" );
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "myled" );
iounmap(gpio_con);
return 0;
}
struct platform_driver led_drv = {
.probe = led_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" );
|
這就是一個簡單的platform驅動,這兩個文件我們完全可以寫道一個文件中去實現,現在看下由一個文件如何實現這個驅動:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static struct class *seconddrv_class;
static struct class_device *seconddrv_class_dev;
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
static int second_drv_open( struct inode *inode, struct file *file)
{
/*
* K1,K2,K3,K4對應GPF1、GPF4、GPF2、GPF0
*/
/* 配置GPF1、GPF4、GPF2、GPF0為輸入引腳 */
*gpfcon &= ~((0x3<<(1*2)) | (0x3<<(4*2)) | (0x3<<(2*2)) | (0x3<<(0*2)));
return 0;
}
ssize_t second_drv_read( struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
/* 返回4個引腳的電平 */
unsigned char key_vals[4];
int regval;
if (size != sizeof (key_vals))
return -EINVAL;
regval = *gpfdat;
key_vals[0] = (regval & (1<<1)) ? 1 : 0;
key_vals[1] = (regval & (1<<4)) ? 1 : 0;
key_vals[2] = (regval & (1<<2)) ? 1 : 0;
key_vals[3] = (regval & (1<<0)) ? 1 : 0;
copy_to_user(buf, key_vals, sizeof (key_vals));
return sizeof (key_vals);
}
static struct file_operations sencod_drv_fops = {
.owner = THIS_MODULE, /* 這是一個巨集,推向編譯模塊時自動創建的__this_module變數 */
.open = second_drv_open,
.read = second_drv_read,
};
int major;
static int second_drv_init( void )
{
major = register_chrdev(0, "second_drv" , &sencod_drv_fops);
seconddrv_class = class_create(THIS_MODULE, "second_drv" );
seconddrv_class_dev = class_device_create(seconddrv_class, NULL, MKDEV(major, 0), NULL, "buttons" ); /* /dev/buttons */
gpfcon = ( volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
return 0;
}
static void second_drv_exit( void )
{
unregister_chrdev(major, "second_drv" );
class_device_unregister(seconddrv_class_dev);
class_destroy(seconddrv_class);
iounmap(gpfcon);
return 0;
}
module_init(second_drv_init);
module_exit(second_drv_exit);
MODULE_LICENSE( "GPL" );
|
由此可見,如果由platform驅動去實現led驅動將會多出很多東西,而這些多出來的就是我們如何把一個驅動程式融合到platform裡面,那既然用platform驅動要多寫那麼多東西那問什麼還要寫基於platform的驅動呢?我的理解是基於以下幾個原因:如果一個設備掛在匯流排上,其結果是配套的sysfs結點,設備電源管理都成為可能;隔離了BSP和驅動,在BSP中定義platform設備和設備使用的資源,設備的具體配置信息,而在驅動中,只要通過API去獲取資源和數據,做到了板相關代碼與驅動代碼的分離,使得驅動具有更好的可移植性和更好的擴展性和跨平臺性;假如我們要實現一個LCD驅動,那麼我們只需修改BSP相關的代碼,platform基本上不需修改,避免重覆著輪子的可能性;基於platformplatform機制將設備本身的資源註冊進內核,由內核統一管理。
上面platform的例子還沒有完全按照linux platform的標準去寫,因為閱讀代碼可知linux platform驅動把platform_device相關的代碼放在一塊,然後統一進行註冊!
下麵記錄下如何把device添加到板級文件中(最開始還是建議寫成兩個文件,分別編譯成模塊載入,因為如果是放到板級文件中那麼需要重新編譯內核,這個將在編譯上浪費很多時間)
步驟:
1. 在/arch/arm/plat-s3c24xx/devs.c中定義led 相關的設備及資源,代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
static struct resource led_resource[] = {
[0] = {
.start = 0x56000010, /* TQ2440的LED是GPB5,6,7,8, GPBCON地址是0x56000010 */
.end = 0x56000010 + 8 - 1,
.flags = IORESOURCE_MEM, /* 標識led控制器io埠*/
},
[1] = {
.start = 5, /* LED1 */
.end = 5,
.flags = IORESOURCE_IRQ, /* 標識LED中斷 */
}
};
EXPORT_SYMBOL(s3c_device_lcd);
|
請註意最後一行是導出led平臺設備,會在mach-smdk2440.c中添加到平臺設備列表中。
2. 如果還需定義led的其它信息,那麼在/arch/arm/plat-s3c2410/include/mach/fb.h 為led平臺設備定義一個s3c2410fb_mach_info結果體。led很簡單所以沒有必要再去定義,但是後面講的LCD的平臺設備需要定義!因為僅僅通過resource,驅動還無法操作LCD。
3. 不要實現,只是記錄下系統還會做什麼事! 在mach-smdk2440.c中將會調用platform_add_devices(),註冊平臺設備。
好了對platform有了一定瞭解後,那麼看看如果將LCD驅動寫成linux設備模型的platform驅動,同樣給出不使用platform架構是一個完整的字元設備驅動的lcd驅動,在給出一個符合platform模型的lcd驅動,通過這兩個文件的對比,就可以知道在符合platform模型的lcd驅動中可以找到LCD驅動的全部信息,也會發現基本上所有plat_form驅動出了驅動本身的東西外,框架性的東西基本上一樣的,所以如果理解了一兩個platform驅動,那麼以後寫platform驅動或看platform代碼跟不以flatform寫出的代碼是沒有什麼區別的!代碼如下: