1. 程式人生 > >【驅動】第4課、LCD驅動之學習筆記

【驅動】第4課、LCD驅動之學習筆記

開發環境

主   機:VMWare--Ubuntu-16.04.2-x64-100ask
開發板:Mini2440--256M NandFlash, 2M NorFlash, 64M SDRAM, LCD-TD35;
    bootlorder:u-boot1.16, Kernel:2.6.22.6;
編譯器:arm-linux-gcc-3.4.5


 目錄

1、LCD驅動程式分析方法

2、本課程LCD驅動原始碼程式的缺陷

3、本LCD驅動程式與裸機LCD程式框架的異同

4、LCD驅動程式設計擴充套件猜想

5、筆記

6、LCD驅動學習難點是以下三個函式的使用

7、問題:在bootloader階段、核心空間、使用者空間對指標指向的空間是如何定義和引用的

  其一:郝斌C語言課程關於指標常見錯誤的筆記

  其二:譚浩強《C程式設計》第八章_善於利用指標

  7.2 核心空間指標和指標變數的引用的方法?或者說對核心空間記憶體單元的操作方法

  總結

8、暫存器位運算詳解

9.測試


 

1、驅動程式分析,一般從入口函式(xx_init()函式)或裝置的 file_operations 結構體入手,一路走下去,

重點分析物件: 1>賦值語句; 2>函式呼叫; 3>
不特別在意的東西: 1>引數的判斷的語句; 2>看不懂的語句;

2、本LCD驅動程式的缺陷:對於LCD的引數的使用是直接檢視LCD手冊,然後填寫到驅動的幀緩衝器結構體、硬體暫存器的配置、
對於不同的LCD操作不具有可移植性,

3、本LCD驅動程式與裸機LCD程式框架的異同:
相同:
不同: 1)本LCD驅動多了幀緩衝器資訊結構體struct fb_info *s3c_lcd 的初始化和配置;
驅動程式中,根據s3c_lcd = framebuffer_alloc(0, NULL);分配幀緩衝器的各項資訊,
I: 內容上類同於裸板LCD結構體的定義,但是本質和功能完全不同,該驅動是幀緩衝器資訊結構體s3c_lcd,裸板是某款LCD的各項時間引數結構體lcd_3_5_params等;
幀緩衝器資訊結構體s3c_lcd作用:定義可供核心呼叫某款LCD的使用的LCD幀緩衝器;
LCD引數結構體lcd_3_5_params作用:給LCD控制器的初始化配置提供資料;
II:FB_BASE視訊記憶體地址的分配方式不同:
驅動上是>> /* 3.3 分配視訊記憶體(framebuffer), 並把地址告訴LCD控制器 */
s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
裸板上是>> #define LCD_3_5_FB_BASE 0x33b00000 //lcd_3.5.c檔案;

2)本LCD驅動少了裸板對於LCD結構體的定義、具體某一款LCD的定義初始化;

4、LCD驅動程式設計擴充套件猜想:可否把裸板的分離分層程式和該章的框架結合起來?甚至把前兩章學習的分離、分層結合起來?
編寫一個具備良好移植相容性的LCD驅動模組。

5、筆記:
1)對於暫存器或者對映記憶體,需要遵循先定義後使用的原則:
gpbcon = ioremap(0x56000010, 8);
lcdcon1 = (4<<8) | (3<<5) | (0x0c<<1);

2)硬體暫存器的設定上,本驅動和我的mini2440的暫存器配置的資料有什麼不同?
I:GPIO口——
II:LCD控制器——

3)LCD背光燈、LCD本身、觸控式螢幕是三個獨立的系統,只不過整合到一個LCD螢幕上工作而已。
4)裸板LCD的主要工作是配置LCD及其控制器,並實現分離分層,然後是關於測試LCD的應用程式:在幀緩衝器中填充資料以完成不同的圖案或字元的輸出;
驅動LCD的主要工作是配置幀緩衝器結構體struct fb_info *s3c_lcd ,然後是LCD相關暫存器的配置。
5)驅動中調色盤(假調色盤)終究沒有用到?只是在fb_ops結構體中定義好元素.fb_setcolreg(即對應LCD的調色盤配置函式s3c_lcdfb_setcolreg),
以相容以往的程式?

6、LCD驅動學習難點是以下三個函式的使用:
I. s3c_lcdfb = framebuffer_alloc(0, NULL);
功能:分配一個幀緩衝器,即分配一段連續的實體記憶體並 set to zero 給幀緩衝器結構體指標 s3c_lcdfb使用。
問題1:以前分配一個結構體時,直接定義然後初始化結構體內的元素即可。這次的xx_fb結構體,定義時(或之後),
需要在初始化之前使用專用的函式分配一段記憶體給結構體xx_fb。是因為這個結構體太大了嗎?
以前裸機全域性變數是直接在.bss段分配記憶體給其使用,由於.bss段在程式有效資料的最後面,只要總的記憶體還夠,就可以直接
順序分配記憶體空間給全域性變數。如此,既有問題2:
問題2:整個linux系統(bootlorder, linux核心, fs根檔案系統)中,關於記憶體是如何管理、使用和分配的?具體到驅動程式的全域性變數和應用程式
的全域性變數的記憶體又是怎樣分配的?
答:百度查到的博文有說,記憶體管理系統可以分為兩部分,分別是核心空間記憶體管理和使用者空間記憶體管理。具體以後再看。
問題3:framebuffer_alloc()函式的使用是因為在其之前只定義了幀緩衝器結構體指標 s3c_lcdfb的原因還是 因為結構體太大需要用專用函式
分配一段記憶體給該結構體使用?對於結構體的定義是怎麼樣的?
補充:以前裸機的全域性變數,若是一般全域性變數分配到.data段,const全域性變數(常變數)分配到.rodata段(只讀資料段),初值為0/無初值的全域性變數
分配到.bss段。這些段在記憶體中的地址分配如下:
SECTIONS{
. = 0x30000000; /*SDRAM在CPU統一編址中的基地址*/
__code_start = .;
. = ALIGN(4);
.text : {*(.text);}
. = ALIGN(4);
.rodata : {*(.rodata);}
. = ALIGN(4);
.data : {*(.data);}
. = ALIGN(4);
.bss : {
_bss_start = . ;
*(.bss) *(.COMMON);
}
. = ALIGN(4);
_end = . ;
}
注意:kzalloc申請過程做了初始化工作並在其後的過程中給指標的成員device做了賦值工作。如果暫時不要給device成員賦值,引數可以寫NULL。
II. s3c_lcdfb->screen_base = dma_alloc_writecombine(NULL, s3c_lcdfb->fix.smem_len, s3c_lcdfb->fix.smem_start, GFP_KERNEL); /* 3.3 分配視訊記憶體,並把視訊記憶體實體地址告訴LCD控制器 */
III. register_framebuffer(s3c_lcdfb); /* 4.註冊幀緩衝器裝置 */

--------------------------------------------------------------------------------------
7、問題:在bootloader階段、核心空間、使用者空間對指標指向的空間是如何定義和引用的?
答:我的困惑源於對各層級指標操作仍存在錯誤的模糊認識!關於bootloader空間、核心空間、使用者空間指標的用法如下。

7.0 <1>舊問題:核心中,對於結構體變數的是如何定義和分配記憶體的?
<2>定義結構體指標變數時,有沒有同時分配儲存空間啊?
<3>看到結構體的陣列定義好以後就直接可以用了。但是結構體指標在連結串列中還要malloc()申請空間。這是為什麼啊?
答:<1>兩種方法,一種是靜態分配記憶體(即直接定義):struct Student stu1; 第二種是通過該結構體指標動態分配一段記憶體空間:struct Student *pst2=malloc(sizeof(struct Student));
<2>定義結構體指標變數 p,並不代表定義了結構體指標指向的變數 *p,系統分配的記憶體是給指標變數p用的,對於指標p指向的空間未定義不分配記憶體空間;
<3>結構體陣列struct Student pstu3[]={{...}, {...}};即同時定義了兩個結構體變數pstu3[0], pstu3[1], 只不過他們是作為陣列pstu3的元素而存在的。
連結串列一般是動態分配記憶體空間,可隨時插入和刪除節點,作為指標域的指標,需要動態分配一個節點的記憶體空間作為其指向的插入的節點。

