LCD是一個常用外設,一般情況下半導體廠商會為自家的晶片編寫好相應的LCD接口驅動程式。開發者無需修改LCD驅動部分,隻要按照所使用的LCD裝置來修改裝置樹即可。雖然不需要修改驅動,但是我們還是有必要了解一下LCD驅動的流程
1. Framebuffer裝置
在Linux中應用程式最終是通過操作RGB LCD的顯存來實作 LCD上顯示字元、圖檔等資訊。Linux系統中記憶體的管理很嚴格,顯存是需要申請的,而且因為虛拟記憶體的存在,驅動程式設定的顯存和應用程式通路的顯存必須要是同一片實體記憶體。
為了解決該問題,Framebuffer誕生了,Framebuffer就是幀緩沖(簡稱 fb)。Framebuffer是儲存着一幀圖像的一塊記憶體,向這塊記憶體寫入資料就相當于向螢幕中寫入資料。簡單來說Framebuffe 把螢幕上的每個點映射成一段線性記憶體空間,程式可以簡單的改變這段記憶體的值來改變螢幕上某一點的顔色。
Framebuffer機制為使用者空間操作顯示裝置提供了統一的接口,屏蔽了底層硬體之間的差異,虛拟出一個fb裝置,當LCD驅動加載成功後,會生成名為 /dev/fbX的裝置,應用程式通過通路 /dev/fbX這個裝置就可以通路 LCD
/dev/fbX是 LCD對應的裝置檔案, 是個字元裝置,是以有其對應的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,
};
Linux核心将所有的Framebuffer抽象為fb_info結構體, fb_info結構體包含了Framebuffer裝置的完整屬性和操作集合,是以每一個Framebuffer裝置都必須有一 個fb_info。該結構體定義在include/linux/fb.h檔案裡面,内容如下
struct fb_info {
atomic_t count;
int node;
int flags;
struct mutex lock; /* 互斥鎖 */
struct mutex mm_lock; /* 互斥鎖,用于fb_mmap和smem_*域*/
struct fb_var_screeninfo var; /* 目前可變參數 */
struct fb_fix_screeninfo fix; /* 目前固定參數 */
struct fb_monspecs monspecs; /* 目前顯示器特性 */
struct work_struct queue; /* 幀緩沖事件隊列 */
struct fb_pixmap pixmap; /* 圖像硬體映射 */
struct fb_pixmap sprite; /* 光标硬體映射 */
struct fb_cmap cmap; /* 目前調色闆 */
struct list_head modelist; /* 目前模式清單 */
struct fb_videomode *mode; /* 目前視訊模式 */
#ifdef CONFIG_FB_BACKLIGHT /* 如果LCD支援背光的話 */
/* assigned backlight device */
/* set before framebuffer registration, 467 remove after unregister */
struct backlight_device *bl_dev; /* 背光裝置 */
/* Backlight level curve */
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif ......
struct fb_ops *fbops; /* 幀緩沖操作函數集 */
struct device *device; /* 父裝置 */
struct device *dev; /* 目前fb裝置 */
int class_flag; /* 私有sysfs标志 */
......
char __iomem *screen_base; /* 虛拟記憶體基位址(螢幕顯存) */
unsigned long screen_size; /* 虛拟記憶體大小(螢幕顯存大小) */
void *pseudo_palette; /* 僞16位調色闆 */
......
};
fb_info結構體的成員變量很多,我們重點關注 var、fix、fbops、 screen_base、screen_size和 pseudo_palette
2. LCD驅動程式分析
同一個晶片下,不同分辨率LCD螢幕的eLCDIF控制器驅動代碼都是一樣的,下面以NXP官方編寫的IMX6ULL晶片在Linux下的LCD驅動為例,來簡單梳理一下LCD驅動的流程
- 打開imx6ull.dtsi,然後找到lcdif節點内容,如下所示:
lcdif: lcdif@021c8000 {
compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
reg = <0x021c8000 0x4000>;
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
<&clks IMX6UL_CLK_LCDIF_APB>,
<&clks IMX6UL_CLK_DUMMY>;
clock-names = "pix", "axi", "disp_axi";
status = "disabled";
};
- 根據compatible屬性值,在Linux源碼中搜尋
符串,即可找到IMX6ULL的LCD驅動檔案drivers/video/fbdev/mxsfb.c"fsl,imx6ul-lcdif", "fsl,imx28-lcdif"
static const struct of_device_id mxsfb_dt_ids[] = {
{ .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
{ .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },
{ /* sentinel */ }
};
......
......
static struct platform_driver mxsfb_driver = {
.probe = mxsfb_probe,
.remove = mxsfb_remove,
.shutdown = mxsfb_shutdown,
.id_table = mxsfb_devtype,
.driver = {
.name = DRIVER_NAME,
.of_match_table = mxsfb_dt_ids,
.pm = &mxsfb_pm_ops,
},
};
module_platform_driver(mxsfb_driver);
- 可見這是一個标準的platform驅動,當驅動和裝置比對以後mxsfb_probe函數就會執行
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; //LCD主要接口
struct fb_info *fb_info; //
struct pinctrl *pinctrl;
int irq = platform_get_irq(pdev, 0);
int gpio, ret;
......
//從裝置樹中擷取eLCDIF接口控制器的寄存器首位址
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Cannot get memory IO resource\n");
return -ENODEV;
}
//給host申請記憶體,
host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL);
if (!host) {
dev_err(&pdev->dev, "Failed to allocate IO resource\n");
return -ENOMEM;
}
//給fb_info申請記憶體,也就是申請fb_info
fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
if (!fb_info) {
dev_err(&pdev->dev, "Failed to allocate fbdev\n");
devm_kfree(&pdev->dev, host);
return -ENOMEM;
}
host->fb_info = fb_info; //設定host的fb_info成員變量為fb_info,
fb_info->par = host; //設定fb_info的par成員變量為host
//申請中斷,中斷服務函數為mxsfb_irq_handler
ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0, dev_name(&pdev->dev), host);
if (ret) {
dev_err(&pdev->dev, "request_irq (%d) failed with
error %d\n", irq, ret);
ret = -ENODEV;
goto fb_release;
}
//對從裝置樹中擷取到的寄存器首位址進行記憶體映射得到虛拟位址,并儲存到host的base成員變量
//是以通過通路host的base成員即可通路IMX6ULL的整個eLCDIF寄存器
host->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->base)) {
dev_err(&pdev->dev, "ioremap failed\n");
ret = PTR_ERR(host->base);
goto fb_release;
}
......
//給fb_info中的pseudo_palette申請記憶體
fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16, GFP_KERNEL);
if (!fb_info->pseudo_palette) {
ret = -ENOMEM;
goto fb_release;
}
INIT_LIST_HEAD(&fb_info->modelist);
pm_runtime_enable(&host->pdev->dev);
//初始化fb_info
ret = mxsfb_init_fbinfo(host);
if (ret != 0)
goto fb_pm_runtime_disable;
mxsfb_dispdrv_init(pdev, fb_info);
if (!host->dispdrv) {
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl)) {
ret = PTR_ERR(pinctrl);
goto fb_pm_runtime_disable;
}
}
if (!host->enabled) {
writel(0, host->base + LCDC_CTRL);
mxsfb_set_par(fb_info); //設定eLCDIF控制器的相應寄存器
mxsfb_enable_controller(fb_info); //設定eLCDIF控制器的相應寄存器
pm_runtime_get_sync(&host->pdev->dev);
}
//向Linux核心注冊fb_info
ret = register_framebuffer(fb_info);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register framebuffer\n");
goto fb_destroy;
}
......
return ret;
}
mxsfb_probe 函數的主要工作内容為: