2440 LCD嵌入式驅動用到了Linux platform driver 機制。一個十分明顯的優勢在於 platform
機制將設備本身的資源註冊進內核,由內核統一管理,在驅動程序中使用這些資源時通過 platform device
提供的標準接口進行申請並使用。這樣提高了驅動和資源管理的獨立性,並且擁有較好的可移植性和安全性 ( 這些標準接口是安全的 ) Platform
機制的本身使用並不複雜,由兩部分組成: platform_device 和 platfrom_driver 。 通過 Platform
機制開發發底層驅動的大致流程爲 :
定義
platform_device
->
註冊
platform_device->
定義
platform_driver->
註冊
platform_driver
。
plat層定義device, 如下面文件定義了 s3c_device_lcd
/linux/linux-2.6.29/arch/arm/plat-s3c24xx/Devs.c
struct
platform_device s3c_device_lcd =
{
.
name =
"s3c2410-lcd"
,
.
id =
-
1,
.
num_resources =
ARRAY_SIZE(
s3c_lcd_resource)
,
.
resource =
s3c_lcd_resource,
.
dev =
{
.
dma_mask =
&
s3c_device_lcd_dmamask,
.
coherent_dma_mask =
0xffffffffUL }
}
|
驅動層即s3c2410fb.c定義platform_driver 如下面文件定義了s3c2410fb_driver: /linux/linux-2.6.29/drivers/video/S3c2410fb.c
static
struct
platform_driver s3c2410fb_driver =
{
.
probe =
s3c2410fb_probe,
.
remove
=
s3c2410fb_remove,
.
suspend =
s3c2410fb_suspend,
.
resume =
s3c2410fb_resume,
.
driver =
{
.
name =
"s3c2410-lcd"
,
.
owner =
THIS_MODULE,
}
,
}
;
|
因爲內核配置是針對s3c2440芯片的,
s3c2440自帶lcd控制器,因此內核已經知道有s3c_device_lcd這個device存在, 驅動爲device服務,driver通過
platform_driver_register(&s3c2410fb_driver) 告訴內核驅動的存在,內核根據
driver.name 找到 device, 然後把device的信息通過 platform_device *pdev
這個參數傳遞給driver下掛着的各個功能函數,從而使驅動完成使命.
驅動函數s3c24xxfb_probe 所引用到的參數 mach_info, 即pdev->dev.platform_data 在 mach-mini2440.c裏定義如下
static
struct
s3c2410fb_mach_info mini2440_fb_info __initdata =
{
.
displays =
&
mini2440_lcd_cfg,
.
num_displays =
1,
.
default_display =
0,
.
gpccon =
0xaa955699,
.
gpccon_mask =
0xffc003cc,
.
gpcup =
0x0000ffff,
.
gpcup_mask =
0xffffffff,
.
gpdcon =
0xaa95aaa1,
.
gpdcon_mask =
0xffc0fff0,
.
gpdup =
0x0000faff,
.
gpdup_mask =
0xffffffff,
.
lpcsel =
0xf82,
}
;
|
驅動根據 lcd device 信息建立一塊buffer, 然後把這塊buffer作爲一個設備通過 ret =
register_buffer(fbinfo) 註冊到linux設備管理系統中, linux在 /dev 下生成一個叫 fb 的節點,
GUI就對這個 fb 節點操作.
下面我們從platform_device s3c_device_lcd定義開始分析。
struct
platform_device s3c_device_lcd =
{
.
name =
"s3c2410-lcd"
,
.
id =
-
1,
.
num_resources =
ARRAY_SIZE(
s3c_lcd_resource)
,
.
resource =
s3c_lcd_resource,
.
dev =
{
.
dma_mask =
&
s3c_device_lcd_dmamask,
.
coherent_dma_mask =
0xffffffffUL
}
}
|
其中
.name "s3c2410-lcd"是2440LCD驅動的設備名稱。它的作用上面已經說明了。是在設備驅動匹配的時候用的。
.id = -1 id表示設備編號,id的值爲-1表示只有一個這樣的設備。
.resource = s3c_lcd_resource 該結構中比較重要的一個成員就是resource, Linux設計了這個通用的數據結構來描述各種I/O資源(如:I/O端口、外設內存、DMA和IRQ等)。它的定義如
// include/linux/ioport.h:
struct resource {
const
char
*
name;
unsigned
long
start,
end;
unsigned
long
flags;
struct
resource *
parent,
*
sibling,
*
child;
}
;
|
struct resource 是linux對掛接在4G總線空間上的設備實體的管理方式。
一個獨立的掛接在cpu總線上的設備單元,一般都需要一段線性的地址空間來描述設備自身,linux是怎麼管理所有的這些外部"物理地址範圍段",進而給用戶和linux自身一個比較好的觀察4G總線上掛接的一個個設備實體的簡潔、統一級聯視圖的呢?
linux採用struct resource結構體來描述一個掛接在cpu總線上的設備實體(32位cpu的總線地址範圍是0~4G):
resource-
>
start 描述設備實體在cpu總線上的線性起始物理地址;
resource-
>
end 描述設備實體在cpu總線上的線性結尾物理地址;
resource-
>
name 描述這個設備實體的名稱,
這個名字開發人員可以隨意起,
但最好貼切;
resource-
>
flag 描述這個設備實體的一些共性和特性的標誌位;
|
只需要瞭解一個設備實體的以上4項,linux就能夠知曉這個掛接在cpu總線的上的設備實體的基本使用情況,也就是 [resource->start, resource->end]這段物理地址現在是空閒着呢,還是被什麼設備佔用着呢?
linux
會堅決避免將一個已經被一個設備實體使用的總線物理地址區間段[resource->start,
resource->end],再分配給另一個後來的也需要這個區間段或者區間段內部分地址的設備實體,進而避免設備之間出現對同一總線物理地址段
的重複引用,而造成對唯一物理地址的設備實體二義性.
以上的4個屬性僅僅用來描述一個設備實體自身,或者是設備實體可以用來自治的單元,但是這不
是linux所想的,linux需要管理4G物理總線的所有空間,所以掛接到總線上的形形色色的各種設備實體,這就需要鏈在一起,因此resource結
構體提供了另外3個成員:指針parent、sibling和
child:分別爲指向父親、兄弟和子資源的指針,它們的設置是爲了以一種樹的形式來管理各種I/O資源,以root
source爲例,root->child(*pchild)指向root所有孩子中地址空間最小的一個;pchild->sibling是
兄弟鏈表的開頭,指向比自己地址空間大的兄弟。
屬性flags是一個unsigned long類型的32位標誌值,用以描述資源的屬性。比如:資源的類型、是否只讀、是否可緩存,以及是否已被佔用等。下面是一部分常用屬性標誌位的定義
2440的s3c_lcd_resource結構定義。
路徑
/opt/FriendlyARM/mini2440/linux-2.6.29/arch/arm/plat-s3c24xx/devs.c
/* LCD Controller */
static
struct
resource s3c_lcd_resource[
]
=
{
[
0]
=
{
.
start =
S3C24XX_PA_LCD,
.
end =
S3C24XX_PA_LCD +
S3C24XX_SZ_LCD -
1,
.
flags =
IORESOURCE_MEM,
}
,
[
1]
=
{
.
start =
IRQ_LCD,
.
end =
IRQ_LCD,
.
flags =
IORESOURCE_IRQ,
}
}
;
|
其中S3C24XX_PA_LCD定義在
/opt/FriendlyARM/mini2440/linux-2.6.29/arch/arm/mach-s3c2400/include/mach/map.h
#
define
S3C2400_PA_LCD (
0x14A00000)
|
S3C24XX_SZ_LCD定義在
/opt/FriendlyARM/mini2440/linux2.6.29/arch/arm/include/asm/sizes.h:
#
define
S3C24XX_SZ_LCD SZ_1M #
define
SZ_1M 0x00100000
|
由上可知LCD佔用的資源包括兩類,一類是MEM類型,一類是IRQ類型。MEME類型資源對應的物理地址範圍是 0x14A00000
- 0x14AFffff;
下面在/opt/FriendlyARM/mini2440/linux-2.6.29/arch/arm/mach-s3c2410/mach-smdk2410.c
中調用platform_add_devices()來向系統中添加該設備了,首先來看它的定義
static
struct
platform_device *
smdk2410_devices[
]
__initdata =
{
&
s3c_device_usb,
&
s3c_device_lcd,
&
s3c_device_wdt,
&
s3c_device_i2c0,
&
s3c_device_iis,
}
;
static
void
__init smdk2410_init(
void
)
{
s3c_i2c0_set_platdata(
NULL
)
;
platform_add_devices(
smdk2410_devices,
ARRAY_SIZE(
smdk2410_devices)
)
;
smdk_machine_init(
)
;
}
|
其中platform_add_devices()->platform_driver_register。
int
platform_device_register(
struct
platform_device *
pdev)
{
device_initialize(
&
pdev-
>
dev)
;
return
platform_device_add(
pdev)
;
}
|
其中device_initialize函數分析在
http://w.xue163.com/html/20091219/3169506.html
這裏暫不分析。
platform_device_add函數
int
platform_device_add(
struct
platform_device *
pdev)
{
int
i,
ret =
0;
if
(
!
pdev)
/*驗證指針的有效性 */
return
-
EINVAL;
if
(
!
pdev-
>
dev.
parent)
/*
都說總線有兩個鏈表,一個是設備鏈表(通過device
內嵌)一個是驅動鏈表(通過device_driver內嵌)這裏如果pdev->dev.parent爲0,說明設備鏈表還沒有設備,因此處理辦
法是將platform_bus作爲設備鏈表的開始,一直感覺platform_bus和platform_bus_type很難區分,不過在這裏清楚了
platform_bus是一個設備,platform_bus_type纔是真正的總線*/
pdev-
>
dev.
parent =
&
platform_bus;
/*device 的父結點*/
pdev-
>
dev.
bus =
&
platform_bus_type;
/*device 要掛接在platform_bus_type這個總線上拉,看到了,設備和總線是這麼勾搭上滴,很直接,很乾脆*/
if
(
pdev-
>
id !
=
-
1)
snprintf(
pdev-
>
dev.
bus_id,
BUS_ID_SIZE,
"%s.%d"
,
pdev-
>
name,
pdev-
>
id)
;
/*這個如果看不懂,可以參考LINUX的格式化輸出的相關資料*/
else
strlcpy(
pdev-
>
dev.
bus_id,
pdev-
>
name,
BUS_ID_SIZE)
;
for
(
i =
0;
i <
pdev-
>
num_resources;
i+
+
)
{
struct
resource *
p,
*
r =
&
pdev-
>
resource[
i]
;
if
(
r-
>
name =
=
NULL
)
/*name一般爲NULL*/
r-
>
name =
pdev-
>
dev.
bus_id;
/*資源的名稱賦值爲pdev->dev.bus_id,如果一個platform_device有多個resource 則出現同名現象*/
p =
r-
>
parent;
if
(
!
p)
{
/*父資源爲0,說明不是從一個大的資源裏面切割出來的*/
if
(
r-
>
flags &
IORESOURCE_MEM)
p =
&
iomem_resource;
else
if
(
r-
>
flags &
IORESOURCE_IO)
p =
&
ioport_resource;
}
if
(
p &
&
insert_resource(
p,
r)
)
{
/*如果從父資源裏面切割失敗,則進行如下處理*/
printk(
KERN_ERR
"%s: failed to claim resource %d/n"
,
pdev-
>
dev.
bus_id,
i)
;
ret =
-
EBUSY;
goto
failed;
}
}
pr_debug(
"Registering platform device '%s'. Parent at %s/n"
,
pdev-
>
dev.
bus_id,
pdev-
>
dev.
parent-
>
bus_id)
;
ret =
device_add(
&
pdev-
>
dev)
;
/*資源也分配好了,準備工作也做足,終於可以把設備添加到設備鏈表裏面了*/
if
(
ret =
=
0)
return
ret;
failed:
/*失敗處理*/
while
(
-
-
i >
=
0)
if
(
pdev-
>
resource[
i]
.
flags &
(
IORESOURCE_MEM|
IORESOURCE_IO)
)
release_resource(
&
pdev-
>
resource[
i]
)
;
return
ret;
}
EXPORT_SYMBOL_GPL(
platform_device_add)
;
|
至此和
platform_device相關的部分分析完畢。下面分析platform_driver