天天看點

一文輕松搞懂LCD驅動編寫

  • 開發環境:
    • 開發闆:JZ2440V3
    • CPU:samsunS3C2440
    • 核心:Linux3.4.2
    • 編譯工具:arm-linux-gcc 4.3.2
    • LCD:4.3存液晶屏AT043TN24
  • 參考文獻:
    • LCD驅動程式詳細講解(一)_weixin_33935505的部落格-CSDN部落格
    • LCD驅動詳解 - Lilto - 部落格園 (cnblogs.com)
    • 主題:s3c2440移植linux-3.4.2中的LCD驅動_大白菜的部落格-CSDN部落格
    • 【第2期】韋東山嵌入式Linux之第2期_驅動大全 (100ask.net)

1、LCD種類、電路連接配接及顯示原理

LCD,即液晶顯示器,是一種采用了液晶控制透光技術來實作色彩的顯示器。LCD有很多種類型,比如STN、TFT、LTPS、OLED等。各有優缺點。JZ2440V3開發闆上面配置的是TFT類型液晶顯示器,也是目前最為主流的液晶顯示器。

  • TFT-LCD的資料傳輸方式有2種:

​ 單掃:對于一整屏的資料,從上到下,從左到右,一個一個地發送出來。

​ 雙掃:将一整屏的資料分為上下兩部分,同時的從上到下,從左到右,一個一個的發送出來。

  • LCD的信号種類:
信号名稱 描述
VSYNC 垂直同步信号
HSYNC 水準同步信号
VD[23:0] 資料信号
HCLK 時鐘信号
LEND 行結束信号
PWREN 電源開關信号
  • 電路子產品圖

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-2M8HW95E-1627057236188)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718154644.png)]

  • LCD控制器原理圖

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-BuSAaCRE-1627057236190)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718154827.png)]

  • 顯示原理

我們除了配置一些寄存器告訴LCD控制器圖像中像素的格式(RGB565),frameBuffer的首位址之類外,對于TFT LCD的通路還需要用到一些信号,是以需要通過配置寄存器來告訴LCD控制器這些信号的資訊(比如何時發出控制信号,發出信号的持續時間等),舉個例子: 向LCD驅動器發送圖檔資料時需要時鐘控制(VCLK),一個時鐘發送一個像素點,那麼控制器就需要主動發出時鐘信号,這個時鐘是由哪個引腳發出的,發出的頻率是多少,這個都是要配置寄存器的, 我們通過時序圖來分析需要用到的一些信号以及如何去配置它們,如果是第一次了解LCD控制,直接看時序還是比較困難的,是以先給出一個形象的比喻 :

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-jC1KnEaW-1627057236192)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210719224716.png)]

frame buffer: 顯存,用于存放LCD顯示資料;frame buffer通過LCD控制器和LCD Panel建立一一映射關系;

LCD控制器: 參考LCD使用者手冊,配置LCD控制器,用于發出LCD控制信号,驅動LCD顯示;

掃描方式: 如圖所示,由start到end的掃描方向是:從左到右,從上到下(掃描方向的一種);

HSYNC: 行同步信号,用于行切換,一行掃描結束,需要掃描新行時,需要先發送行同步信号;

VSYNC: 列同步信号,用于列切換,一幀掃描結束,需要掃描新的一幀時,需要先發送列同步信号;

時鐘信号: 每來一個時鐘,掃描的點移位一;

上圖中LD驅動器可以比喻成電子槍,LCD控制器就是控制這個電子槍的,它從顯示緩存中拿像素資料傳給電子槍并發送指令讓電子槍發射像素顔色, 上圖中,成像過程

  1. LCD控制器發出VSYNC信号,告訴電子槍,要發出一張新幀了,然後電子槍把槍頭調轉到LCD螢幕的左上角準備開始發射像素
  2. 發出VSYNC信号的同時,發出HSYNC信号(告訴電子槍新行開始, 從左向右動發射子彈吧)但是電子槍畢竟反應比較慢,過了少許開始發射子彈

    對于上面兩個過程,由于電子槍接受了VSYNC信号,調轉槍頭後,需要反應一段時間才能正常開始工作, 是以就白白掃射了幾行的無效資料,相當于經過了幾個HSYNC信号周期的時間, 一個HSYNC周期就是電子槍掃射一行的時間(從HSYNC信号開始掃射第一行直到到一行結束掃射結束所用時間),就出現了上方無效區

  3. 當第一行結束時,LCD控制器又發出HSYNC信号, 電子槍槍頭扭轉到下一行新行開始發射資料, 但是槍頭扭轉的比較慢, 是以出現了左右的無效區(即第一行結束後,電子槍由于硬體原因要反應一段時間, 是以在右邊出現了無效資料區, 調轉槍頭後, 也得反應一段時間開始發射子彈,是以出現了左邊的無效區),有人會問電子槍如何知道第一行何時結束(其實是我們通過寄存器告訴LCD控制器第一行有多少個資料的,我們的螢幕分辨率是480*272, 即這個資訊會設定到寄存器裡), 當一行結束時,LCD控制器就不會再發有效像素資料,并且等待電子槍遊離一段時間,之後再發下一行的HSYNC信号.
  4. loop第三個過程
  5. 當掃描到最後一行結束時(一幀即将結束),LCD控制器就不會再發有效像素資料,并且等待電子槍遊離一段時間,是以會繼續往下掃描,出現了下方的無效區, 之後再發下一行的VSYNC信号, 之後回到過程1開始重複。

在工作中的顯示器上,可以在四周看見黑色的邊框。上方的黑框是因為當發出VSYNC信号時,需要經過若幹行之後第一行資料才有效;下方的黑框是因為顯示完所有行的資料時,顯示器還沒有掃描到最下邊(VSYNC信号還沒有發出),這時資料是無效的;左邊的黑框是因為當發出HSYNC信号時,需要經過若幹像素之後第一列資料才有效;右邊的黑框是因為顯示完一行資料時,顯示器還沒掃描到最右邊(HSYNC信号還沒有發出),這時資料已經無效。顯示器隻會依據VSYNC、HSYNC信号來取得、顯示資料,并不理會該資料是否有效,何時發出有效的資料由顯示卡或LCD控制器決定。

VSYNC信号出現的頻率表示一秒鐘内能顯示多少幀圖像,稱為垂直頻率或場頻率,這就是我們常說的“顯示器頻率”;HSYNC信号出現的頻率稱為水準頻率,表示一秒鐘能顯示多少個像素的資料。

顯示器上,一幀資料的存放位置與VSYNC、HSYNC信号的關系如下圖所示:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-tEfm1q0j-1627057236195)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718155615.png)]

有效資料的行數、列數,即分辨率,它與VSYNC、HSYNC信号之間的距離等,都是可以設定的,這由LCD控制器來完成。

  • 資料組織方式

一幅圖像被稱為一幀(frame),每幀由多行組成,每行由多個像素組成,每個像素的顔色使用若幹位的資料來表示。對于單色顯示器,每個像素使用1位來表示,稱為1BPP;對于256色顯示器,每個像素使用8位來表示,被稱為8BPP。

顯示器上每個像素的顔色由3部分組成:紅(Red)、綠(Green)、藍(Blue)。它們被稱為三基色,這三者的混合幾乎可以表示人眼所能識别的所有顔色。比如可以根據顔色的濃烈程度将三基色都分為256個級别,則可以使用255級的紅色、255級的綠色、255級的藍色組合成白色,可以使用0級紅色、0級的綠色、0級的藍色組合成黑色。

LCD控制器可以支援單色(1BPP)、4級灰階(2BPP)、16級灰階(4BPP)、256色(8BPP)的調色闆顯示模式,支援64K(16BPP)和16M(24BPP)非調色闆顯示模式。下面介紹64K(16BPP)色顯示模式下,圖像資料的存儲格式。

64K(16BPP)色的顯示模式就是使用16位的資料來表示一個像素的顔色。這16位資料的格式又分為兩種:5:6:5、5:5:5:1,前者使用高5位來表示紅色,中間的6位來表示綠色,低5位來表示藍色;後者的高15從高到低分成3個5位來表示紅、綠、藍色,最低位來表示透明度。5:5:5:1的格式也被稱為RGBA格式(A:Alpha,表示透明度)。

一個4位元組可以表示兩個16BPP的像素,使用高2位元組還是低2位元組來表示第一個像素,這也是可以選擇的。顯示模式為16BPP時,記憶體資料與像素位置的關系如下:

  1. 當BSWP=0、HWSWP=0時,記憶體中像素的排列格式:
位址 D[31:16] D[15:0]
00H P1 P2
04H P3 P4
08H P5 P6
  1. 當BSWP=0、HWSWP=1時,記憶體中像素的排列格式:
位址 D[31:16] D[15:0]
00H P2 P1
04H P4 P3
08H P6 P5
  1. 像素在LCD屏上的排列

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-Otx47F98-1627057236198)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718171819.png)]

  1. 像素色值與VD[23:0]引腳的對應關系
