來公司上班現在已經整整一個月了,蔽人不才,能力有限,學習進度緩慢,不過也是有一點點的收獲與心得,在這里寫出來與大家分享,養成良好的記錄習慣也免得后忘記。
不啰嗦了,開入正題。來公司一個月左右的時間,主要接觸了高通android4.0和android2.3的LCD驅動,當然在bootloader下LCD的驅動我也調試的。
(1) kernel
先來說一下kernel里LCD的移植,之前從來沒有接觸過LINUX里LCD的驅動,所以剛一開始我竟然連MIPI接口、LCDC接口這些最基本的都不知道,鄙視一下我自己,呵呵。感覺android2.3和android4.0在kernel里LCD驅動上的區別不是特別大。只是android2.3里多了一個late_display.c這個文件,背光和MDP的開關以及屏的初始化都是在這個文件里調用的。
因為高通給的codebase本身包含有一些LCD芯片的驅動,我在2.3上用的是MIPI接口的,是根據truly的例子來做的。在2.3上我們使用的是MIPI CMD模式,從truly的例子移植的時候代碼的大體結構不需要改動,一般只是需要針對要更改的屏的文檔作一些參數與配置的修改就可以了。我是照着truly的在/kernel/drivers/video/msm下創建mipi_hx8369a.cMipi_hx8369a_cmd_wvga_pt.c這兩個文件,從truly的例子中把內容拷過來,把名字全改成hx8369a,的mipi_hx8369a.c里面,主要就是GPIO_HX8369A_LCD_RESET的宏,根據原理圖來定義正確的RESET管腳,然后還有mipi_hx8369a_lcd_reset()里LCD初始化之前reset的時序要根據lcd芯片的spec來,初始化之前的reset對lcd非常重要;最后就是根據FAE給的初始化指令填好初始化數組。
在Mipi_hx8369a_cmd_wvga_pt.c文件里有一些要傳到frame_buffer中去的一些參數,這些參數與所選用的顯示屏有關,在這里把最主要的一些列出來:
- pinfo.xres = 480; // LCD的x方向寬度
- pinfo.yres = 800; // LCD的y方向高度
- pinfo.type = MIPI_CMD_PANEL; // LCD接口是哪種類型,如MIPI_VIDEO_PANEL,LCDC_PANEL
- pinfo.pdest = DISPLAY_1;// 應該是系統中的第幾個屏(我自己猜測的)
- pinfo.bpp = 24;//每個像素的bit位數,即是多少位色
- //下面幾個porch值可以從FAE要到
- pinfo.lcdc.h_back_porch = 16;
- pinfo.lcdc.h_front_porch = 16;
- pinfo.lcdc.h_pulse_width = 16;
- pinfo.lcdc.v_back_porch = 4;
- pinfo.lcdc.v_front_porch = 4;
- pinfo.lcdc.v_pulse_width = 4;
- pinfo.bl_max = 32; //背光的最大和最小等級
- pinfo.bl_min = 1;
- pinfo.fb_num = 2;// 顯存的個數
- pinfo.clk_rate = 349000000;//LCD的p_clock時種頻率,要與Modem里的值對應起來
- pinfo.mipi.mode = DSI_CMD_MODE;
- //mipi接口的模式
- pinfo.mipi.dst_format = DSI_CMD_DST_FORMAT_RGB888; //RGB的格式
- pinfo.mipi.rgb_swap = DSI_RGB_SWAP_RGB; //RGB的排序
- pinfo.mipi.data_lane0 = TRUE; //是否使用MIPI的第一個通道
- pinfo.mipi.data_lane1 = TRUE; //是否使用MIPI的第二個通道
其它的一些參數我還沒弄清是什么意思,不明白就照着別的例子抄來吧。
由於android2.3里有一個late_display.c的函數,這個函數用來控制背光、MDP的開關以及打開屏的時候調用mipi_hx8369a.c和Mipi_hx8369a_cmd_wvga_pt.c中的初始化函數mipi_hx8369a_lcd_late_init()和mipi_cmd_hx8369a_wvga_pt_late_init()。所以late_display.c里把變量gpio_backlight_en的值改為當前使用的背光控制管腳。函數mipi_hx8369a_lcd_late_init()和mipi_cmd_hx8369a_wvga_pt_late_init()是在late_display_ioctl()里調用,在上層打開LCD設備的時候會來設用這兩個函數來初始化LCD設備。
下面就是要改board文件了,在board文件里會給LCD分配顯存,這里會有一個宏MSM_FB_SIZE,通過計算后填一個有余量的值便好。然后就是在board文件里添加設備以及設備相關的一些資源,也可以參考別的芯片的例子來。定義
- static struct msm_panel_common_pdata mipi_pdata = {
- .gpio = GPIO_BACKLIGHT_EN,
- };
這里定義了控制背光的GPIO管腳,如果背光控制管腳使用的是PMIC的一個GPIO來控制,使用的方法也不一樣,在下一篇4.0的LCD移植中會寫這種方式。還要定義一個platform_device,
- static struct platform_device mipi_dsi_panel_device = {
- .name = "mipi_hx8369a",
- .id = 0,
- .dev = {
- .platform_data = &mipi_pdata,
- }
- };
這里name要與mipi_hx8369a.c里的platform_driver里的名字一樣,id要選擇0,因為mipi_hx8369a.c里還會分配一個platform_device,它的name和這個一樣,所以mipi_hx8369a.c里的probe函數會被調用兩次,第一次與board里的platform_devices匹配,目的是為了將背光的管腳號傳遞給驅動(因為我這里使用了late_display.c里的變量,而且背光控制是在late_display.c里,所以這里的管腳號也不重要了)。
最后再把結構體mipi_dsi_panel_device加到board文件里添加設備的數組中,當系統啟動時統一向內核中注冊設備。
kernel里驅動文件添加和修改就基本完成了,然后就是要在/kernel/driver/video/msm里修改Kconfig和Makefile文件.
在Makefile里,添加
- obj-$(CONFIG_FB_MSM_MIPI_DSI_HX8369A) += mipi_hx8369a.o
- obj-$(CONFIG_FB_MSM_MIPI_HX8369A_CMD_WVGA_PT) += mipi_hx8369a_cmd_wvga_pt.o
在Kconfig里,添加
- config FB_MSM_MIPI_HX8369A_CMD_WVGA_PT_PANEL
- bool "MIPI Yassy Command WVGA PT Panel"
- select FB_MSM_MIPI_HX8369A_CMD_WVGA_PT
- config FB_MSM_MIPI_HX8369A_CMD_WVGA_PT
- bool
- select FB_MSM_MIPI_DSI_HX8369A
- default n
但有一點要注意的是,config FB_MSM_MIPI_HX8369A_CMD_WVGA_PT_PANEL這個的位置要放在choice包含的里面,具體可以參考truly的例子的位置。
最后,運行make kernelconfig,在里面選中FB_MSM_MIPI_HX8369A_CMD_WVGA_PT_PANEL和FB_MSM_MIPI_HX8369A_CMD_WVGA_PT,
然后在/kernel/arch/arm/configs下找到與工程相關的配置文件,在里面可以找到選中的配置,在編繹的時候,根據這個成.config文件,在out/target/product/$(Projec)/obj/KERNEL_OBJ,在這里也可以查到最后選中的配置。
(2) bootloader
我每次都是先移植kernel里的LCD驅動,因為我覺得kernel里的代碼要相對集中一些,而且就算是代碼弄的有問題,也不會造成手機找不到端口而下不了程序,而且kernel里的LOG要容易抓一些,bootloader里還得接個串口,太麻煩。然后就是有了kernel里驅動,bootloader里的LCD也要簡單一些了,畢竟有了一些經驗了。
在bootloader里移植LCD有一點需要特別注意,就是/bootable/bootloader/lk/platform/msm_shared/splash.h這個文件,如果高通的codebase是Lcdc接口的,而你要改為mipi接口的話,這個文件一定要注意更換,否則會造成手機找不到端口而直接掛掉。蔽人不才,由於這個原因燒壞了手機不下十次,鄙視一下在這里,哈哈。更換了splash.h后還要注釋掉/vendor/qcom/opensource/qrdplus/QRDExtensions/DynamicComponents/res/Splash_QRD/Android.mk這個文件下的
- ifeq ($(strip $(QRDExt_Splash)),cu)
- $(shell cp $(LOCAL_PATH)/splash_cu.h bootable/bootloader/lk/platform/msm_shared/include/splash.h)
- else
- $(shell cp $(LOCAL_PATH)/splash_qrd.h bootable/bootloader/lk/platform/msm_shared/include/splash.h)
- endif
這幾句,否則在你編譯的時候,你改過的splash.h會被更改回去。
步入正題吧,該說說bootloader里移植lcd驅動了。高通的bootloader使用的是一個叫lk(little kernel)的bootloader,在lk里也有不少高通提供的示例代碼,我一般也是參考truly的來改,同樣的,代碼的大體結構都不需要更改。首先要在目錄/bootable/bootloader/lk/target/$(Projec)下更改lcd的makefile文件rules.mk,由於我們的手機是從nand flash啟動,所以要改的都在NDND啟下的方式下改。在rules.mk下添加:
- DEFINES += DISPLAY_MIPI_CMD_PANEL_HX8369A=1
代碼的主要流程基本上可以根着codebase里truly的來照着填,這里簡單的列一下:
- void kmain()(/bootable/bootloader/lk/kernel/main.c)
- -> thread_resume (thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
- -> bootstrap2
- -> target_init(); (bootable\bootloader\lk\target\$(project)\Init.c)
- ->/*這里是bootloader里按鍵的驅動*/
- #if (!ENABLE_NANDWRITE)
- keys_init();
- keypad_init();
- #endif
- /* 下面的就是與屏相關的函數,下面來一個一個的分析*/
- #if DISPLAY_SPLASH_SCREEN
- display_init();
- dprintf(SPEW, "Diplay initialized\n");
- display_image_on_screen();
- mdelay(50);
- //turn on backlight
- gpio_set(I300_PANEL_BACKLIGHT, 1); /*打開LCD背光*/
- #endif
- display_init() (/bootable/bootloader/lk/platform/msm7627a/platform.c)
- ->panel_dsi_init()/*主要是初始化LCD相關的管腳,如reset和backlight*/
- ->mipi_init()(/bootable/bootloader/lk/platform/msm_shared/mipi_dsi.c)
- ->struct mipi_dsi_panel_config *panel_info = get_panel_info()
- -> return &hx8369a_cmd_panel_info;/*結構體hx8369a_cmd_panel_info里是一些與LCD相關的參數,可參考kernel里填寫,但有一點要注意,就是這里面有lcd芯片的初始化參數,它有特定的格式,第一個是參數個數,接着三是固定值,剩下的才是初始化參數,而且所有這些必須是4的整數倍,若不夠則補0xff*/
- ->mipi_dsi_phy_init() (/bootable/bootloader/lk/target/$(project)/panel.c)/*設置mipi接口相關的一些寄存器*/
- ->status += mipi_dsi_panel_initialize(panel_info);
- -> status = mipi_dsi_cmds_tx(pinfo->panel_cmds, pinfo->num_of_panel_cmds);/*這里對LCD進行初始化,然后屏里寫初始化參數*/
- -> mipi_fb_cfg.base = MIPI_FB_ADDR; /*DMA地址*/
- -> cmd_mode_status = 1;/*標記為cmd模式*/
- display_image_on_screen() (/bootable/bootloader/lk/dev/fbcon/fbcon.c)
- -> for (i = 0; i < SPLASH_IMAGE_WIDTH_HDPI; i++) /*向DMA地址寫數據*/
- {
- memcpy (config->base + ((image_base_hdpi + (i * (config->width))) * bytes_per_bpp),
- imageBuffer_rgb888 + (i * SPLASH_IMAGE_HEIGHT_HDPI * bytes_per_bpp),
- SPLASH_IMAGE_HEIGHT_HDPI * bytes_per_bpp);
- }
- -> fbcon_flush();
- -> if(is_cmd_mode_enabled()) /*如果是CMD模式,就啟動MDA,往LCD里送顯示數據*/
- -> mipi_dsi_cmd_mode_trigger();
到這里lk里LCD移植也寫完了,如果認為有問題的地方,歡迎提出來。下一篇再寫4.0里LCD的移植,4.0里使用的是LCDC的接口。
高通安卓調試LCD幾方面總結(二)
最近工作太忙了,而且經常出差,所以有一個多月沒有來得及更新博客了,唉,之前一個項目LCD也遇到幾個棘手的問題,壓力山大。
閑話少說,直接進入正題了,在上一篇里寫了高通平台android2.3里的kernel和bootloader(LK)里LCD驅動的移植,這一篇主要寫一下在4.0里LCD驅動的移植。
(1) kernel
高通的android4.0和2.3在kernel里的LCD驅動是有區別的,主要在於4.0里少了latedisplay.c這個文件。這里我以renesas公司的r61408這顆IC來介紹。我使用的是RGB565的接口,由於kernel里有高通公司提供的其它家IC的代碼,我同樣是以truly的來作為參考的,代碼的框架不變。首先就是在kernel\drivers\video\msm下以truly的代碼為模板,創建一個名為lcdc_r61408.c的文件,文件里的內容照着truly的來做。
這里先來把Makefile和Kconfig文件修改好。在kernel\drivers\video\msm文件夾下的Makefile里添加:
- obj-$(CONFIG_FB_MSM_LCDC_RENESAS_R61408) += lcdc_renesas_r61408.o
在此目錄下打開Kconfig文件,在里面添加:
- config FB_MSM_LCDC_RENESAS_R61408
- bool
- select FB_MSM_LCDC_PANEL
- default n
- config FB_MSM_LCDC_RENESAS_R61408_PT_PANEL
- depends on FB_MSM_LCDC_HW
- bool "LCDC Renesas PT Panel"
- select FB_MSM_LCDC_RENESAS_R61408
- default n
- ---help---
- Support for LCDC Renesas PT panel
還要在kernel\arch\arm\configs目錄下的board config文件里添加:
- CONFIG_FB_MSM_LCDC_RENESAS_R61408_PT_PANEL=y
在lcdc_r61408.c文件里主要就是要寫向LCD用SPI發指令和發數據的函數,我們的這個項目里使用的是GPIO口模擬的SPI總線,所以SPI的時序一定要寫正確,R61408在用SPI發指令的時候data和command是在寫8bit數據前通過SDO口的高和低來判別的,但是有些LCD的IC,比如NT35510,需要在發8bit數據之前要發送R/W,H/L,D/C幾個信號來區別接下來發的數據表示什么。所以一定要根據所選用的LCD的IC的spec嚴格的來寫SPI的時序,否則有可能造成LCD的初始化都不成功。
在kernel里,LCD的驅動的調用的流程是:
- msm_fb_open() (msm_fb.c)
- -> msm_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable)
- -> if (!mfd->panel_power_on) {
- ret = pdata->on(mfd->pdev); //這里的on指向的就是lcdc.c里的lcdc_on()函數
- -> lcdc_on() /*這個函數里開啟LCDC時鍾,並且打開LCD*/
- -> clk_prepare_enable(pixel_mdp_clk);
- clk_prepare_enable(pixel_lcdc_clk);
- if (lcdc_pdata && lcdc_pdata->lcdc_power_save)
- lcdc_pdata->lcdc_power_save(1);/*函數指針,指向board-display.c里的msm_lcdc_power_save()*/
- if (lcdc_pdata && lcdc_pdata->lcdc_gpio_config) /*這里lcdc_gpio_config指針是空,所以不會接着調 下一步*/
- ret = lcdc_pdata->lcdc_gpio_config(1);
- ret = panel_next_on(pdev);/*函數指針,指向lcdc_renesas_r61408.c里的lcdc_renesas_panel_on()函數*/
- panel_next_on(pdev) (lcdc.c)
- -> lcdc_renesas_panel_on() (lcdc_renesas_r61408.c)
- -> renesas_disp_reginit() /*初始化LCD的寄存器*/
- -> renesas_spi_write_bytes() /*設用SPI寫函數*/
- lcdc_renesas_panel_init(void) (lcdc_renesas_r61408.c)
- -> platform_driver_register(&this_driver);/*注冊LCD驅動,此時renesas_probe函數會調一次,主要是為了從board-display.c里獲取SPI和背光各管腳的定義值*/
- /*設置與LCD相關的參數,具體各參數意義參考上一篇*/
- pinfo->xres = 480;
- pinfo->yres = 800;
- MSM_FB_SINGLE_MODE_PANEL(pinfo);
- pinfo->type = LCDC_PANEL;
- pinfo->pdest = DISPLAY_1;
- pinfo->wait_cycle = 0;
- pinfo->bpp = 16;
- pinfo->fb_num = 2;
- pinfo->clk_rate = 30720000;
- pinfo->bl_max = 32;
- pinfo->bl_min = 1;
- pinfo->lcdc.h_back_porch = 10;/* hsw = 8 + hbp=16 */
- pinfo->lcdc.h_front_porch = 150;////100
- pinfo->lcdc.h_pulse_width = 10;
- pinfo->lcdc.v_back_porch = 22;///12/* vsw=1 + vbp = 7 */
- pinfo->lcdc.v_front_porch = 10;
- pinfo->lcdc.v_pulse_width = 2;
- pinfo->lcdc.border_clr = 0; /* blk */
- pinfo->lcdc.underflow_clr = 0xff;/* blue */
- pinfo->lcdc.hsync_skew = 0;
- platform_device_register(&this_device);/*這里又注冊了一個device,這個device和board-display.c里 的device是通過id來區別的,然后會再次調用renesas_probe函數*/
- -> msm_fb_add_device(pdev) /*向高通的FB架構里將上面的LCD信息注冊進去*/
在關閉LCD的時候,kernel里驅動調用流程剛好是和上面反向的。
這里有一點,因為我們使用的背光是通過PMIC的一個1號腳來控制的,使用PMIC管腳產生PWM的使用方法是,在board-display.c里初始化GPIO的函數里,加上這么一句:
- pmapp_disp_backlight_init();
然后在board-display.c里加上如下配置:
- static int lcdc_set_bl(int level)
- {
- int ret;
- //PRINTK("lcdc_renesas_set_bl,level = %d\r\n",level);
- ret = pmapp_disp_backlight_set_brightness(level);
- if (ret)
- pr_err("%s: can't set lcd backlight!\n", __func__);
- return ret;
- }
- static struct msm_panel_common_pdata lcdc_panel_data = {
- .panel_config_gpio = NULL,
- .pmic_backlight = lcdc_set_bl,
- .gpio_num = lcdc_gpio_table,
- }
最后在lcdc_renesas_r61408.c的pmic_backlight這個函數指針就可以實現背光的控制和調節。
(2) bootloader(lk)
和上一篇一樣,首先要在目錄/bootable/bootloader/lk/target/$(Projec)下更改lcd的makefile文件rules.mk,在rules.mk下添加:
- DEFINES += DISPLAY_LCDC_PANEL_R61408=1
代碼的流程可以跟着truly的例子來做,這里簡單的介紹一下流程:
- void kmain()(/bootable/bootloader/lk/kernel/main.c)
- -> thread_resume (thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
- -> bootstrap2
- -> target_init(); (bootable\bootloader\lk\target\$(project)\Init.c)
- -> /*這里是bootloader里按鍵的驅動*/
- #if (!ENABLE_NANDWRITE)
- keys_init();
- keypad_init();
- #endif
- display_init(); /*初台化LCD驅動*/
- dprintf(INFO, "Diplay initialized\n");
- display_image_on_screen(); /*向LCD里發送圖像數據*/
- display_init() (/bootable/bootloader/lk/platform/msm7627a/platform.c)
- /*這里lcdc_init()和panel_lcdc_init()的順序如果調過來LCD顯示就不正常,我也很奇怪,不知道是為什么*/
- -> fb_config = lcdc_init();
- -> mdp_lcdc_clock_init();/*初始化LCDC的時鍾*/
- lcd_timing = get_lcd_timing();/*獲取LCD相關的硬件設置*/
- fb_cfg = lcdc_init_set(lcd_timing);/*根據上面得到的設置數據來設置CPU的寄存器*/
- -> panel_lcdc_init(); /*初始化LCD,要完成的有RESET,SPI的讀寫函數等等*/
LCDC接口的LCD驅動在LK里相對MIPI的來說流程比較簡單,SPI的時序參考KERNEL里的一般就沒有什么問題的。
到這里,LCDC和MIPI的兩種LCD在高通平台上的驅動也算是寫完了,小弟也正處在學習的過程當中,寫得不好。我再寫一下在調試LCDC接口的LCD驅動時的一些心得(也是之前在網上看到的):
1)調試lcd背光,背光主要分為PMIC自帶的和單獨的DCDC,如果為PMIC自帶的背光,一般平台廠商已經做好,直接調用接口即可,如果為單獨的DCDC驅動,則需要用GPIO控制DCDC的EN端
2)確認lcd的模擬電,io電是否正常
3)根據lcd的分辨率,RGB/CPU/MIPI等不同的接口,配置控制寄存器接口
4)根據lcd spec配置PCLK的頻率,配置PCLK,VSYNC,HSYNC,DE等控制線的極性
5)使用示波器測試所有clk的波形,確認頻率,極性是否符合要求
6)使用示波器測試data線,看是否有數據輸出,bpp的設置是否正確
7)如果lcd需要初始化,配置spi的接口,一般分為cpu自帶的spi控制器,和gpio模擬的spi。
8)根據lcd spec中的初始化代碼進行lcd的初始化
9)用示波器測量lcd的spi clk及數據線,確認是否正常輸出
10)正常情況下,此時lcd應該可以點亮。如果沒有點亮,按照上述步驟1到9,逐項進行檢查測試,重點檢查第5項,clk的極性
11)如果lcd點亮,但是花屏。則需要先確認數據格式是否正確,然后確認fb里的數據是否正常,有以下幾種方法確認fb里的數據
i)cat /dev/graphics/fb0 > /sdcard/fb0,然后將/sdcard/fb0 >到另一台相同分辨率及相同格式的手機上,看圖片顯示是否正常 ii)使用irfanview軟件顯示cat /dev/graphics/fb0出來的raw數據,注意要正確設置分辨率及格式,否則顯示花屏 iii)如果adb連接正常,可以使用豌豆莢等軟件,查看fb中的數據是否正常
通過以上三種途徑,如果確認fb中的數據正常顯示,則很可能為lcd初始化代碼的問題,或者clk極性的問題,如果fb數據不正常,則可能為lcd控制寄存器配置不正常導致