之前在Linux系統(tǒng)移植時提到過LCD驅動,本篇來看下Linux設備樹如何配置LCD驅動。
1 知識點
首先需要了解一個新的概念:Framebuffer
1.1 Framebuffer
Framebuffer直譯即幀緩沖,簡稱 fb,它是Linux將系統(tǒng)中所有跟顯示有關的硬件以及軟件集合起來,將底層的LCD虛擬抽象出一 個/dev/fbX設備,應用程序可以通過操作/dev/fbX來實現(xiàn)對屏幕的顯示控制。
NXP官方Linux內核已默認開啟了LCD驅動,在dev/目錄下可以看到fb0這樣一個設備
Framebuffer在內核中的表現(xiàn)就是fb_info結構體:
完整的結構體定義如下:
struct fb_info {
atomic_t count;
int node;
int flags;
struct mutex lock; /* Lock for open/release/ioctl funcs */
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
struct fb_var_screeninfo var; /* 當前的可變參數(shù) */
struct fb_fix_screeninfo fix; /* 當前的固定參數(shù) */
struct fb_monspecs monspecs; /* Current Monitor specs */
struct work_struct queue; /* Framebuffer event queue */
struct fb_pixmap pixmap; /* Image hardware mapper */
struct fb_pixmap sprite; /* Cursor hardware mapper */
struct fb_cmap cmap; /* Current cmap */
struct list_head modelist; /* mode list */
struct fb_videomode *mode; /* current mode */
#ifdef CONFIG_FB_BACKLIGHT
/* assigned backlight device */
/* set before framebuffer registration,
remove after unregister */
struct backlight_device *bl_dev;
/* Backlight level curve */
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
struct fb_deferred_io *fbdefio;
#endif
struct fb_ops *fbops; /* 幀緩沖操作函數(shù)集 */
struct device *device; /* This is the parent */
struct device *dev; /* This is this fb device */
int class_flag; /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /* Tile Blitting */
#endif
char __iomem *screen_base; /* 虛擬內存基地址(屏幕顯存) */
unsigned long screen_size; /* 虛擬內存大小(屏幕顯存大小) */
void *pseudo_palette; /* 偽16位調色板 */
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /* Hardware state i.e suspend */
void *fbcon_par; /* fbcon use-only private area */
/* From here on everything is device dependent */
void *par;
/* we need the PCI or similar aperture base/size not
smem_start/size as smem_start may just be an object
allocated inside the aperture so may not actually overlap */
struct apertures_struct {
unsigned int count;
struct aperture {
resource_size_t base;
resource_size_t size;
} ranges[0];
} *apertures;
bool skip_vt_switch; /* no VT switch on suspend/resume required */
};
注意結構體中的fb_fops這一項,/dev/fb0 是個字符設備,fb_fops就是它的文件操作結構體,它的file_operations操作集在drivers/video/fbdev/core/fbmem.c 文件中:
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
.fsync = fb_deferred_io_fsync,
#endif
.llseek = default_llseek,
};
可以看到有熟悉的open、release等函數(shù)接口。
因此,LCD驅動的重點就是初始化fb_info里面的各個成員。
fb_info結構體的成員變量很多,需要重點關注的是這幾個:
var:當前的可變參數(shù)
fix:當前的固定參數(shù)
fbops:幀緩沖操作函數(shù)集
screen_base:虛擬內存基地址(屏幕顯存)
screen_size:虛擬內存大小(屏幕顯存大小)
pseudo_palette:偽16位調色板
初始化完成fb_info后,通過register_framebuffer函數(shù)向內核注冊剛剛初始化的fb_info。
1.2 LCD驅動文件mxsfb介紹
LCD的驅動文件為mxsfb.c,這是一種platform驅動框架,驅動和設備匹配之后,mxsfb_probe函數(shù)就會執(zhí)行。
LCD的初始化通過mxsfb_probe函數(shù)來實現(xiàn),該函數(shù)的主要功能有:
申請fb_info
初始化fb_info結構體中的各個成員變量
初始化eLCDIF控制器
使用register_framebuffer函數(shù)向Linux內核注冊初始化好的fb_info
該函數(shù)位于:/drivers/video/fbdev/mxsfb.c中
該函數(shù)的實現(xiàn)如下:
static int mxsfb_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
of_match_device(mxsfb_dt_ids, &pdev->dev);
struct resource *res;
struct mxsfb_info *host; //<-----NXP的fb_info
struct fb_info *fb_info; //<-----Linux的fb_info
struct pinctrl *pinctrl;
int irq = platform_get_irq(pdev, 0);
int gpio, ret;
if (of_id)
pdev->id_entry = of_id->data;
gpio = of_get_named_gpio(pdev->dev.of_node, "enable-gpio", 0);
if (gpio == -EPROBE_DEFER)
return -EPROBE_DEFER;
//省略...
fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);//<--------申請fb_info
if (!fb_info) {
dev_err(&pdev->dev, "Failed to allocate fbdev\n");
devm_kfree(&pdev->dev, host);
return -ENOMEM;
}
host->fb_info = fb_info; //<---將mxsfb_info與fb_info聯(lián)系起來
fb_info->par = host;
//省略...
ret = mxsfb_init_fbinfo(host);
if (ret != 0)
goto fb_pm_runtime_disable;
mxsfb_dispdrv_init(pdev, fb_info);
//省略...
ret = register_framebuffer(fb_info); //<------------注冊
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register framebuffer\n");
goto fb_destroy;
}
console_lock();
ret = fb_blank(fb_info, FB_BLANK_UNBLANK);
console_unlock();
if (ret < 0) {
dev_err(&pdev->dev, "Failed to unblank framebuffer\n");
goto fb_unregister;
}
dev_info(&pdev->dev, "initialized\n");
}
其中,register_framebuffer函數(shù)的原型如下:
函數(shù)參數(shù)和返回值含義:
fb_info:需上報的fb_info
返回值:0-成功,負值-失敗
1.3 LCD 驅動程序編寫
6ULL的eLCDIF接口驅動程序 NXP 已經編 寫好了,因此 LCD 驅動部分我們不需要去修改。我們需要做的就是按照所使用的 LCD 來修改設備樹。
1.3.1 查看設備樹
1.3 先來看一下NXP官方編寫的Linux下的 LCD 驅動。打開 imx6ull.dtsi,然后找到 lcdif節(jié)點內容:
lcdif: lcdif@021c8000 {
compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
reg = <0x021c8000 0x4000>;
interrupts = ;
clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
<&clks IMX6UL_CLK_LCDIF_APB>,
<&clks IMX6UL_CLK_DUMMY>;
clock-names = "pix", "axi", "disp_axi";
status = "disabled";
};
其中021c8000 這個地址,可以從參考手冊中找到對應的介紹:
1.3.2 屏幕IO配置
打開 imx6ull-myboard.dts 文件,在 iomuxc 節(jié)點中找到如下內容:
具體為:
pinctrl_lcdif_dat: lcdifdatgrp {
fsl,pins = <
MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x79
MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x79
MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x79
MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x79
MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x79
MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x79
MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x79
MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x79
MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x79
MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x79
MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x79
MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x79
MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x79
MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x79
MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x79
MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x79
MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x79
MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x79
MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x79
MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x79
MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x79
MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x79
MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x79
MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x79
>;
};
pinctrl_lcdif_ctrl: lcdifctrlgrp {
fsl,pins = <
MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x79
MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x79
MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x79
MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x79
>;
};
pinctrl_pwm1: pwm1grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0
>;
};
這里有3個節(jié)點:
子節(jié)點pinctrl_lcdif_dat ,為 RGB LCD 的 24根數(shù)據線配置項
子節(jié)點 pinctrl_lcdif_ctrl ,為RGB LCD 的 4根控制線配置項,包括 CLK、ENABLE、VSYNC 和 HSYNC
子節(jié)點 pinctrl_pwm1 ,為RGB LCD 的背光亮度配置項
1.3.3 屏幕參數(shù)配置
在imx6ull-myboard.dts 文件中找到lcdif 節(jié)點,根據自己使用的LCD,修改為對應的參數(shù)。
下面是NXP官方板子的參數(shù):
我用的野火7寸屏(GT911,800x480),其參數(shù)為:
參數(shù) | 值 |
---|---|
width | 800 |
height | 480 |
HBP | 46 |
HFP | 22 |
VBP | 23 |
VFP | 22 |
HSPW | 1 |
VSPW | 1 |
修改后的lcdif 節(jié)點如下:
&lcdif {
pinctrl-names = "default"; /* 使用到的 IO */
pinctrl-0 = <&pinctrl_lcdif_dat
&pinctrl_lcdif_ctrl
&pinctrl_lcdif_reset>;
display = <&display0>;
status = "okay";
display0: display { /* LCD 屬性信息 */
bits-per-pixel = <16>; /* 一個像素占用幾個bit */
bus-width = <24>; /* 總線寬度 */
display-timings {
native-mode = <&timing0>; /* 時序信息 */
timing0: timing0 {
clock-frequency = <9200000>; /* LCD像素時鐘,單位Hz */
hactive = <800>; /* LCD X軸像素個數(shù) */
vactive = <480>; /* LCD Y軸像素個數(shù) */
hfront-porch = <22>; /* LCD hfp 參數(shù) */
hback-porch = <46>; /* LCD hbp 參數(shù) */
hsync-len = <23>; /* LCD hspw 參數(shù) */
vback-porch = <22>; /* LCD vbp 參數(shù) */
vfront-porch = <4>; /* LCD vfp 參數(shù) */
vsync-len = <1>; /* LCD vspw 參數(shù) */
hsync-active = <0>; /* hsync 數(shù)據線極性 */
vsync-active = <0>; /* vsync 數(shù)據線極性 */
de-active = <1>; /* de 數(shù)據線極性 */
pixelclk-active = <0>; /* clk 數(shù)據線極性 */
};
};
};
};
1.3.4 屏幕背光配置
通過PWM信號來控制LCD屏幕背光的亮度
pinctrl_pwm1: pwm1grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0
>;
};
LCD 背光要用到PWM1,因此也要設置 PWM1 節(jié)點,在imx6ull.dtsi 文件中找到如下內容:
這個節(jié)點信息不用修改,使用默認的配置即可。如果要修改的話,也不要修改這里,可以通過imx6ull-myboard.dts文件中進行修改。
imx6ull-myboard.dts中的pwm1節(jié)點:
&pwm1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm1>;
status = "okay";
};
imx6ull-myboard.dts中的backlight節(jié)點:
backlight {
compatible = "pwm-backlight";
pwms = <&pwm1 0 5000000>;
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <6>;
status = "okay";
};
2 實驗測試
2.1使能Linux logo顯示
uboot啟動的時候,LCD左上角上會顯示NXP的圖標,而Linux內核啟動的時候,LCD左上角上會顯示一個小企鵝。因此,可以通過小企鵝logo的顯示來驗證LCD 驅動是否正常。
默認情況下是已經開啟logo顯示的,可以再確認一下。
在Linux內核源碼目錄,輸入以下指令打開內核的圖形化配置:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
Linux內核配置界面:
然后按下路徑找到對應的配置項:
-> Device Drivers
-> Graphics support
-> Bootup logo (LOGO [=y])
最終到達這個界面:
這三個選項分別對應黑白、16 位、24 位色彩格式的 logo。
2.2 編譯設備樹
修改設備樹中的lcdif節(jié)點后(主要是修改屏幕的參數(shù)),在Linux內核源碼目錄執(zhí)行下面的命令,重新編譯設備樹并拷貝到網絡啟動位置。
make imx6ull-myboard.dtb
cp arch/arm/boot/dts/imx6ull-myboard.dtb ~/myTest/tftpboot/nxp/
然后重啟開發(fā)板,就可以在Linux內核驅動的時候看到屏幕上的企鵝圖標了:
2.3 設置LCD作為終端控制臺
之前一直使用串口來顯示板子的啟動和調試信息,實際上可以設置 LCD 作為終端進行同步顯示:
2.3.1 設置uboot的bootargs
重啟開發(fā)板,在倒計時時按回充進入ubout,可以先看下之前的bootargs配置:
只需要在原來的基礎上再添加console=tty1
即可:
setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.5.104:/home/xxpcb/myTest/nfs/rootfs,proto=tcp,nfsvers=4 rw ip=192.168.5.102:192.168.5.104:192.168.5.1:255.255.255.0::eth1:off'
saveenv
然后重啟開發(fā)板,在Linux內核驅動的時候就可以在屏幕上看到輸出信息了:
對比一下串口輸出的信息,可以看出屏幕輸出到Freeing unused kernel memory: 400K (8090e000 - 80972000)
這句后就沒有了,沒有出現(xiàn)按下回車鍵繼續(xù)的提示,也沒有顯示開啟自啟動的hello word測試程序的打印,這是因為某些設置還未完成。
2.3.2 修改/etc/inittab文件
該修改用于設置屏幕作為終端進行交互。
打開根文件系統(tǒng)中的/etc/inittab 文件,加入下面這一行:
tty1::askfirst:-/bin/sh
保存后重啟板子,并在板子的USB接口插上鍵盤,就可以通過鍵盤和板子交互了:
現(xiàn)在通過板子插入鍵盤,也可以在屏幕上操作板子了。
注意,之前設置的開機啟動的hello word程序的打印沒有出現(xiàn)在屏幕上,是因為printf的輸入沒有設置的LCD中,我們可以通過將輸出指向 /dev/tty1 來實現(xiàn)LCD屏幕的打印,比如測試屏幕輸出hello linux:
echo hello linux > /dev/tty1
2.4 其它問題
2.4.1 自動熄屏的問題
當沒有操作LCD屏幕一段時間后,屏幕會自動黑屏,這時可以通過接入鍵盤按下回車鍵進行喚醒(也可以通過板子的ON/OFF按鍵進行喚醒,因為該按鍵也被賦予了回車鍵的功能)。
這個時間是在Linux源碼的 drivers/tty/vt/vt.c中設置的,默認是10分鐘(10*60秒)。
如果想讓屏幕一直亮著,可以將改值設為0,并重新編輯Linux內核得到zImage,然后用新的zImage啟動開發(fā)板。
如果不想修改zImage,另外一種方式可以創(chuàng)建一個開機啟動的應用程序來控制屏幕不熄滅, lcd_always_on.c
的內容為:
#include
#include
#include
int main(int argc, char *argv[])
{
int fd;
fd = open("/dev/tty1", O_RDWR);
write(fd, "\033[9;0]", 8);
close(fd);
return 0;
}
在ubuntu中編譯該程序,然后將可執(zhí)行程序拷貝到板子的根文件系統(tǒng)中:
arm-linux-gnueabihf-gcc lcd_always_on.c -o lcd_always_on
cp lcd_always_on ~/myTest/nfs/rootfs/usr/bin/
然后,/etc/init.d/rcS中設置該程序開機自啟動即可。
保存后,重啟開發(fā)板,屏幕就不會自動熄屏了。
2.4.2 屏幕亮度調節(jié)
屏幕的亮度也是可以調節(jié)的,設備樹中背光節(jié)點設置了8 個等級,可以在 0~7范圍內進行亮度調節(jié),進入下面的目錄,可以查看當前屏幕的亮度:
/sys/devices/platform/backlight/backlight/backlight
通過下面的指令可以實時修改屏幕的亮度,比如修改亮度為1:
echo 1 > brightness
總結
本篇介紹了LCD屏幕驅動相關知識并進行了實驗,因為NXP官方的板子和我這個板子的LCD引腳一樣,因此主要的修改就是將設備樹中的lcdif 節(jié)點的屏幕參數(shù)進行修改即可。
通過實驗,可以將企鵝logo顯示出來,并將板子的輸出信息定向到了LCD屏幕顯示,通過接入鍵盤可實現(xiàn)與Linux板子的交互。最后,還測試了屏幕熄屏和亮度調節(jié)功能。
-
嵌入式
+關注
關注
5060文章
18976瀏覽量
302220 -
lcd
+關注
關注
34文章
4407瀏覽量
166950 -
Linux
+關注
關注
87文章
11213瀏覽量
208736 -
LCD驅動
+關注
關注
4文章
88瀏覽量
26608 -
i.MX6
+關注
關注
1文章
37瀏覽量
16272
發(fā)布評論請先 登錄
相關推薦
評論