7.1 在使用者空間中指標變數的引用的方法,詳解如下:
其一:郝斌C語言課程關於指標常見錯誤的筆記
<0>指標就是地址,地址就是指標; 指標不是指標變數,指標變數不是指標。
<1>int *p; #不代表定義了一個叫做*p的變數!
<2>所有的變數、記憶體單元都遵循先定義再使用的規則,否則就會編譯報錯或程式崩潰!
例:int main(void){
int *p;
int i = 5;
// *p = i; //不遮蔽則程式執行崩潰!
printf("p = %#x\n", p); //列印:p = 0xcccccccc
// printf("*p = %d\n", *p); //不遮蔽則程式執行崩潰!
return 0;
}
/*在VC++6.0中的執行結果:
p = 0xcccccccc
Press any key to continue
*/
語句*p = i; (//不遮蔽則程式執行崩潰!)的詳解:
p須先有指向,然後才能有一個值(地址)賦給它; 若p無指向,則p為空/不可知的垃圾值,因為不知道p的值是多少,所以不知*p
到底代表的是哪一個變數。而*p=i=5,就是把5賦給了一個不知道地址的單元。歸根到底是因為使用者程式只能訪問屬於本程式的記憶體單元,
對於不屬於本程式的記憶體單元,指標p無權訪問。
<3>為指標變數指向的記憶體單元分配記憶體空間的方法(目前只有這兩種方法,其他alloc與malloc類同):
a.靜態分配指標p指向的記憶體空間:int *p; int i; p=&i; *p=5;
b.動態分配指標p指向的記憶體空間:int *p = (int *)malloc(sizeof(int)); *p=5; //其中(int *)可以省略,系統會自動轉換到需要的指標型別;
說明:記憶體的動態分配主要應用於建立程式中的動態資料結構(如連結串列)中。
<4>給指標變數賦值的方法只有一種: int *p; int i; p=&i; ?應該還有另外一種,如上malloc。

其二:譚浩強《C程式設計》第八章_善於利用指標
8.1 指標是什麼
為了說清楚什麼是指標,必須先弄清楚資料在記憶體中是如何儲存的,又是如何讀取的。
如果在程式中定義了一個變數,在對程式進行編譯時,系統就會給這個變數分配記憶體單元。編譯系統根據程式中定義的變數型別,分配一定長度的空間。
由於通過地址能找到所需的變數單元,可以說,地址指向該變數單元。
在程式中一般是通過變數名來引用變數的值,例如:printf("%d\n", i);
上面實際上,是通過變數名i找到儲存單元的地址,從而對儲存單元進行存取操作的。程式經過編譯以後已經將變數名轉換為變數的地址,對變數值的存取都是
通過地址進行的。 (對變數操作的本質>>>>>>>>>對記憶體操作)。
將數值3送到變數i中,有兩種表達方法:
<1>直接訪問:將3直接送到變數i所標誌的單元中,例如“i=3;”; “scanf("%d", &i);”。
<2>間接訪問:將3送到變數pi所指向的單元(即變數i的儲存單元),例如“*pi=3;”, 其中*pi表pi指向的物件。
8.2 指標變數
存放地址的變數是指標變數,它用來指向另一個物件(如變數、陣列、函式等)。
8.2.1 定義指標變數
型別名 * 指標變數名;
8.2.1 引用指標變數
<1>給指標變數賦值。如:p=&a; //把a的地址賦給指標變數p;
<2>引用指標變數指向的變數。如果已經執行"p=&a;",即指標變數p指向了整型變數a,則:printf("%d",*p);其作用是以整數形式輸出指標變數p所指向的變數的值,即變數a的值。如果有賦值語句:*p=1;表示把整數1賦給p當前所指向的變數。
<3>引用指標變數的值。如:printf("%#x", p);
注意:要熟練掌握兩個有關的運算子:<1>& 取地址運算子。<2>* 指標運算子。
8.8 動態記憶體分配與指向它的指標變數
8.8.1 什麼是記憶體的動態分配
除了靜態記憶體分配(一般變數),C語言還允許建立記憶體動態分配區域,以存放一些臨時用的資料,這些資料不必在程式的宣告部分定義,也不必等到函式結束
時才釋放,而是需要時隨時開闢,不需要時隨時釋放。由於未在宣告部分定義它們為變數或陣列,因此不能通過變數名或者陣列名去引用這些資料,只能通過指標來引用。
8.8.2 怎樣建立記憶體的動態分配
對記憶體的動態分配是通過系統提供的庫函式來實現的,主要有malloc, calloc, free, realloc等函式。
記憶體的動態分配主要應用於建立程式中的動態資料結構(如連結串列)中。
【知識歸納】
指標的定義:一個從0開始的非負整數的記憶體單元編號。
指標變數的使用:先定義(int i;int *p;),然後賦初值(p=&i),再然後才可以對其指向的記憶體單元進行讀/寫等操作。

7.2 核心空間指標和指標變數的引用的方法?或者說對核心空間記憶體單元的操作方法?
分兩類:一類是對硬體地址的直接引用(*(volatile unsigned int *)(0x48000000)) ,但只有讀/寫兩種操作方式,這是在bottloader(和核心?)階段使用的,
使用者空間所不具備的應用方式。使用者空間屬於應用層,通過核心方可訪問硬體(包括各種裝置的地址)。
這個對硬體地址的直接引用的層級沒有系統概念,因為系統是我們自己定義編寫的。
二類是與使用者空間的方法一樣:定義指標變數靜態分配記憶體,然後賦初值再引用,p=&a;*p=3; 或動態分配記憶體int *p=malloc(sizeof(int));*p=5;可以有讀/寫/算數加減(包括自加減)等運算操作。
【總結】
造成這種模糊認知是因為arm學習中,CPU對GPIO介面、協議類介面、記憶體類介面控制器(不包括Nandflash)進行統一編址,
對暫存器的操作(包括讀/寫資料)是直接用暫存器的地址(即指標)的指標運算子* Addr(例如:GPACON)作為物件進行讀/寫操作,而非藉助
某一物件,或者說這個物件已經迴歸到某一具體(已知地址的)裝置或記憶體單元。
#define __REG(x) (*(volatile unsigned int *)(x))
#define BWSCON __REG(0x48000000) //Memory Controllers:Bus width & wait status control
#define GPACON __REG(0x56000000) //I/O port:Port A control
在裸機和核心中,對於沒有賦初值的變數一般自動寫0,指標變數寫NULL。這時,對指標變數 p 操作:*p=3;是不現實的。

----------------------------------------------------------------------------------------------------------------
8、暫存器位運算詳解
<1>lcdsaddr1的設定如下,哪種方法正確?
LCDSADDR1位定義:
LCDBANK [29:21]
這些位表明系統儲存器中視訊緩衝器的 bank 位置的 A[30:22]。即使當
移動視口時也不能改變 LCDBANK 的值。LCD 幀緩衝器應該在 4MB
連續區域內,以保證當移動視口時不會改變 LCDBANK 的值。因此應
該謹慎使用 malloc()函式。
預設值:0x00
LCDBASEU [20:0]
對於雙掃描 LCD:這些位表明遞增地址計數器的開始地址的 A[21:1],
它是用於雙掃描 LCD 的遞增幀儲存器或單掃描 LCD 的幀儲存器。
對於單掃描 LCD:這些位表明 LCD 幀緩衝器的開始地址的 A[21:1]。
預設值:0x000000
LCD裸板程式該暫存器的配置:
addr = plcdparams->fb_base<<1;
addr >>= 2;
LCDSADDR1 = addr; //幀緩衝器的開始地址;
方法1:plcd_regs->lcdsaddr1 = ((s3c_lcdfb->fix.smem_start<<1)>>2);
方法2:lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
問題:哪種可以達到正確配置要求?
答:方法2是經過LCD驅動上機驗證可行的,對於方法1是裸板LCD程式使用的方法,但是對於long型資料的直接多次位操作並賦予
暫存器尚待商榷。
驗證程式碼:
#include <stdio.h>
int main(void)
{ unsigned int addr1 = 0xffffffff;
unsigned int lcdsaddr1 = ((addr1<<1)>>2);
unsigned int lcdsaddr2 = ((0xffffffff<<1)>>2);
printf("lcdsaddr1 = %#x, lcdsaddr2 = %#x\n", lcdsaddr1, lcdsaddr2);
return 0;
}
/*在VC++6.0中的執行結果:
lcdsaddr1 = 0x3fffffff, lcdsaddr2 = 0x3fffffff
Press any key to continue
*/
VC++6.0和gcc編譯器對於這類位操作的規則是否一致?為什麼不在直接在arm平臺測試該應用程式呢?##########
若一致,即可說明方法2也是可行的。本次LCD驅動測試使用方法2,觀察LCD執行是否正常。
現象:可使得LCD正常工作!以上兩種方法均可執行!


9.測試
目的:配置一個沒有LCD模組的核心,用新核心去掛接到開發板0x30000000(即SDRAM)進行測試:
# nfs 30000000 192.168.1.105:/work/nfs_root/uImage_mlcd
掛接(伺服器上的)網路根檔案系統到(開發板根檔案系統的)/mnt,從flash上啟動根檔案系統:
# mount -t nfs -o nolock,vers=2 192.168.1.105:/work/nfs_root/fs_second /mnt
說明:經在單板測試,若單板上的根檔案系統原本就是掛接的伺服器上的根檔案系統(例如:fs_second),則不需再用mount命令掛接網路根檔案
系統到開發板了!可直接進行驅動模組載入等試,執行命令:
# echo hello > /dev/tty1
均可在單板LCD上打印出字串“hello”!

操作步驟:
9.1 編譯一個新的沒有LCD模組的核心--uImage_nolcd
$ cd /home/book/workbook/mini2440/systems/linux-2.6.22.6/
$ make menuconfig //配置核心,去掉原來的LCD驅動程式;
-> Device Drivers
-> Graphics support
<M> S3C2410 LCD framebuffer support
$ make uImage //編譯生成新核心;
$ cp arch/arm/boot/uImage /work/nfs_root/uImage_nolcd
$ make modules //編譯模組,是為了把fb_ops結構體的3個cfb_xx函式對應的cfb_xx.c原始檔編譯成.ko檔案(模組),供稍後測試時使用。

9.2 用新核心啟動開發板,在倒數計時結束前按下“空格”鍵,進入uboot選單>
OpenJTAG> print
...(列印的核心資訊..)
ipaddr=192.168.7.17
...(列印的核心資訊..)
OpenJTAG> set ipaddr 192.168.1.17
OpenJTAG> save
OpenJTAG> nfs 30000000 192.168.1.105:/work/nfs_root/uImage_mlcd //uImage_mlcd是核心選單配置時,LCD_fb配置為<M>,即模組,可以事後載入使用;
OpenJTAG> bootm 30000000 //啟動新核心uImage_mlcd;
<啟動核心...>
# mount -t nfs -o nolock,vers=2 192.168.1.105:/work/nfs_root/fs_second /mnt
# cd /mnt
# ls
bin driver_test lib mnt sbin usr
dev etc linuxrc proc sys

9.3 重新配置單板根檔案系統的/etc/inittab檔案,使得單板上的Linux系統擁有串列埠0終端和單板按鍵-LCD兩個控制檯
修改inittab檔案:
# vi /etc/inittab
#/etc/inittab
#console::askfirst:-/bin/sh
::sysinit:/etc/init.d/rcS
s3c2410_serial0::askfirst:-/bin/sh
tty1::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
檢視:
# cat /etc/inittab
#/etc/inittab
#console::askfirst:-/bin/sh
::sysinit:/etc/init.d/rcS
s3c2410_serial0::askfirst:-/bin/sh
tty1::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
# reboot //重啟系統;
OpenJTAG> nfs 30000000 192.168.1.105:/work/nfs_root/uImage_mlcd
OpenJTAG> bootm 30000000 //啟動新核心uImage_mlcd;
<啟動核心...>
# mount -t nfs -o nolock,vers=2 192.168.1.105:/work/nfs_root/fs_second /mnt
# cd /mnt
# insmod cfbcopyarea.ko
# insmod cfbimgblt.ko
# insmod cfbfillrect.ko
# insmod lcd_6.ko
Console: switching to colour frame buffer device 30x40
# insmod input_keys2.ko
input: Unspecified device as /class/input/input1
<此時,LCD螢幕顯示提示資訊...以下都是單板按鍵--LCD螢幕控制檯的LCD螢幕的顯示資訊...>
Please press Enter to activate
starting pid 768, tty '/dev/tty1': '/bin/sh'
# ls
bin dev etc driver_test ...

問題:剛載入好了LCD模組,LCD可以正常使用,但是過一會不用就黑屏了,不能再顯示寫入的字串了,為什麼?
答:Linux下的LCD驅動預設在無操作之後10分鐘後會自動關閉螢幕。
問題:怎麼喚醒LCD螢幕呢?
答:若載入了單板的輸入子系統,可直接敲擊單板上的指令“按鍵”,即可自動喚醒LCD螢幕進行指令操作。
在中斷執行命令# echo wakakak > /dev/tty1 雖然仍然可以傳送字元到單板LCD螢幕,但是不能喚醒LCD螢幕,需要用單板按鍵喚醒!

 

10、原始碼

  1 /*
  2 2018-12-14
  3 File: lcd_2.c
  4 */
  5 #include <linux/module.h>
  6 #include <linux/kernel.h>
  7 #include <linux/errno.h>
  8 #include <linux/string.h>
  9 #include <linux/mm.h>
 10 #include <linux/slab.h>
 11 #include <linux/delay.h>
 12 #include <linux/fb.h>
 13 #include <linux/init.h>
 14 #include <linux/dma-mapping.h>
 15 #include <linux/interrupt.h>
 16 #include <linux/workqueue.h>
 17 #include <linux/wait.h>
 18 #include <linux/platform_device.h>
 19 #include <linux/clk.h>
 20 
 21 #include <asm/io.h>
 22 #include <asm/uaccess.h>
 23 #include <asm/div64.h>
 24 
 25 #include <asm/mach/map.h>
 26 #include <asm/arch/regs-lcd.h>
 27 #include <asm/arch/regs-gpio.h>
 28 #include <asm/arch/fb.h>
 29 MODULE_LICENSE("GPL");
 30 
 31 /* 函式宣告 */
 32 static int s3c_lcdfb_setcolreg(unsigned regno, unsigned red, unsigned green,
 33                 unsigned blue, unsigned transp, struct fb_info *info);    
 34 
 35 struct lcd_regs {
 36     unsigned long lcdcon1;
 37     unsigned long lcdcon2;
 38     unsigned long lcdcon3;
 39     unsigned long lcdcon4;
 40     unsigned long lcdcon5;
 41     unsigned long lcdsaddr1;
 42     unsigned long lcdsaddr2;
 43     unsigned long lcdsaddr3;
 44     unsigned long redlut;
 45     unsigned long greenlut;
 46     unsigned long bluelut;
 47     unsigned long reserved[8];
 48     unsigned long dithmode;
 49     unsigned long tpal;
 50     unsigned long lcdintpnd;
 51     unsigned long lcdsrcpnd;
 52     unsigned long lcdintmsk;
 53     unsigned long tconsel;
 54 };
 55 
 56 static struct fb_ops s3c_lcdfb_ops = {
 57     .owner = THIS_MODULE,
 58     /* set color register */
 59     .fb_setcolreg = s3c_lcdfb_setcolreg,
 60     /* Draws a rectangle */
 61     .fb_fillrect = cfb_fillrect,        /* void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect); */
 62     /* Copy data from area to another*/
 63     .fb_copyarea = cfb_copyarea,        /* void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);*/
 64     /* Draws a image to the display */
 65     .fb_imageblit = cfb_imageblit,        /*void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);*/
 66 };
 67 static struct fb_info *s3c_lcdfb;    /* 定義一個幀緩衝器結構體 */
 68 static volatile struct lcd_regs *plcd_regs;
 69 static volatile unsigned long *gpbcon;
 70 static volatile unsigned long *gpbdat;
 71 static volatile unsigned long *gpccon;
 72 static volatile unsigned long *gpdcon;
 73 static volatile unsigned long *gpgcon;
 74 static u32 pseudo_palette[16];
 75 
 76 /* 畫素格式轉換 */
 77 static inline unsigned int chan_to_field(u_int chan, struct fb_bitfield * bf)
 78 {
 79     chan &= 0xffff;                /* 保留該color的低16位 */
 80     chan >>= 16 - bf->length;    /* 裁剪到設定畫素資料格式的該color長度 */
 81     return chan<<bf->offset;
 82 }
 83 /* 設定調色盤 */
 84 static int s3c_lcdfb_setcolreg(unsigned regno, unsigned red, unsigned green,
 85                 unsigned blue, unsigned transp, struct fb_info *info)
 86 {
 87     unsigned int val;
 88     if(regno > 16)
 89         return 1;    /* unknown tybe */
 90     val = chan_to_field(red, &info->var.red);
 91     val |= chan_to_field(green, &info->var.green);
 92     val |= chan_to_field(blue, &info->var.blue);
 93     pseudo_palette[regno] = val;
 94     return 0;
 95 }
 96 
 97 static int lcd_init(void)
 98 {
 99     int ret = 0;
100     
101     /* 1.分配一個幀緩衝器 */
102     s3c_lcdfb = framebuffer_alloc(0, NULL);        /* sucess: return info; err: return NULL; */
103     if(!s3c_lcdfb)
104     {
105         printk(KERN_ERR "Unable to alloc s3c_lcdfb!\n");
106         goto err_fail1;
107     }
108     /* 2.設定幀緩衝器結構體 */
109     /* 2.1設定固定引數 */
110     strcpy(s3c_lcdfb->fix.id, "mylcd");    
111     //s3c_lcdfb->fix.smem_start     = ;        /* 幀緩衝器的起始地址(實體地址) */
112     s3c_lcdfb->fix.smem_len        = 320*240*16/8;            /* 幀緩衝器的長度 */
113     s3c_lcdfb->fix.type            = FB_TYPE_PACKED_PIXELS;
114     s3c_lcdfb->fix.visual        = FB_VISUAL_TRUECOLOR;    /* TFT真彩 */
115     s3c_lcdfb->fix.line_length    = 240*16/8;                /* length of a line in bytes */
116     /* 2.2設定可變引數 */
117     s3c_lcdfb->var.xres            = 240;        /* 可見解析度 */
118     s3c_lcdfb->var.yres         = 320;
119     s3c_lcdfb->var.xres_virtual    = 240;        /* 虛擬解析度 */
120     s3c_lcdfb->var.yres_virtual    = 320;
121     s3c_lcdfb->var.bits_per_pixel    = 16;    /* 16bpp */
122     /* RGB = 565 */
123     s3c_lcdfb->var.red.offset    = 11;        /* 開始位域偏移量 */
124     s3c_lcdfb->var.red.length    = 5;        /* 位域長度 */
125     s3c_lcdfb->var.green.offset = 5;    
126     s3c_lcdfb->var.green.length = 6;
127     s3c_lcdfb->var.blue.offset    = 0;
128     s3c_lcdfb->var.blue.length    = 5;
129     s3c_lcdfb->var.activate        = FB_ACTIVATE_NOW;
130     
131     /* 2.3設定操作函式 */
132     s3c_lcdfb->fbops    = &s3c_lcdfb_ops;
133     /* 2.4設定其餘引數 */
134     //s3c_lcdfb->screen_base         = ;    /* 螢幕開始的虛擬地址 */
135     s3c_lcdfb->screen_size         = 320*240*16/8; /* Amount of ioremapped VRAM or 0 */ 
136     s3c_lcdfb->pseudo_palette    = pseudo_palette;
137     /* 3.設定硬體(LCD相關暫存器) */
138     /* 3.1設定用於LCD的GPIO引腳 */
139     /* 對映暫存器地址到虛擬記憶體 */
140     gpbcon = ioremap(0x56000010, 8);
141     gpbdat = gpbcon + 1;
142     gpccon = ioremap(0x56000020, 4);
143     gpdcon = ioremap(0x56000030, 4);
144     gpgcon = ioremap(0x56000060, 4);
145     /* LCD背光燈LED+-, 硬體自動連線到電源正負極,不管? 
146      * GPB1: 配置為輸出引腳,高電平:背光供電;  低電平:不供電 
147      * GPBCON &= ~(3<<2);
148      * GPBCON |=  (1<<2);
149      */
150     *gpbcon &= ~(3<<2);
151     *gpbcon |=  (1<<2);
152     *gpbdat &= ~(1<<1);        /* 低電平,先不提供背光 */
153     /* S3C2440-LCD連線引腳狀態控制 */
154     *gpccon = 0xaaaaaaaa;     /* GPIO管腳用於VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
155     *gpdcon = 0xaaaaaaaa;    /* GPIO管腳用於VD[23:8] */
156     /* GPG4: 配置為 LCD_PWREN 功能引腳 */
157     *gpgcon |= (3<<8);
158     
159     /* 3.2設定LCD控制器 */
160     plcd_regs = ioremap(0X4D000000, sizeof(struct lcd_regs));
161     /* LCDCON1位定義:
162      * CLKVAL   [17:8] 決定 VCLK 的頻率和 CLKVAL[9:0]。TFT:VCLK = HCLK / [(CLKVAL + 1) × 2] (CLKVAL≥0);
163      * PNRMODE  [6:5] 11 = TFT LCD 面板;
164      * BPPMODE  [4:1] 選擇 BPP(位每畫素)模式:  1100 = TFT 的 16 bpp;  
165      * ENVID    [0]     0 = 禁止視訊輸出和 LCD 控制訊號;  1 = 允許視訊輸出和 LCD 控制訊號;
166      *
167      * //int CLKVAL = (double)HCLK/plcdparams->time_seq.vclk/2 - 1 + 0.5;
168      * //int CLKVAL = 5;        // jz2440  的4.3寸TFT螢幕;
169      * int CLKVAL = 7;            // mini2440的3.5寸TFT螢幕;
170      *  int bppmode =     plcdparams->bpp == 8?  0xb :\
171                     plcdparams->bpp == 16? 0xc :\
172                     0xd;    //0xd: 32/24bpp;
173      * LCDCON1 =  (CLKVAL<<8) | (3<<5) | (bppmode<<1);
174       */
175      plcd_regs->lcdcon1 =  (7<<8) | (3<<5) | (0xc<<1);
176     /* LCDCON2位定義:
177      * VBPD  [31:24]TFT:tvb-1, 垂直後沿為幀開始時,垂直同步週期後的的無效行數。0x00
178      * LINEVAL  [23:14]  xres-1 ,TFT/STN:此位決定了 LCD 面板的垂直尺寸。  0000000000
179      * VFPD  [13:6]TFT: tvf-1, 垂直前沿為幀結束時,垂直同步週期前的的無效行數。00000000
180      * VSPW  [5:0]TFT:  tvp-1, 通過計算無效行數垂直同步脈衝寬度決定 VSYNC 脈衝的高電平寬度。000000
181      
182      * LCDCON2 =     (plcdparams->time_seq.tvb - 1<<24) |\
183                 (plcdparams->time_seq.tvf - 1<<6)  |\
184                 (plcdparams->time_seq.tvp - 1<<0)  |\
185                 (plcdparams->yres - 1<<14);
186      */
187     plcd_regs->lcdcon2 =     (1<<24) | (319<<14) |(8<<6) | (1<<0);                
188     /* LCDCON3位定義:
189      * HBPD(TFT)[25:19] TFT:thb-1, 水平後沿為 HSYNC 的下降沿與有效資料的開始之間的 VCLK 週期數。0000000
190      * HOZVAL     [18:8]  TFT/STN:yres-1, 此位決定了 LCD 面板的水平尺寸。必須決定 HOZVAL 來滿足 1 行的總位元組為 4n 位元組。
191      *             如果單色模式中 LCD 的 x 尺寸為 120個點,但不能支援 x=120,因為 1 行是由 16 位元組(2n)所組成。
192      *            LCD面板驅動器將捨棄額外的 8 個點。00000000000
193      * HFPD(TFT)[7:0]   TFT:thf-1, 水平後沿為有效資料的結束與 HSYNC 的上升沿之間的 VCLK 週期數。0X00
194     
195      * LCDCON3 =     (plcdparams->time_seq.thb - 1<<19) |\
196                   (plcdparams->xres- 1<<8)             |\
197                   (plcdparams->time_seq.thf - 1<<0);
198       */
199      plcd_regs->lcdcon3 =     (34<<19) | (239<<8) | (39<<0);
200     /*
201      * LCDCON4位定義:
202      * HSPW(TFT)[7:0]TFT:通過計算 VCLK 的數水平同步脈衝寬度決定 HSYNC 脈衝的高電平寬度  0X00
203      
204      * LCDCON4 = (plcdparams->time_seq.thp - 1<<0);
205      */
206     plcd_regs->lcdcon4 = (4<<0);
207     /*
208      * LCDCON5位定義:
209      * FRM565     [11]TFT:此位選擇 16 bpp 輸出視訊資料的格式, 0 = 5:5:5:1 格式  1 = 5:6:5 格式;   0
210      * INVVCLK    [10]STN/TFT:此位控制 VCLK 有效沿的極性,  0 = VCLK 下降沿取視訊資料  1 = VCLK 上升沿取視訊資料; 0
211      * INVVLINE   [9]STN/TFT:此位表明 VLINE/HSYNC 脈衝極性, 0 = 正常  1 = 反轉;    0,
212      * INVVFRAME  [8]STN/TFT:此位表明 VFRAME/VSYNC 脈衝極性, 0 = 正常  1 = 反轉;    0,
213      * INVVD      [7]STN/TFT:此位表明 VD(視訊資料)脈衝極性, 0 = 正常  1 = 反轉 VD;    0
214      * INVVDEN    [6]TFT:此位表明 VDEN 訊號極性, 0 = 正常  1 = 反轉;    0
215      * INVPWREN   [5]STN/TFT:此位表明 PWREN 訊號極性, 0 = 正常  1 = 反轉;    0
216      * INVLEND    [4]TFT:此位表明 LEND 訊號極性, 0 = 正常  1 = 反轉;    0
217      * PWREN      [3]STN/TFT:LCD_PWREN 輸出訊號使能/禁止, 0 = 禁止 PWREN 訊號  1 = 允許 PWREN 訊號;    0
218      * ENLEND     [2]TFT:LEND 輸出訊號使能/禁止, 0 = 禁止 LEND 訊號  1 = 允許 LEND 訊號;    0
219      * 
220      * BPP24BL    [12]TFT:此位決定 24 bpp 視訊儲存器的順序, 0 = LSB 有效  1 = MSB 有效;    0
221      * BSWP       [1]STN/TFT:位元組交換控制位, 0 = 交換禁止  1 = 交換使能;    0
222      * HWSWP      [0]STN/TFT:半位元組交換控制位, 0 = 交換禁止  1 = 交換使能,    0
223      *
224      * 儲存器資料格式(TFT)
225      * pixels_data_format =     plcdparams->bpp == 32 ? (0<<0) : \
226                                 plcdparams->bpp == 16 ? (1<<0) : \
227                                 (1<<1);        //8bpp;
228        LCDCON5 =     (plcdparams->pin_pol.vclk<<10) |\
229                     (plcdparams->pin_pol.hsync<<9) |\
230                     (plcdparams->pin_pol.vsync<<8) |\
231                     (plcdparams->pin_pol.rgb<<7)   |\
232                     (plcdparams->pin_pol.de<<6)    |\
233                     (plcdparams->pin_pol.pwren<<5) |\
234                     (1<<11) | pixels_data_format;
235       */
236      plcd_regs->lcdcon5 = (1<<11) | (0<<10) |(1<<9) | (1<<8) | (0<<7) |\
237                           (0<<6)  |    (0<<5) |(0<<1) | (1<<0);
238 
239     /* 3.3 配置視訊記憶體 */
240     s3c_lcdfb->screen_base = dma_alloc_writecombine(NULL, s3c_lcdfb->fix.smem_len, (dma_addr_t *)&s3c_lcdfb->fix.smem_start, GFP_KERNEL);    /* 螢幕開始的虛擬地址 */
241     /* sucess: return info; err: return NULL; */
242     if(!s3c_lcdfb->screen_base)
243     {
244         printk(KERN_ERR "Unable to alloc the framebuffer of s3c_lcdfb->screen_base!\n");
245         goto err_fail2;
246     }
247     /*
248      * LCDSADDR1位定義:
249         LCDBANK  [29:21]
250         這些位表明系統儲存器中視訊緩衝器的 bank 位置的 A[30:22]。即使當
251         移動視口時也不能改變 LCDBANK 的值。LCD 幀緩衝器應該在 4MB
252         連續區域內,以保證當移動視口時不會改變 LCDBANK 的值。因此應
253         該謹慎使用 malloc()函式。
254         0x00
255         LCDBASEU  [20:0]
256         對於雙掃描 LCD:這些位表明遞增地址計數器的開始地址的 A[21:1],
257         它是用於雙掃描 LCD 的遞增幀儲存器或單掃描 LCD 的幀儲存器。
258         對於單掃描 LCD:這些位表明 LCD 幀緩衝器的開始地址的 A[21:1]。
259         0x000000
260     
261         addr = plcdparams->fb_base<<1;
262         addr >>= 2;
263         LCDSADDR1 = addr;        //幀緩衝器的開始地址;
264      */
265     plcd_regs->lcdsaddr1 = ((s3c_lcdfb->fix.smem_start<<1)>>2);
266     /*
267     LCDSADDR2  位  描述  初始狀態
268      LCDBASEL  [20:0]
269      對於單掃描 LCD:這些位表明 LCD 幀緩衝器的結束地址的 A[21:1]。
270      LCDBASEL = ((幀結束地址) >> 1) + 1
271      = LCDBASEU + (PAGEWIDTH+OFFSIZE) × (LINEVAL+1);
272      0x0000
273     
274     //addr = plcdparams->fb_base + plcdparams->xres*plcdparams->yres*plcdparams->bpp/8;
275     //LCDSADDR2 = (addr>>1) + 1;        //幀緩衝器的結束地址;
276 
277     addr = plcdparams->fb_base + plcdparams->xres*plcdparams->yres*plcdparams->bpp/8;
278     addr >>= 1;
279     //addr &= 0x1fffff;    //有沒有都一樣!
280     LCDSADDR2 = addr; 
281     */
282     plcd_regs->lcdsaddr2 = ((s3c_lcdfb->fix.smem_start + s3c_lcdfb->fix.smem_len)>>1) & 0x1fffff;
283     plcd_regs->lcdsaddr3 = 240*16/16;    /* 虛擬屏的頁寬度(單位: 半位元組) */
284     /* 3.4啟動LCD */
285     /* GPB0: 輸出高電平,給LCD提供背光燈LED+-
286      * GPBDAT |= (1<<1);
287      */
288     *gpbdat |= (1<<1);
289     /* LCDCON1位定義:
290      * ENVID [0] 使能LCD訊號輸出:  0 = 禁止視訊輸出和 LCD 控制訊號  1 = 允許視訊輸出和 LCD 控制訊號 
291      * LCDCON1 |= (1<<0);
292      */
293     plcd_regs->lcdcon1 |= (1<<0);
294     /* LCDCON5位定義:
295      * PWREN [3] STN/TFT:LCD_PWREN 輸出訊號使能/禁止, 0 = 禁止 PWREN 訊號  1 = 允許 PWREN 訊號;    0 
296      * LCDCON5 |= (1<<3);
297      */
298     plcd_regs->lcdcon5 |= (1<<3);
299 
300     /* 4.註冊幀緩衝器 */
301     ret = register_framebuffer(s3c_lcdfb);
302     if(ret < 0)
303     {
304         printk(KERN_ERR "Unable to register the s3c_lcdfb framebuffer device!\n");
305         goto err_fail3;
306     }
307     
308     return 0;
309  err_fail3:
310     unregister_framebuffer(s3c_lcdfb);
311     *gpbdat &= ~(1<<1);
312     plcd_regs->lcdcon1 &= ~(1<<0);
313     plcd_regs->lcdcon5 &= ~(1<<3);
314  err_fail2:
315     dma_free_writecombine(NULL, s3c_lcdfb->fix.smem_len, s3c_lcdfb->screen_base, s3c_lcdfb->fix.smem_start);
316     iounmap(plcd_regs);
317     iounmap(gpbcon);
318     iounmap(gpccon);
319     iounmap(gpdcon);
320     iounmap(gpgcon);
321  err_fail1:
322     framebuffer_release(s3c_lcdfb);
323     return ret;
324 }
325 
326 static void lcd_exit(void)
327 {
328     /* 1.登出幀緩衝器 */
329     unregister_framebuffer(s3c_lcdfb);
330     /* 2.禁止LCD使能 */
331     /* GPB0: 輸出高電平,給LCD提供背光燈LED+-
332      * GPBDAT &= ~(1<<1);
333      */
334     *gpbdat &= ~(1<<1);
335     /* LCDCON1位定義:
336      * ENVID [0] 使能LCD訊號輸出:  0 = 禁止視訊輸出和 LCD 控制訊號  1 = 允許視訊輸出和 LCD 控制訊號 
337      * LCDCON1 &= ~(1<<0);
338      */
339     plcd_regs->lcdcon1 &= ~(1<<0);
340     /* LCDCON5位定義:
341      * PWREN [3] STN/TFT:LCD_PWREN 輸出訊號使能/禁止, 0 = 禁止 PWREN 訊號  1 = 允許 PWREN 訊號;    0     
342      * LCDCON5 &= ~(1<<3);
343      */
344     plcd_regs->lcdcon5 &= ~(1<<3);
345 
346     /* 3.釋放視訊記憶體 */
347     dma_free_writecombine(NULL, s3c_lcdfb->fix.smem_len, s3c_lcdfb->screen_base, s3c_lcdfb->fix.smem_start);
348     /* 4.釋放暫存器對映的記憶體 */
349     iounmap(plcd_regs);
350     iounmap(gpbcon);
351     iounmap(gpccon);
352     iounmap(gpdcon);
353     iounmap(gpgcon);
354     /* 5.收回分配給幀緩衝器的空間 */
355     framebuffer_release(s3c_lcdfb);
356 }
357 
358 module_init(lcd_init);
359 module_exit(lcd_exit);

Makefile

 1 ifneq ($(KERNELRELEASE),)
 2     obj-m := lcd_6.o
 3 else
 4     KERN_DIR ?= /home/book/workbook/mini2440/systems/linux-2.6.22.6
 5 
 6     PWD = $(shell pwd)
 7 all:
 8     $(MAKE) -C $(KERN_DIR) M=$(PWD) modules
 9 clean:
10     $(MAKE) -C $(KERN_DIR) M=$(PWD) modules clean
11     rm -rf modules.order
12 
13 endif