VD 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
RED 4 3 2 1 NC NC NC NC NC NC NC NC
GREEN 5 4 3 2 1
BLUE 4 3 2 1
  • 輸出方式
    • 1、通過frame buffer顯示(最典型的方式)

    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-dgq5xefR-1627057236200)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210719224717.png)]

    ​ 上圖中可以看到,我們需要在記憶體裡面申請一塊記憶體(此記憶體被稱為frame buffer),之後各種配置LCD控制器,配置顯示模式為16PP, 顯示模式為5:6:5, 把frame buffer的首位址告訴控制器, 那麼控制器就會從frame buffer擷取像素值,根據像素的不同值将不同顔色打向LCD螢幕(LCD控制器類似于電子槍, 向玻璃闆發不同的光,LCD控制器内部有個DMA通道)對于frameBuffer來講,每個值對應LCD螢幕的一個像素,如上圖,LCD屏分辨率為(480*272),我們可以定義一個數組a[272][480]大小的數組,并把數組首位址告訴LCD控制器, 那麼數組每一項對應LCD屏的一個像素, 比如a[0][0]指派為0xFFE0,對應LCD螢幕的第一個像素顯示為黃色 。

    • 2、通過臨時調色闆顯示

    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-cGVYqI9O-1627057236202)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210719224718.png)]

    這裡要解釋幾點:

    1. 什麼叫臨時調色闆

      我們根據2440 使用者手冊的一句話:this register value will be video data at next frame.(在下一幀顯示寄存器的值, tips:一幀就一個圖像)

      可知:臨時調色闆是一個寄存器,我們向此寄存器寫入顔色,那麼LCD螢幕下一次顯示圖像就會是此寄存器中記錄的顔色, 即起到了刷屏的作用(整個螢幕都是一個顔色)

    2. 什麼時候起作用以及用途

      當使能此寄存器時, 臨時調色闆起作用, 這時之前配置的功能(如:通過frame buffer顯示)就會無效, 因為使能,LCD螢幕會被迅速刷屏,達到了快速刷屏的目的(不需要通過SDRAM中向frameBuffer中所有元素指派同一個值來實作刷屏 tips:使用SDRAM,本來就慢),如果要恢複之前的配置功能, 即disable臨時調色闆功能即可

    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-PzDcD2Fo-1627057236203)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210719224719.png)]
    • 3、通過調色闆顯示

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-ocWH6M5q-1627057236205)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210719224720.png)]

上圖中, 調色闆在控制器内部(注意差別臨時調色闆)是一塊兒記憶體,首位址為0x4D00400, 一共有256個2位元組大小(每兩個位元組表示一個顔色), 上圖中,通過配置寄存器告訴LCD控制器調色闆的顯示格式為RGB565,之後需要手動将此調色闆指派,比如圖00H的位置指派為”黃色”,之後對于framebuffer來講,其中的每一項代表一個調色闆中的索引, 比如frameBuffer的第一項的值為0,則硬體就會自動找調色闆中的第一項值, 即将0xFFE0輸出, LCD第一個像素點顯示黃色

還有一個問題,如何使能調色闆功能呢? 我們上面介紹”通過frame buffer顯示”中提到,配置寄存器顯示模式為16BPP,顯示方式是5:6:5,那麼控制器就會認為frame buffer中的每一個元素代表的就是顔色的值,并且顯示方式是5:6:5, 但是如果我們配置顯示模式為8BPP,顯示方式是5:6:5, LCD控制器就自動認為用的調色闆模式,且調色闆中顔色的顯示方式為(5:6:5)(這裡的8Bpp,代表frame buffer中的每個元素都是8位2進制表示,每個元素的值是調色闆中的索引值),那麼調色闆的應用場合是什麼樣呢?我們引用網上的一個說明(稍微修改下):

在筆者開發LCD,其顯示分辨率設定為640×480。如果色深設定為16 bpp,在系統使用時,畫面将會出現明顯的抖動、不連貫,這是由于晶片的運算負荷過重造成的。如果按本文中提到的調色闆方法,即采用8位色深顯示,顔色的選取可以滿足需要,畫面的顯示将明顯穩定。這說明,在顯示分辨率較高,色彩種類要求比較簡單的嵌入式應用中,調色闆技術是一個非常值得重視的選擇
  • TFT-LCD的時序

每個VSYNC信号表示一幀資料的開始;每個HSYNC信号表示一行資料的開始,無論這些資料是否有效;每個VCLK信号表示正在傳輸一個像素的資料,無論它是否有效。資料是否有效隻是對CPU的LCD控制器來說的,LCD根據VSYNC、HSYNC、VCLK不停的讀取總線資料并顯示。

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-bxsJJKq6-1627057236206)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210719224721.png)]

上圖中的時序圖,分為兩個部分,上面部分是一幀的時序圖,下面部分是一行的時序圖我們分析下時序圖中每個參數的意義(上圖中的①->⑩) :

  1. 對應于上述的過程1,2, VSYNC信号(代表一幀的開始)需要持續一段時間②(VSPW+1), 電子槍認為收到了VSYNC信号(即白掃射了VSPW+1行,也可以說白掃射了(VSPW+1)個HSYNC周期時間),收到信号後,還要繼續持續時間③(VBPD+1), LCD控制器才開始發送有效資料, 進而電子槍發射有效像素, 即(② + ③)為LCD螢幕上邊的無效區, 對于①參數, 這是手冊上的資料, 即告我們預設LCD控制器發送HSYNC信号為高電平,但實際LCD接受HSYNC硬體上有可能設計成低電平有效, 是以可以對寄存器進行修改, 讓LCD控制器發出HSYNC控制信号為低電平
    tips: VSPW VBPD參數會根據datasheet來具體設定(下文會提到), 設定這些參數的目的是告訴LCD控制器電子槍的反應時間以便發送幀資料(比如電子槍, 發送HSYNC後, 得知道電子槍的反應時間後才開始傳送有效資料)
  2. ④為, 即有效資料為(LINEVAL+1)行,我們分辨率為480*272,是以LINEVAL為271 *
  3. ⑤VFPD+1參數對應于過程5, 當掃描到最後一行結束時(即一幀結束了),LCD控制器不會再發送有效像素資料, 此時電子槍會收遊離一段時間(會繼續往下白掃好幾行(VFPD+1行)無效資料), 這個時間需要告訴LCD控制器,以便控制器知道等待多長時間在發送VSYNC信号,進而進行下一幀的開始
  4. 對于⑥、⑦、⑧、⑩三個參數,對應于上述過程3, 接受到HSYNC信号(表示一行的開始)後,此信号必須持續一段時間⑦(HSPW+1個VCLK周期)後, 電子槍才認為信号有效,接受到HSYNC信号後,電子槍還要反應一段時間⑧(白白掃射HBPD+1個VCLK周期後,也可以說發射HBPD+1個無效像素點)後, LCD控制器才開始傳送有效資料給電子槍, 當一行掃描結束後,即LCD控制器不發射有效資料了,此時電子槍要遊離一段時間⑩(HFPB+1), 這段時間需要告訴LCD控制器,以便讓LCD控制器等待此段時間後在發送HSYNC信号進而進行下一行的掃描, 對于⑨參數來說, 分辨率為480*272,是以HOZVAL = 479, 即一行有480個有效資料, 注意有效資料的開始時機, 即需要經曆(⑦、⑧)時間後,LCD控制器才開始發送有效資料 。

參數計算

根據LCDdatasheet确認上述參數的值, 下圖為AT043TN24資料手冊的時序圖, 我們很容易對應上面2440手冊中LCD的時序圖中的參數

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-Lu4ncPxY-1627057236208)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210719224722.png)]

上圖中已經标注對應關系,就不細說了,強調一點, VSYNC與HSYNC信号都是低電平有效,但是2440手冊中LCD時序是高電平有效,是以在配置寄存器時需要注意,要将這兩個VSYNC,HSYNC信号設定成低電平有效(極性反轉: 預設為高電平,反轉後為低電平)

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-nnY2MfPC-1627057236210)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210719224723.png)]

我們可以看到,上圖中左邊是具體的參數值,Min(最小值), Typ.(典型值), Max(最大值),我們舉個例子,在右圖中,我們知道關系 VSPW+1 = tvp, 我們在左圖中發現tvp的典型值為10, 機關是H(Hsync), 是以VSPW+1 = 10==> VSPW=9, 其餘參數的取值都能通過上述方法确定, 還有個問題,VSPW, VSPD,VFBD的時間都依賴于HSYNC周期時間,那麼HSYNC周期時間如何确認呢? 檢視了下寄存器的設定中好像也沒找到相關設定,最後在2440手冊中找到這句話

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-kixJcdwS-1627057236211)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210719224724.png)]

其實意思就是說 LCD控制器會根據電子槍發射像素點的個數來确認HSYNC時間的,比如我們LCD螢幕分辨率是480*272, 當發出VSYNC信号後,要經過VSPW+1反應時間,即VSPW+1個HSYNC周期,我們假設VSPW+1的值為10,那麼就是10個HSYNC周期,也就是電子槍掃描了10 x 480個像素點後,LCD控制器就認為經曆了10個HSYNC周期時間 。

1、VCLK(Hz) = HCLK/[(CLKVAL+1)*2]

2、VSYNC =1/[ {(VSPW+1)+(VBPD+1)+(LIINEVAL+1)+(VFPD+1)} x {(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)} x {2x (CLKVAL+1) / (HCLK )} ]

3、HSYNC = 1/[{(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)} x {2x (CLKVAL+1) / (HCLK )}]

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-xY9izPmt-1627057236212)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718175250.png)]

将VSYNC、HSYNC、VCLK等信号的時間參數設定好之後,并将幀記憶體的位址告訴LCD控制器,它即可自動地發出DMA傳輸從幀記憶體中得到圖像資料,最終在上述信号的控制下出現在資料總線VD[23:0]上。使用者隻需要把要顯示的圖像資料寫入幀記憶體中。

2、LCD控制器REGBANK寄存器組介紹

LCD控制器中REGBANK的17個寄存器可以分為6種,如下表所示:

對于TFT-LCD,一般情況下隻需要設定前兩種寄存器,即LCDCON和LCDSADDR。

名稱 說明
LCDCON1~LCDCON5 用于選擇LCD類型,設定各類控制信号的時間特性等
LCDSADDR1~LCDSADDR5 用于設定幀記憶體的位址
TPAL 臨時調色闆寄存器,可以快速的輸出一幀單色的圖像
LCDINTPND 用于LCD的中斷,在一般應用中無需中斷
LCDSRCPND 用于LCD的中斷,在一般應用中無需中斷
LCDINTMSK 用于LCD的中斷,在一般應用中無需中斷
REDLUT 專用于STN-LCD
GREENLUT 專用于STN-LCD
BLUELUT 專用于STN-LCD
DITHMODE 專用于STN-LCD
TCONSEL 專用于SEC TFT-LCD

2.1、LCD控制寄存器LCDCON1

主要用于選擇LCD類型、設定像素時鐘、使能LCD信号的輸出等,格式如下表所示:

功能 說明
LINECNT [27:18] 隻讀,每輸出一個有效行其值減一,從LINEVAL減到0;
CLKVAL [17:8] 用于設定VCLK(像素時鐘);
MMODE [7] 設定VM信号的反轉效率,專用于STN-LCD;
PNRMODE [6:5] 設定LCD類型,對于TFT-LCD設定0b11;
BPPMODE [4:1] 設定BPP,對于TFT-LCD:0b1100 = 16BPP;
ENVID [0] LCD信号輸出使能位,0:禁止,1:使能;

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-VXIKcMGb-1627057236213)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718173545.png)]

2.2、LCD控制寄存器LCDCON2

用于設定垂直方向各信号的時間參數,格式如下表所示:

功能 說明
VBPD [31:24] VSYNC信号脈沖之後,還要經過(VBPD+1)個HSYNC信号周期,有效的行資料才出現;
LINEVAL [23:14] LCD的垂直寬度,(LINEVAL+1)行;
VFPD [13:6] 一幀中的有效資料完結後,到下一個VSYNC信号有效前的無效行數目:VFPD+1行;
VSPW [5:0] 表示VSYNC信号的脈沖寬度位(VSPW+1)個HSYNC信号周期,即(VSPW+1)行,這個(VSPW+1)行的資料是無效的;

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-rXtdMdCZ-1627057236214)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718173636.png)]

2.3、LCD控制寄存器LCDCON3

用于設定水準方向各信号的時間參數,格式如下表所示:

功能 說明
HBPD [25:19] HSYNC信号脈沖之後,還要經過(HBPD+1)個VCLK信号周期,有效的像素資料才出現;
HOZVAL [18:8] LCD的水準寬度,(HOZVAL+1)類(像素);
HFPD [7:0] 一行中的有效資料完結後,到下一個HSYNC信号有效前的無效像素個數,HFPD+1個像素;

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-trwr1GvB-1627057236216)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718173756.png)]

2.4、LCD控制寄存器LCDCON4

對于TFT-LCD,這個寄存器隻用來設定HSYNC信号的脈沖寬度,位[7:0]的數值稱為HSPW,表示脈沖寬度位(HSPW+1)個VCLK周期。

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-LJdlSJXk-1627057236217)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718173853.png)]

2.5、LCD控制寄存器LCDCON5

用于設定各個控制信号的極性,并可從中讀到一些狀态資訊,格式如下表所示:

功能 說明
VSTATUS [16:15] 隻讀,垂直狀态;00:正處于VSYNC信号脈沖期間;01:正處于VSYNC信号結束到行有效之間;10:正處于有效行期間;11:正處于行有效結束到下一個VSYNC信号之間;
HSTATUS [14:13] 隻讀,水準狀态;00:正處于HSYNC信号脈沖期間;01:正處于HSYNC信号結束到像素有效之間;01:正處于像素有效期間;11:正處于像素有效結束到下一個HSYNC信号之間;
BPP24BL [12] 設定TFT-LCD的顯示模式為24BPP時,一個4位元組中的哪3個位元組有效,0:LSB有效,1:MSB有效(高位址的3個位元組);
FRM565 [11] 設定TFT-LCD的顯示模式為16BPP時,使用的資料格式,0表示5:5:5:1格式,1表示5:6:5格式;
INVVCLK [10] 設定VCLK信号有效沿極性:0表示在VCLK的下降沿讀取資料;1表示在VCLK的上升沿讀取資料;
INVVLINE [9] 設定VINE/HSYNC脈沖的極性;0表示正常極性,1表示反轉的極性;
INVVFRAME [8] 設定VFRAME/VSYNC脈沖的極性;0表示正常極性,1表示反轉的極性;
INVVD [7] 設定VD資料線表示資料的極性;0表示正常極性,1表示反轉的極性;
INVVDEN [6] 設定VDEN信号的極性;0表示正常進行,1表示反轉的極性;
INVPWREN [5] 設定PWREN信号的極性;0表示正常進行,1表示反轉的極性;
INVLEND [4] 設定LEND信号的極性;0表示正常進行,1表示反轉的極性;
PWREN [3] LCD_PWREN信号輸出使能;0表示禁止,1表示使能;
ENLEND [2] LEND信号輸出使能;0表示禁止,1表示使能;
BSWP [1] 位元組交換使能;0表示禁止,1表示使能;
HWSWP [0] 半字(2位元組)交換使能,0表示禁止,1表示使能;

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-icBZD9sA-1627057236218)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718174055.png)]

2.6、幀記憶體位址寄存器LCDSDRR1~LCDSDRR3

幀記憶體可以很大,而真正要顯示的區域被稱為視口(view point),它處于幀記憶體之内,這個3個寄存器用于确定幀記憶體的起始位址,定位視口在幀記憶體中的位置。

下圖給出了幀記憶體和視口的位置關系:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-UsEom5WP-1627057236219)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718174314.png)]

下面分别介紹各個幀記憶體寄存器;

  • LCDSADRR1寄存器格式
功能 說明
LCDBANK [29:21] 用于儲存幀記憶體起始位址A[30:22],幀記憶體起始位址必須為4MB對齊;
LCDBASEU [20:0] 對于TFT-LCD,用于儲存視口所對應的記憶體起始位址A[21:1],這塊記憶體也被稱為LCD的幀緩沖區(frame buffer);

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-W75PHeTa-1627057236220)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718174853.png)]

  • LCDSADRR2寄存器格式
功能 說明
LCDBASEL [20:0] 對于TFT-LCD,用來儲存LCD的幀緩沖區結束位址A[21:1],其值可如下計算:LCDBASEL=LCDBASEU+(PAGEWIDTH+OFFSIZE)*(LINEVAL+1)

注意:可以修改LCDBASEU、LCDBASEL的值來實作圖像的移動,不過不能在一幀圖像的結束階段進行修改;

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-XZ4cLOie-1627057236221)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718174924.png)]

  • LCDSADRR3寄存器格式
功能 說明
OFFSIZE [21:11] 表示上一行最後一個資料與下一行第一個資料之間位址內插補點的半位元組,即以半字位機關的位址差;0表示兩行資料是緊接着的,1表示它們之間相差2個位元組,以此類推;
PAGEWIDTH [10:0] 視口的寬度,以半字位為機關;

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-nkeP9nK2-1627057236222)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718175007.png)]

3、調色闆

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-U3EZHNSi-1627057236223)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718175642.png)]

  • 臨時調色闆寄存器TPAL

如果要輸出一幀單色的圖像,可以在TPAL寄存器中設定這個顔色值,然後使能TPAL寄存器,這種方法可以避免修改整個調色闆或幀緩沖區。

TPAL寄存器格式:

功能 說明
TPALEN [24] 調色闆寄存器使能位,0禁止,1使能;
TPALVAL [23:0] 顔色值;TPALVAL[23:16]:紅色TPALVAL[15:8]:綠色TPALVAL[7:0]:藍色

注意:臨時調色闆寄存器TPAL可以用在任何顯示模式下,并非隻能用在8BPP模式下。

4、編寫驅動

4.1 lcd.c

  • 搭建整體架構
可參考核心自帶的相關lcd驅動(drivers/video/),添加頭檔案:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>

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

#include <asm/mach/map.h>
#include <mach/regs-lcd.h>
#include <mach/regs-gpio.h>
#include <mach/fb.h>

static struct fb_info *s3c_lcd;
static int lcd_init(void)
{
	/* 1. 配置設定一個fb_info */
	s3c_lcd = framebuffer_alloc(0, NULL);

	/* 2. 設定 */
    	/* 2.1 設定固定參數 */
    	/* 2.2 設定可變參數 */
    	/* 2.3 設定操作函數 */
    	/* 2.4 設定其它内容 */
    
    /* 3. 硬體相關的操作 */
    	/* 3.1 配置GPIO用于LCD */
    	/* 3.2 根據LCD手冊設定LCD控制器,例如VCLK頻率等 */
    	/* 3.3 配置設定顯存(frambuffer),并将位址告訴LCD控制器 */
    
    /* 4. 注冊 */
	register_framebuffer(s3c_lcd);
	
	return 0;
}

static void lcd_exit(void)
{
    
}

module_init(lcd_init);
module_exit(lcd_exit);

MODULE_LICENSE("GPL");
           
  • 入口函數lcd_init()
    • 1、配置設定一個fb_info
    • 2、 設定
    1. 設定固定參數——fb_fix_screeninfo結構體
    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-9EoDHYB7-1627057236224)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718213023.png)]
    /* 2.1 設定固定的參數 */
    strcpy(s3c_lcd->fix.id, "mylcd");
    s3c_lcd->fix.smem_len = 480*272*16/8;	//螢幕分辨率480X272,16bpp/pix
    s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;	//螢幕類型
    s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR; /* 真彩TFT */
    s3c_lcd->fix.line_length = 480*2;	//一行需要的存儲長度=480像素X2位元組
               
    1. 設定可變參數
    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-ZpiGw0hb-1627057236225)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718222829.png)]
    /* 2.2 設定可變參數 */
    s3c_lcd->var.xres           = 480;	//X方向的分辨率
    s3c_lcd->var.yres           = 272;	//y方向的分辨率
    s3c_lcd->var.xres_virtual   = 480;	//X方向虛拟的分辨率
    s3c_lcd->var.yres_virtual   = 272;	//y方向的虛拟分辨率
    s3c_lcd->var.bits_per_pixel = 16;	//每個像素用多少位表示
    
    /* RGB:565 */
    s3c_lcd->var.red.offset     = 11;	//從第11位開始
    s3c_lcd->var.red.length     = 5;	//占5個位
    s3c_lcd->var.red.msb_right  = 0;	//資料在offset的右邊嗎?預設為0,表示在左邊(高位方向)。可以不需設定
    
    s3c_lcd->var.green.offset   = 5;	//從第5位開始
    s3c_lcd->var.green.length   = 6;
    
    s3c_lcd->var.blue.offset    = 0;	//從第0位開始
    s3c_lcd->var.blue.length    = 5;
    
    s3c_lcd->var.activate       = FB_ACTIVATE_NOW;	//不明白,暫用預設值
               
    1. 設定操作函數——fbops
    • 在函數外定義fb_ops結構體:
    static struct fb_ops s3c_lcdfb_ops = {
    	.owner			= THIS_MODULE,
    	.fb_setcolreg	= s3c_lcdfb_setcolreg,	//調色闆設定函數
    	.fb_fillrect	= cfb_fillrect,
    	.fb_copyarea	= cfb_copyarea,
    	.fb_imageblit	= cfb_imageblit,
    };
               
    1. 其它的設定
    s3c_lcd->pseudo_palette = pseudo_palette;	//調色闆數組位址
    s3c_lcd->screen_base  = ;  /* 顯存的虛拟位址 */ 
    s3c_lcd->screen_size   = 480*272*16/8;
               
    • 3、硬體相關的操作
    1. 配置GPIO用于LCD

    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-F0Ec5lFa-1627057236226)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718230408.png)]

    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-KDUxqaPJ-1627057236227)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718231335.png)]

    通過原理圖可知,所有使用到的引腳均要配置。然後檢視原理圖,找到各引腳對應的IO端口:

    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-mAmQKkZS-1627057236228)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718230823.png)]

    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-aVhJSvWB-1627057236230)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210718231849.png)]

    首先在函數外定義用到的IO口的寄存器指針變量:

    static volatile unsigned long *gpbcon;
    static volatile unsigned long *gpbdat;
    static volatile unsigned long *gpccon;
    static volatile unsigned long *gpdcon;
    static volatile unsigned long *gpgcon;
               
    然後在函數體内映射位址:
    /*配置GPIO用于LCD*/
    //即使你寫了僅映射4個位元組,系統也還是會映射至少1頁(4KB)
    gpbcon = ioremap(0x56000010, 8);
    gpbdat = gpbcon+1;
    gpccon = ioremap(0x56000020, 4);	
    gpdcon = ioremap(0x56000030, 4);
    gpgcon = ioremap(0x56000060, 4);
    
    *gpccon  = 0xaaaaaaaa;   /* GPIO管腳用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
    *gpdcon  = 0xaaaaaaaa;   /* GPIO管腳用于VD[23:8] */
    
    /* GPB0設定為輸出引腳 */
    *gpbcon &= ~(3);  
    *gpbcon |= 1;
    *gpbdat &= ~1;     /* 先輸出低電平,使背光電源關閉 */
    
    *gpgcon |= (3<<8); /* GPG4用作LCD_PWREN(LCD本身電源) */
               
    1. 根據LCD手冊設定LCD控制器,例如VCLK頻率等
      **首先**,檢視S3C2440晶片手冊,并設定LCD controller章節中的控制寄存器。為了友善引用,先定義一個全局結構體(lcd_regs),内容就是各寄存器的位址。即:
                 

struct lcd_regs {

unsigned long lcdcon1;

unsigned long lcdcon2;

unsigned long lcdcon3;

unsigned long lcdcon4;

unsigned long lcdcon5;

unsigned long lcdsaddr1;

unsigned long lcdsaddr2;

unsigned long lcdsaddr3;

unsigned long redlut;

unsigned long greenlut;

unsigned long bluelut;

unsigned long reserved[9];

unsigned long dithmode;

unsigned long tpal;

unsigned long lcdintpnd;

unsigned long lcdsrcpnd;

unsigned long lcdintmsk;

unsigned long lpcsel;

};

​				**然後**,再定義一個指向該類型結構體的指針`lcd_regs`:

```c
static volatile struct lcd_regs* lcd_regs;	//所有指向寄存器的位址必須是volatile修飾的
           

​ 最後,到

lcd.c

函數中進行位址映射,而後根據LCD資料手冊設定LCD控制寄存器:

為了便于大家檢視,将這3幅圖重新放在這邊:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-ksNUdcvq-1627057236231)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210719214801.png)]

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-et2piAqd-1627057236232)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210719214922.png)]

lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
	//1.設定LCDCON1寄存器
	/* CLKVAL 	=>	bit[17:8]: 	VCLK = HCLK / [(CLKVAL+1) x 2],
     *							VCLK取值檢視LCD手冊3.5.1節的Clock cycle
	 *            				9MHz(Typ) = 100MHz / [(CLKVAL+1) x 2]
	 *            				CLKVAL = 4(在此取整數4)
	 * MMODE  	=>	bit[7]:取預設值
	 * PRNMODE	=>	bit[6:5]: 0b11	(TFT LCD panel)
	 * BPPmode	=>	bit[4:1]: 0b1100(16 bpp for TFT)
	 * ENVID  	=>	bit[0]  : 0b0	(先暫時禁止,需要時打開.)
	 */
	lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);

	//2.設定LCDCON2-4寄存器
#if 1
	/* 垂直方向的時間參數
	 * VBPD		=>	bit[31:24]: 1, VSYNC之後再過多長時間才能發出第1行資料
	 * LINEVAL	=>	bit[23:14]: 271, 是以LINEVAL=272-1=271
	 * VFPD		=>	bit[13:6] : 1, 發出最後一行資料之後,再過多長時間才發出VSYNC,是以VFPD=2-1=1
	 * VSPW		=>	bit[5:0]  : 9, VSYNC信号的脈沖寬度,VSPW=10-1=9
	 */
	lcd_regs->lcdcon2  = (1<<24) | (271<<14) | (1<<6) | (9);
	
	/* 水準方向的時間參數
	 * HBPD		=>	bit[25:19]: 1, VSYNC之後再過多長時間才能發出第1行數,HBPD=2-1
	 * HOZVAL	=>	bit[18:8]: 479, HOZVAL=480-1=479
	 * HFPD		=>	bit[7:0] : 1 , 發出最後一行裡最後一個象素資料之後,再過多長時間才發出HSYNC,HFPD=2-1=1
	 */
	lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1);

	/* 水準方向的同步信号
	 * HSPW	=>	bit[7:0]: 40, HSYNC信号的脈沖寬度, HSPW=41-1=40
	 */	
	lcd_regs->lcdcon4 = 40;

#else
	lcd_regs->lcdcon2 =	S3C2410_LCDCON2_VBPD(5) | \
		S3C2410_LCDCON2_LINEVAL(319) | \
		S3C2410_LCDCON2_VFPD(3) | \
		S3C2410_LCDCON2_VSPW(1);

	lcd_regs->lcdcon3 =	S3C2410_LCDCON3_HBPD(10) | \
		S3C2410_LCDCON3_HOZVAL(239) | \
		S3C2410_LCDCON3_HFPD(1);

	lcd_regs->lcdcon4 =	S3C2410_LCDCON4_MVAL(13) | \
		S3C2410_LCDCON4_HSPW(0);
#endif

/* 信号的極性 
	 * bit[11]: 1=565 format
	 * bit[10]: 0 = 根據LCD手冊,其在下降沿取資料
	 * bit[9] : 1 = HSYNC信号要反轉(對比S3C2440手冊與LCD手冊的時序圖)
	 * bit[8] : 1 = VSYNC信号要反轉,
	 * bit[7] : 0 = INVVD不用反轉(資料引腳低電平表示資料1)
	 * bit[6] : 0 = VDEN不用反轉(對比S3C2440手冊與LCD手冊的時序圖)
	 * bit[5] : 0 = INVPWREN不用反轉(電源使能開關高電平有效)
	 * bit[3] : 0 = PWREN信号輸出使能(暫時先不使能它,到後面設定完後再打開)
	 * bit[1:0] : 01,記憶體資料和像素點對應關系,00表示D[31:0]的高8位對應Pix1,
	 *  			低8位對應Pix2,01表示D[31:0]的高8位對應Pix2,低8位對應Pix1
	 *				S3C2440手冊第413頁
	 */
	lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
           
  1. 配置設定顯存(frambuffer),并将位址告訴LCD控制器 ,最後啟動LCD
/* screen_base:顯存的虛拟位址;smem_start:顯存的實體位址。*/
s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);

	/* Frame Buffer 的起始位址
	 * LCDBANK	=>	bit[29:21]: ,對應視訊緩存區開始位址的A[30:22]位,(4MB位址對齊)
     * LCDBASEU	=>	bit[20:0]: ,對應視訊緩存區開始位址的A[21:1]位,
	 */
	lcd_regs->lcdsaddr1  = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
	
	/* Frame Buffer 的結束位址
	 * LCDBASEL	=>	bit[20:0]: ,對應視訊緩存區結束位址的A[21:1]位,
	 */
	lcd_regs->lcdsaddr2  = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;

	/* Frame Buffer 的有效顯示區的寬度(半字,即2位元組為機關)
	 * OFFSIZE	 =>	bit[21:11]: ,不懂,取預設值
	 * PAGEWIDTH =>	bit[10:0]: ,一行的長度(機關: 2位元組) 
	 */
	lcd_regs->lcdsaddr3  = (480*16/16);  
	
	/* 啟動LCD */
	lcd_regs->lcdcon1 |= (1<<0); /* 使能ENVID信号,表示傳輸資料 */
	lcd_regs->lcdcon5 |= (1<<3); /* 使能PWREN信号 */
	*gpbdat |= 1;     			 /* 輸出高電平, 使能背光 */	
           
  • 4、注冊
/* 4. 注冊 */
register_framebuffer(s3c_lcd);
           
  • 出口函數lcd_exit()
static void lcd_exit(void)
{
	unregister_framebuffer(s3c_lcd);	//登出fb
	lcd_regs->lcdcon1 &= ~(1<<0); /* 停止向LCD發送資料 */
    lcd_regs->lcdcon5 &= ~(1<<3); /* 關閉PWREN信号 */
	*gpbdat &= ~1;     /* 關閉背光 */
    //dma_free_writecombine(裝置,記憶體長度,虛拟起始位址,起始實體位址)
	dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
    
	iounmap(lcd_regs);
	iounmap(gpbcon);
	iounmap(gpccon);
	iounmap(gpdcon);
	iounmap(gpgcon);
    
	framebuffer_release(s3c_lcd);	//釋放fb
}
           
  • 調色闆相關函數設定
  1. 為了相容性,我們要先定義一個僞調色闆數組,
  1. 設定顔色填充函數
/* from pxafb.c */
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length;
	return chan << bf->offset;
}
           
  1. 設定調色闆
static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
			     unsigned int green, unsigned int blue,
			     unsigned int transp, struct fb_info *info)
{
	unsigned int val;
	
	if (regno > 16)
		return 1;

	/* 用red,green,blue三原色構造出val */
	val  = chan_to_field(red,	&info->var.red);
	val |= chan_to_field(green, &info->var.green);
	val |= chan_to_field(blue,	&info->var.blue);
	
	//((u32 *)(info->pseudo_palette))[regno] = val;
	pseudo_palette[regno] = val;
	return 0;
}
           
  • lcd.c檔案的整體代碼結構
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>

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

#include <asm/mach/map.h>
#include <mach/regs-lcd.h>
#include <mach/regs-gpio.h>
#include <mach/fb.h>

static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
			     unsigned int green, unsigned int blue,
			     unsigned int transp, struct fb_info *info);


struct lcd_regs {
	unsigned long	lcdcon1;
	unsigned long	lcdcon2;
	unsigned long	lcdcon3;
	unsigned long	lcdcon4;
	unsigned long	lcdcon5;
    unsigned long	lcdsaddr1;
    unsigned long	lcdsaddr2;
    unsigned long	lcdsaddr3;
    unsigned long	redlut;
    unsigned long	greenlut;
    unsigned long	bluelut;
    unsigned long	reserved[9];
    unsigned long	dithmode;
    unsigned long	tpal;
    unsigned long	lcdintpnd;
    unsigned long	lcdsrcpnd;
    unsigned long	lcdintmsk;
    unsigned long	lpcsel;
};

static struct fb_ops s3c_lcdfb_ops = {
	.owner		= THIS_MODULE,
	.fb_setcolreg	= s3c_lcdfb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};


static struct fb_info *s3c_lcd;
static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile struct lcd_regs* lcd_regs;
static u32 pseudo_palette[16];


/* from pxafb.c */
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length;
	return chan << bf->offset;
}


static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
			     unsigned int green, unsigned int blue,
			     unsigned int transp, struct fb_info *info)
{
	unsigned int val;
	
	if (regno > 16)
		return 1;

	/* 用red,green,blue三原色構造出val */
	val  = chan_to_field(red,	&info->var.red);
	val |= chan_to_field(green, &info->var.green);
	val |= chan_to_field(blue,	&info->var.blue);
	
	//((u32 *)(info->pseudo_palette))[regno] = val;
	pseudo_palette[regno] = val;
	return 0;
}

static int lcd_init(void)
{
	/* 1. 配置設定一個fb_info */
	s3c_lcd = framebuffer_alloc(0, NULL);

	/* 2. 設定 */
	/* 2.1 設定固定的參數 */
	strcpy(s3c_lcd->fix.id, "mylcd");
	s3c_lcd->fix.smem_len = 480*272*16/8;
	s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;
	s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR; /* TFT */
	s3c_lcd->fix.line_length = 480*2;
	
	/* 2.2 設定可變的參數 */
	s3c_lcd->var.xres           = 480;
	s3c_lcd->var.yres           = 272;
	s3c_lcd->var.xres_virtual   = 480;
	s3c_lcd->var.yres_virtual   = 272;
	s3c_lcd->var.bits_per_pixel = 16;

	/* RGB:565 */
	s3c_lcd->var.red.offset     = 11;
	s3c_lcd->var.red.length     = 5;
	
	s3c_lcd->var.green.offset   = 5;
	s3c_lcd->var.green.length   = 6;

	s3c_lcd->var.blue.offset    = 0;
	s3c_lcd->var.blue.length    = 5;

	s3c_lcd->var.activate       = FB_ACTIVATE_NOW;
	
	
	/* 2.3 設定操作函數 */
	s3c_lcd->fbops              = &s3c_lcdfb_ops;
	
	/* 2.4 其他的設定 */
	s3c_lcd->pseudo_palette = pseudo_palette;
	//s3c_lcd->screen_base  = ;  /* 顯存的虛拟位址 */ 
	s3c_lcd->screen_size   = 480*272*16/8;

	/* 3. 硬體相關的操作 */
	/* 3.1 配置GPIO用于LCD */
	gpbcon = ioremap(0x56000010, 8);
	gpbdat = gpbcon+1;
	gpccon = ioremap(0x56000020, 4);
	gpdcon = ioremap(0x56000030, 4);
	gpgcon = ioremap(0x56000060, 4);

    *gpccon  = 0xaaaaaaaa;   /* GPIO管腳用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
	*gpdcon  = 0xaaaaaaaa;   /* GPIO管腳用于VD[23:8] */
	
	*gpbcon &= ~(3);  /* GPB0設定為輸出引腳 */
	*gpbcon |= 1;
	*gpbdat &= ~1;     /* 輸出低電平 */

	*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */
	
	/* 3.2 根據LCD手冊設定LCD控制器, 比如VCLK的頻率等 */
	lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));

	/* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手冊P14
	 *            10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
	 *            CLKVAL = 4
	 * bit[6:5]: 0b11, TFT LCD
	 * bit[4:1]: 0b1100, 16 bpp for TFT
	 * bit[0]  : 0 = Disable the video output and the LCD control signal.
	 */
	lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);

#if 1
	/* 垂直方向的時間參數
	 * bit[31:24]: VBPD, VSYNC之後再過多長時間才能發出第1行資料
	 *             LCD手冊 T0-T2-T1=4
	 *             VBPD=3
	 * bit[23:14]: 多少行, 320, 是以LINEVAL=320-1=319
	 * bit[13:6] : VFPD, 發出最後一行資料之後,再過多長時間才發出VSYNC
	 *             LCD手冊T2-T5=322-320=2, 是以VFPD=2-1=1
	 * bit[5:0]  : VSPW, VSYNC信号的脈沖寬度, LCD手冊T1=1, 是以VSPW=1-1=0
	 */
	lcd_regs->lcdcon2  = (1<<24) | (271<<14) | (1<<6) | (9);


	/* 水準方向的時間參數
	 * bit[25:19]: HBPD, VSYNC之後再過多長時間才能發出第1行資料
	 *             LCD手冊 T6-T7-T8=17
	 *             HBPD=16
	 * bit[18:8]: 多少列, 240, 是以HOZVAL=240-1=239
	 * bit[7:0] : HFPD, 發出最後一行裡最後一個象素資料之後,再過多長時間才發出HSYNC
	 *             LCD手冊T8-T11=251-240=11, 是以HFPD=11-1=10
	 */
	lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1);

	/* 水準方向的同步信号
	 * bit[7:0]	: HSPW, HSYNC信号的脈沖寬度, LCD手冊T7=5, 是以HSPW=5-1=4
	 */	
	lcd_regs->lcdcon4 = 40;

#else
lcd_regs->lcdcon2 =	S3C2410_LCDCON2_VBPD(5) | \
		S3C2410_LCDCON2_LINEVAL(319) | \
		S3C2410_LCDCON2_VFPD(3) | \
		S3C2410_LCDCON2_VSPW(1);

lcd_regs->lcdcon3 =	S3C2410_LCDCON3_HBPD(10) | \
		S3C2410_LCDCON3_HOZVAL(239) | \
		S3C2410_LCDCON3_HFPD(1);

lcd_regs->lcdcon4 =	S3C2410_LCDCON4_MVAL(13) | \
		S3C2410_LCDCON4_HSPW(0);

#endif
	/* 信号的極性 
	 * bit[11]: 1=565 format
	 * bit[10]: 0 = The video data is fetched at VCLK falling edge
	 * bit[9] : 1 = HSYNC信号要反轉,即低電平有效 
	 * bit[8] : 1 = VSYNC信号要反轉,即低電平有效 
	 * bit[6] : 0 = VDEN不用反轉
	 * bit[3] : 0 = PWREN輸出0
	 * bit[1] : 0 = BSWP
	 * bit[0] : 1 = HWSWP 2440手冊P413
	 */
	lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
	
	/* 3.3 配置設定顯存(framebuffer), 并把位址告訴LCD控制器 */
	s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
	
	lcd_regs->lcdsaddr1  = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
	lcd_regs->lcdsaddr2  = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
	lcd_regs->lcdsaddr3  = (480*16/16);  /* 一行的長度(機關: 2位元組) */	
	
	//s3c_lcd->fix.smem_start = xxx;  /* 顯存的實體位址 */
	/* 啟動LCD */
	lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
	lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
	*gpbdat |= 1;     /* 輸出高電平, 使能背光 */		

	/* 4. 注冊 */
	register_framebuffer(s3c_lcd);
	
	return 0;
}

static void lcd_exit(void)
{
	unregister_framebuffer(s3c_lcd);
	lcd_regs->lcdcon1 &= ~(1<<0); /* 關閉LCD本身 */
	lcd_regs->lcdcon5 &= ~(1<<3); /* 關閉PWREN信号 */
	*gpbdat &= ~1;     /* 關閉背光 */
	dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
	iounmap(lcd_regs);
	iounmap(gpbcon);
	iounmap(gpccon);
	iounmap(gpdcon);
	iounmap(gpgcon);
	framebuffer_release(s3c_lcd);
}

module_init(lcd_init);
module_exit(lcd_exit);

MODULE_LICENSE("GPL");
           

5、編譯和測試

  • 打開核心的圖形化配置界面
cd ~/linux3.4.2/
make menuconfig
           
  • 子產品化核心自帶的LCD驅動

依次進入Device Drivers——> Graphics support——>Support for frame buffer devices,去掉勾選,選擇子產品化[S3C2410 LCD framebuffer support]選項(因為在我們的驅動程式的fops中需要用到cfb_fillrect、cfb_copyarea、cfb_imageblit這3個函數,而它們位于核心的該子產品中)。

  • 重新編譯核心并安裝子產品
cd ~/linux3.4.2
make uImage
make modules
cd arch/arm/boot
mv uImage uImage_nolcd
cd ~/linux3.4.2
//複制新的核心到tftp共享檔案夾
cp arch/arm/boot/uImage_nolcd  /mnt/hghf/virtual_shared/tftp

//複制包含cfb_fillrect、cfb_copyarea、cfb_imageblit這3個函數的驅動子產品cfb*.ko到網絡檔案系統
cp drivers/video/cfb*.ko  ~/nfs_root/first_fs
           
  • 使用新核心啟動開發闆
//啟動開發闆時,按任意鍵進入uboot菜單
# q		//退出菜單,進入指令行

//将新編譯的核心複制到tftp共享檔案夾中馬克後執行下面語句
# tftp 30000000 uImage_nolcd	ec//将核心鏡像下載下傳到記憶體30000000處

//或者使用NFS服務下載下傳到記憶體的30000000處
# nfs 30000000 192.168.1.101:/home/leon/nfs_root/first_fs/uImage

# bootm 30000000	//啟動

//系統成功啟動後,挂接網絡檔案系統
# mount -nfs -o nolock 192.168.1.101:/home/leon/nfs_root/first_fs /mnt
           
  • 安裝驅動子產品cfb*.ko和lcd驅動

切換回開發闆序列槽終端,執行以下指令:

//安裝複制過來的三個子產品
# insmod cfbcopyarea.ko
# insmod cfbfillrect.ko
# insmod cfbimgblt.ko

//安裝LCD驅動
# insmod lcd.ko

//檢查是否安裝成功
# ls /dev/fb*
/dev/fb0		//發現裝置檔案裡出現了新安裝的fb0,開發闆螢幕也亮了起來
           
  • 測試lcd驅動(3種方法)
    • 随便向驅動檔案寫入資料,觀察LCD螢幕是否有花屏
    # cat anyfile > /dev/fb0 
               
    • 或者向螢幕輸出字元串,觀察LCD螢幕是否有字元串輸出
    # echo "hello darkbird!" > /dev/tty1
               
    • 修改/etc/inittab檔案,添加tty1後,重新開機新核心,重新安裝各子產品後再測試
    tty1:輸入時對應開發闆鍵盤,輸出時對應我們的LCD,其對應的驅動檔案是之前寫過的buttons.ko
    tty1::askfirst:-/bin/sh
               
    # mount -nfs -o nolock 192.168.1.101:/home/leon/nfs_root/first_fs /mnt
    # cd /mnt
    # insmod cfbcopyarea.ko
    # insmod cfbfillrect.ko
    # insmod cfbimgblt.ko
    # insmod lcd.ko
    # insmod buttons.ko
    
    而後按下開發闆上的按鍵,LCD頻幕上會顯示進入指令行,
    之後,按下ls+回車對應的3個按鍵,螢幕上顯示`ls`指令執行結果。
               

6、知識拓展——修改核心自帶lcd驅動,使之支援4.3寸(480X272)屏

6.1 linux核心驅動架構分析及修改

lcd顯示屏使用的是linux核心中标準的幀緩沖子系統。幀緩沖(FrameBuffer)是Linux為顯示裝置提供的一個接口,使用者可以将幀緩沖看成是顯示記憶體的一種映像,将其映射到程序位址空間之後,就可以直接進行讀寫操作,而寫操作可以立即反映到螢幕上,這種操作是抽象和統一的,使用者不必關心顯存的位置、換頁機制等具體細節,這些都是由FrameBuffer裝置驅動來實作,幀緩沖把顯示裝置描述成一個緩沖區,允許應用程式通過幀緩沖定義好的接口通路這些圖形裝置,進而不用關心具體的硬體細節。

LCD驅動使用幀緩沖子系統涉及三個檔案:

fbmem.c

:實作幀緩沖的具體細節,隻是一個抽象層,對上提供操作函數接口,對下提供硬體操作函數。

s3c2410fb.c

:用于初始化一個lcd硬體裝置的,内含硬體操作的具體細節。

mach-smdk2440.c

:提供了LCD顯示屏的配置資訊,例如長,寬,像素位數,像素時鐘頻率,以及LCD顯示屏的時序等。

6.2 修改mach-smdk2440.c代碼(LCD驅動部分)

/* LCD driver info */
/************************************************************************/
static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {
 
	.lcdcon5	= 	S3C2410_LCDCON5_FRM565 |
			  		S3C2410_LCDCON5_INVVLINE |
			  		S3C2410_LCDCON5_INVVFRAME |
			  		S3C2410_LCDCON5_PWREN |
			  		S3C2410_LCDCON5_HWSWP,
 
	.type		= S3C2410_LCDCON1_TFT,
 	.width		= 480,
	.height		= 272,
	
	.pixclock	= 111000,
	.xres		= 480,
	.yres		= 272,
	.bpp		= 16,
	
	.left_margin	= 2,/*HBP=VBPD+1,行切換,從同步到繪圖之間的延遲*/ 
	.right_margin	= 2,/*HFP=HFPD+1,行切換,從繪圖到同步之間的延遲*/ 
	.hsync_len	    = 41,/*HSPW+1,水準同步的長度*/ 
        
	.upper_margin	= 2, /*VBP=VBPD+1,幀切換,從同步到繪圖之間的延遲*/ 
	.lower_margin	= 2, /*VFB=VFPD+1,幀切換,從繪圖到同步之間的延遲*/ 
	.vsync_len		= 10,/*VSPW+1,垂直同步的長度*/
};
 
static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
	.displays	= &smdk2440_lcd_cfg,
	.num_displays	= 1,
	.default_display = 0,
 
#if 1
	/* currently setup by downloader */
    .gpccon         = 0xaaaaaaaa,   /* 将GPC端口配置為LCD控制管腳 */
    .gpccon_mask    = 0xffffffff,
    .gpcup          = 0xffff,       /* 禁止GPC端口内部上拉 */
    .gpcup_mask     = 0x0000,
    .gpdcon         = 0xaaaaaaaa,   /* 将GPD端口配置為LCD控制管腳 */
    .gpdcon_mask    = 0xffffffff,
    .gpdup          = 0xffff,       /* 禁止GPD端口内部上拉 */
    .gpdup_mask     = 0x0000,
#endif
 
//.lpcsel		= ((0xCE6) & ~7) | 1<<4,//非LPC3600三星自家的顯示屏
}
 
====================================================================
static void __init smdk2440_machine_init(void)
{	
 
	s3c24xx_fb_set_platdata(&smdk2440_fb_info);
	s3c_i2c0_set_platdata(NULL);
 
	platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
	smdk_machine_init();
    
    /* 添加以下兩句代碼 */
    writel((readl(S3C2410_GPBCON) & ~(3)) | 1, S3C2410_GPBCON);  // 初始化背光控制引腳為輸出
    writel((readl(S3C2410_GPBDAT) | 1), S3C2410_GPBDAT);         // 打開背光
}
           

像素時鐘pixclock的概念:

pixclock=1/dotclock 其中dotclock是視訊硬體在顯示器上繪制像素的速率 dotclock=(x向分辨率+左空邊+右空邊+HSYNC長度)* (y向分辨率+上空邊+下空邊+YSYNC長度)整屏的重新整理率 其中x向分辨率、左空邊、右空邊、HSYNC長度、y向分辨率、上空邊、下空邊和YSYNC長度可以在X35LCD說明文檔中查到。 整屏的重新整理率計算方法如下: 假如我們通過查X35LCD說明文檔,知道fclk=6.34MHZ,那麼畫一個像素需要的時間就是1/6.34us,如果屏的大小是240320,那麼現實一行需要的時間就是240/6.34us,每條掃描線是240,但是水準回掃和水準同步也需要時間,如果水準回掃和水準同步需要29個像素時鐘,是以,畫一條掃描線完整的時間就是(240+29) /6.34us。完整的屏有320根線,但是垂直回掃和垂直同步也需要時間,如果垂直回掃和垂直同步需要13個像素時鐘,那麼畫一個完整的屏需要(240+29)(320+13)/6.34us,是以整屏的重新整理率就是6.34/((240+29)(320+13))MHZ

pixclock計算方法:

DOTCLK = fframe × (X + HBP + HFP+HSPW) × (Y + VBP + VFP+VSPW) (機關:MHz)

pixclock = 10^12/ DOTCLK=10^12/ (fframe × (X + HBP + HFP+hsynclen) × (Y + VBP + VFP+vsynclen)) (機關:皮秒)

例如:

假設有fframe=60,X=480,Y=272,VBP=2, VFP=2,HBP=2, HFP=2,HSPW=40,VSPW=9。

pixclock = 10^12/(fframe × (X + HBP + HFP+hsync_len) × (Y + VBP + VFP+vsync_len))
    	 = 10^12/(60*(480+2+41+2)*(272+2+10+2))
    	 = 10^12/8960400
    	 = 111000皮秒
           
  • s3c24xx_fb_set_platdata作用就是把傳入的結構體smdk2440_fb_info存入s3c_device_lcd.dev中的platform_data,即:s3c_device_lcd.dev.platform_data = smdk2440_fb_info.目的為了在使用platform虛拟總線裝置是可以通過platform_device(也就是s3c_device_lcd的結構類型)可以找到smdk2440_fb_info(該結構體記錄了lcd的配置資訊,像素,長,寬等)
void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)//smdk2440_fb_info
{
	struct s3c2410fb_mach_info *npd;
	npd = s3c_set_platdata(pd, sizeof(*npd), &s3c_device_lcd);
    if (npd) 
    {
        npd->displays = kmemdup(pd->displays,
                sizeof(struct s3c2410fb_display) * npd-> num_displays,GFP_KERNEL);
        if (!npd->displays)
            printk(KERN_ERR "no memory for LCD display data\n");
    } 
    else 
    {
        printk(KERN_ERR "no memory for LCD platform data\n");
    }
}
           
  • LCD的配對使用的是platform架構,這是裝置資訊 s3c_device_lcd裝置在mach-smdk2440.c中被使用,當uboot傳入的machid等于smdk2440的機器id就會通過platform一次性注冊多個裝置。
static struct resource s3c_lcd_resource[] = {
	[0] = DEFINE_RES_MEM(S3C24XX_PA_LCD, S3C24XX_SZ_LCD),
	[1] = DEFINE_RES_IRQ(IRQ_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		= &samsung_device_dma_mask,
		.coherent_dma_mask	= DMA_BIT_MASK(32),
	}
};
 
/* 需要注冊的多個裝置 */
static struct platform_device *smdk2440_devices[] __initdata = {
	&s3c_device_ohci,
	&s3c_device_lcd,
	&s3c_device_wdt,
	&s3c_device_i2c0,
	&s3c_device_iis,
	&wr2440_device_eth,
}
//通過platform_add_devices函數實作platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
//具體實作如下
int platform_add_devices(struct platform_device **devs, int num)
{
	int i, ret = 0;
 
	for (i = 0; i < num; i++) {
		ret = platform_device_register(devs[i]);
		if (ret) {
			while (--i >= 0)
				platform_device_unregister(devs[i]);
			break;
		}
	}
	return ret;
}
           
  • 當在核心中開啟配置s3c2410 LCD framebuffer時就會把s3c2410fb.c編譯進核心
->Device Drivers
	->Graphics support
		->Support for fame buffer devices
			->S3C2440 LCD framebuffer support(需要開啟)
           

6.3 分析s3c2410fb.c中代碼(不用修改)

//驅動的入口函數,使用platform架構進行比對
int __init s3c2410fb_init(void)
{
	int ret = platform_driver_register(&s3c2410fb_driver);//在此處進行比對,s3c2410fb_driver在下一步
 
	if (ret == 0)
		ret = platform_driver_register(&s3c2412fb_driver);
 
	return ret;
}
 
//s3c2410fb_driver是裝置驅動資訊根據driver.name進行比對的
static struct platform_driver s3c2410fb_driver = {
	.probe		= s3c2410fb_probe,
	.remove		= __devexit_p(s3c2410fb_remove),
	.suspend	= s3c2410fb_suspend,
	.resume		= s3c2410fb_resume,
	.driver		= {
		.name	= "s3c2410-lcd",
		.owner	= THIS_MODULE,
	},
};
=======================================================================
//當比對成功時,調用s3c2410fb_probe函數
static int __devinit s3c2410fb_probe(struct platform_device *pdev)
{
	return s3c24xxfb_probe(pdev, DRV_S3C2410);//DRV_S3C2410是type類型,為了區分DRV_S3C2412
}
//看一下具體的s3c24xxfb_probe
static int __devinit s3c24xxfb_probe(struct platform_device *pdev, enum s3c_drv_type drv_type)
{	
	//傳入的參數pdev = s3c_device_lcd,
/*	static struct resource s3c_lcd_resource[] = {
	[0] = DEFINE_RES_MEM(S3C24XX_PA_LCD, S3C24XX_SZ_LCD),
	[1] = DEFINE_RES_IRQ(IRQ_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		= &samsung_device_dma_mask,
			.coherent_dma_mask	= DMA_BIT_MASK(32),
		}
	};
*/
	struct s3c2410fb_info *info;
	struct s3c2410fb_display *display;
	struct fb_info *fbinfo;
	struct s3c2410fb_mach_info *mach_info;
	struct resource *res;
	int ret;
	int irq;
	int i;
	int size;
	u32 lcdcon1;
 
	mach_info = pdev->dev.platform_data;//這裡取出來前面存入的東西,就是smdk2440_fb_info,此變量内含各種LCD的參數資訊
 
    //找到是哪個display,default_display = 0,num_displays = 1
	display = mach_info->displays + mach_info->default_display;
 
	irq = platform_get_irq(pdev, 0);//注冊LCD中斷
 
    //申請一個幀緩沖區結構體,内含有幀緩沖區裝置的屬性和操作函數的集合
	fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
 
	platform_set_drvdata(pdev, fbinfo);//把fbinfo存入pdev.dev.driver_data,pdev也就是s3c_device_lcd
 
	info = fbinfo->par;//fbinfo->par是在framebuffer_alloc()申請時開辟了一塊空間
	info->dev = &pdev->dev;
	info->drv_type = drv_type;//drv_type = DRV_S3C2410
 
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//獲得平台資源
 
	size = resource_size(res);
	info->mem = request_mem_region(res->start, size, pdev->name);//申請記憶體
 
	info->io = ioremap(res->start, size);//記憶體映射
 
	if (drv_type == DRV_S3C2412)
		info->irq_base = info->io + S3C2412_LCDINTBASE;
	else
		info->irq_base = info->io + S3C2410_LCDINTBASE;//0x4D000000 + 0x54 = 0x4D000054
 
	strcpy(fbinfo->fix.id, driver_name);//driver_name = s3c2410fb
 
	/* Stop the video */
	lcdcon1 = readl(info->io + S3C2410_LCDCON1);//讀lcdcon1寄存器的内容
	writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
				//S3C2410_LCDCON1_ENVID = 1,把lcdcon1中第0位清零。意思是關閉圖像輸出和lcd 控制器信号輸出
	fbinfo->fix.type	        = FB_TYPE_PACKED_PIXELS;
	fbinfo->fix.type_aux	    = 0;
	fbinfo->fix.xpanstep	    = 0;
	fbinfo->fix.ypanstep	    = 0;
	fbinfo->fix.ywrapstep	    = 0;
	fbinfo->fix.accel	    = FB_ACCEL_NONE;
 
	fbinfo->var.nonstd	    = 0;
	fbinfo->var.activate	    = FB_ACTIVATE_NOW;
	fbinfo->var.accel_flags     = 0;
	fbinfo->var.vmode	    = FB_VMODE_NONINTERLACED;
 
	fbinfo->fbops		    = &s3c2410fb_ops;//應用層使用open,read,write等操作函數集合
	fbinfo->flags		    = FBINFO_FLAG_DEFAULT;
	fbinfo->pseudo_palette      = &info->pseudo_pal;
 
	for (i = 0; i < 256; i++)
		info->palette_buffer[i] = PALETTE_BUFF_CLEAR;//清除緩沖區
 
	ret = request_irq(irq, s3c2410fb_irq, 0, pdev->name, info);//申請lcd中斷
 
    //擷取lcd的時鐘,并使能時鐘
	info->clk = clk_get(NULL, "lcd");
	clk_enable(info->clk);
	info->clk_rate = clk_get_rate(info->clk);
 
	/* 計算緩存,一幀圖像的大小 */
	for (i = 0; i < mach_info->num_displays; i++) {
		unsigned long smem_len = mach_info->displays[i].xres;
 
		smem_len *= mach_info->displays[i].yres;
		smem_len *= mach_info->displays[i].bpp;
		smem_len >>= 3;//長*寬*bpp(16位)     / 8
		if (fbinfo->fix.smem_len < smem_len)
			fbinfo->fix.smem_len = smem_len;
	}
 
	/* Initialize video memory */
	ret = s3c2410fb_map_video_memory(fbinfo);
	fbinfo->var.xres = display->xres;
	fbinfo->var.yres = display->yres;
	fbinfo->var.bits_per_pixel = display->bpp;
 
     //初始化lcd内部寄存器,主要是gpio,用于資料傳輸的VD[0:23]
	s3c2410fb_init_registers(fbinfo);
 
    //注冊一個幀緩沖實體,也就是fb_info結構體
	ret = register_framebuffer(fbinfo);
 
	/* create device files */
	ret = device_create_file(&pdev->dev, &dev_attr_debug);
	if (ret)
		printk(KERN_ERR "failed to add debug attribute\n");
 
	printk(KERN_INFO "fb%d: %s frame buffer device\n",
		fbinfo->node, fbinfo->fix.id);
 
	return 0;
}
           

fbinfo->fbops = &s3c2410fb_ops; //操作函數集合

  • 之後并沒有看到注冊裝置節點,也就是在看到在/dev目錄下注冊裝置。猜測應該在ret = register_framebuffer(fbinfo);中幹了什麼東西!下面重點看一下register_framebuffer(fbinfo):
int register_framebuffer(struct fb_info *fb_info)
{
    //省略了不必要代碼
	ret = do_register_framebuffer(fb_info);
 
	return ret;
}
===================================================================
static int do_register_framebuffer(struct fb_info *fb_info)
{
	int i;
	struct fb_event event;
	struct fb_videomode mode;
 
    //周遊register_fb數組中找到一個未用的空項
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])
			break;
 
    //在此處注冊了裝置節點,FB_MAJOR = 29,i根據register_fb中第幾個空項來确定,如果是第一個就是i = 0,名字fb0/1/2
	fb_info->dev = device_create(fb_class, fb_info->device,
                                 MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
 
    //從此處開始,沒看懂
	if (fb_info->pixmap.addr == NULL) {
		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
		if (fb_info->pixmap.addr) {
			fb_info->pixmap.size = FBPIXMAPSIZE;
			fb_info->pixmap.buf_align = 1;
			fb_info->pixmap.scan_align = 1;
			fb_info->pixmap.access_align = 32;
			fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
		}
	}	
	fb_info->pixmap.offset = 0;
 
	if (!fb_info->pixmap.blit_x)
		fb_info->pixmap.blit_x = ~(u32)0;
 
	if (!fb_info->pixmap.blit_y)
		fb_info->pixmap.blit_y = ~(u32)0;
 
	if (!fb_info->modelist.prev || !fb_info->modelist.next)
		INIT_LIST_HEAD(&fb_info->modelist);
 
	fb_var_to_videomode(&mode, &fb_info->var);
	fb_add_videomode(&mode, &fb_info->modelist);
 
    //以上的不知道要幹嘛,但是下面的看懂了,就是把一個幀緩沖實體fb_info填入registered_fb中的一個空項
 
	registered_fb[i] = fb_info;
 
	event.info = fb_info;
	if (!lock_fb_info(fb_info))
		return -ENODEV;
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	unlock_fb_info(fb_info);
	return 0;
}
           
  • 還有fbmem.c檔案中沒有分析,它裡面就是對幀緩沖區的抽象,對應用層提供接口函數。
/**
 *	fbmem_init - init frame buffer subsystem
 *
 *	Initialize the frame buffer subsystem.
 *
 *	NOTE: This function is _only_ to be called by drivers/char/mem.c.
 *
 */
 
static int __init fbmem_init(void)
{
	proc_create("fb", 0, NULL, &fb_proc_fops);
    
    //注冊字元裝置,fb_fops是操作函數集合,主裝置号:FB_MAJOR = 29,和上面s3c2410fb.c中注冊裝置節點使用的主裝置号一樣。
    //發現在fbmem_init隻是注冊裝置并沒有生成裝置節點,隻有在一個實際的幀緩沖區也就是lcd裝置注冊 時候才生成裝置節點
	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
		printk("unable to get major %d for fb devs\n", FB_MAJOR);
 
	fb_class = class_create(THIS_MODULE, "graphics");
 
	return 0;
}
 
//fb_fops操作函數集合如下
static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.unlocked_ioctl = fb_ioctl,
	.mmap =		fb_mmap,
	.open =		fb_open,
	.release =	fb_release,
	.llseek =	default_llseek,
};
這些操作函數是給應用調用的
           
  • 這裡簡單分析一下應用層如何調用到裝置驅動的,以open為例:
應用層:open("/dev/fb0",O_RDWR);
                ||
==================================================
驅動層:         || 
                || 
                \/
 fb_fops->open(),
                        ||
                        ||
                        \/
                info->fbops->fb_open
    根據s3c240fb.c中fbinfo->fbops = &s3c2410fb_ops;故此也就是s3c2410fb_ops函數中的open
雖然s3c2410fb_ops中沒有open函數,那就應用層使用open也就是不調用實際的函數。
 
fbmem.c中 fb_fops->open函數實作如下:
static int fb_open(struct inode *inode, struct file *file){
	int fbidx = iminor(inode);
	struct fb_info *info;
	int res = 0;
 
	info = get_fb_info(fbidx);
	if (!info) {
		request_module("fb%d", fbidx);
		info = get_fb_info(fbidx);
		if (!info)
			return -ENODEV;
	}
	
	file->private_data = info;
	if (info->fbops->fb_open) {
		//這裡沒運作
		res = info->fbops->fb_open(info,1);
		if (res)
			module_put(info->fbops->owner);
	}
	
#ifdef CONFIG_FB_DEFERRED_IO
	if (info->fbdefio)
		fb_deferred_io_open(info, inode, file);
#endif
 
	return res;
}
           

6.4核心配置

在linux-3.4.2/目錄下輸入“make menuconfig”,配置如下:

# cd linux-3.4.2/
# make menuconfig

    Device Drivers  --->
        Graphics support  --->
            <*> Support for frame buffer devices  --->
                --- Support for frame buffer devices
                [*]   Enable firmware EDID
                [ ]   Framebuffer foreign endianness support  ----
                [*]   Enable Video Mode Handling Helpers
                [ ]   Enable Tile Blitting Support
                *** Frame buffer hardware drivers **
                < >   Epson S1D13XXX framebuffer support
                <*>   S3C2410 LCD framebuffer support
                [ ]     S3C2410 lcd debug messages
                < >   SMSC UFX6000/7000 USB Framebuffer suppor
                < >   Displaylink USB Framebuffer support
                < >   Virtual Frame Buffer support (ONLY FOR TESTING!)
                < >   E-Ink Metronome/8track controller support
                < >   E-Ink Broadsheet/Epson S1D13521 controller suppor
                
            [*] Bootup logo  --->
                --- Bootup logo
                [ ]   Standard black and white Linux logo
                [ ]   Standard 16-color Linux logo
                [*]   Standard 224-color Linux logo

           

6.5 編譯測試

  • 修改好./arch/arm/mach-s3c24xx/mach-smdk2440.c檔案後,重新編譯生成uImage映像檔案。
  • 将生成的uImage檔案複制到tftp共享檔案夾
  • 重新開機開發闆,進入菜單,退出菜單。
  • 将uImage檔案通過tftp方式下載下傳到開發闆記憶體30000000處:

    tftp 30000000 uImage

  • 從30000000處重新開機開發闆:

    bootm 30000000

  • 此時你會在開發闆螢幕上看到可愛的小企鵝圖檔
  • 向螢幕輸出字元串,可執行

    echo “hello DarkBirds!” > /dev/tty1

繼續閱讀