一.Framebuffer設備
????????LCD 顯示器都是由一個一個的像素點組成,像素點就類似一個燈(在 OLED 顯示器中,像素點就是一個小燈),這個小燈是 RGB 燈,也就是由 R(紅色)、G(綠色)和 B(藍色)這三種顏色組成的,而 RGB 就是光的三原色。1080P 的意思就是一個 LCD 屏幕上的像素數量是1920*1080 個,也就是這個屏幕一列 1080 個像素點,一共 1920 列。
????????通過控制 R、G、B 這三種顏色的亮度就可以顯示出各種各樣的色彩。那該如何控制 R、G、B 這三種顏色的顯示亮度呢?一般一個 R、G、B 這三部分分別使用 8bit 的數據,那么一個像素點就是 8bit*3=24bit,也就是說一個像素點3 個字節,這種像素格式稱為 RGB888。如果再加入 8bit 的 Alpha(透明)通道的話一個像素點就是 32bit,也就是 4 個字節,這種像素格式稱為 ARGB8888。
????????如果采用 ARGB8888 格式的話一個像素需要 4 個字節的內存來存放像素數據,那么 1024*600 分辨率就需要 1024*600*4=2457600B≈2.4MB 內存。但是 RGB LCD 內部是沒有內存的,所以就需要在開發板上的 DDR3 中分出一段內存作為 RGBLCD 屏幕的顯存,我們如果要在屏幕上顯示什么圖像的話直接操作這部分顯存即可。
1.裸機編寫LCD驅動
????????a.初始化 I.MX6U 的 eLCDIF 控制器,重點是 LCD 屏幕寬(width)、高(height)、hspw、
hbp、hfp、vspw、vbp 和 vfp 等信息。
??????? b.初始化 LCD 像素時鐘。
??????? c.設置 RGBLCD 顯存。
??????? d.應用程序直接通過操作顯存來操作 LCD,實現在 LCD 上顯示字符、圖片等信息。
2. LInux下的Framebuffer設備
????????在 Linux 中應用程序最終也是通過操作 RGB LCD 的顯存來實現在 LCD 上顯示字符、圖片
等信息。在裸機中我們可以隨意的分配顯存,但是在 Linux 系統中內存的管理很嚴格,顯存是
需要申請的,不是你想用就能用的。而且因為虛擬內存的存在,驅動程序設置的顯存和應用程
序訪問的顯存要是同一片物理內存。
????????為了解決上述問題,Framebuffer 誕生了, Framebuffer 翻譯過來就是幀緩沖,簡稱 fb,因
此大家在以后的 Linux 學習中見到“Framebuffer”或者“fb”的話第一反應應該想到 RGBLCD或者顯示設備。fb 是一種機制,將系統中所有跟顯示有關的硬件以及軟件集合起來,虛擬出一個 fb 設備,當我們編寫好 LCD 驅動以后會生成一個名為/dev/fbX(X=0~n)的設備,應用程序通過訪問/dev/fbX 這個設備就可以訪問 LCD。
????????在linux系統中,/dev/fb0 就是 LCD 對應的設備文件,/dev/fb0 是個字符設備,fb 的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,
};
二.LCD驅動
????????不同分辨率的 LCD 屏幕其 eLCDIF 控制器驅動代碼都是一樣的,只需要修改好對應的屏幕參數即可。屏幕參數信息屬于屏幕設備信息內容,這些肯定是要放到設備樹中的,我們需要在設備樹中將屏幕信息修改為我們所使用的屏幕參數。
1.內核驅動
????????NXP 官方編寫的 Linux 下的 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";};
????????lcdif 節點信息是所有使用 I.MX6ULL 芯片的板子所共有的,是lcd控制器相關的信息,后續需要根據使用LCD屏幕規格參數的不同,在此節點后面續上特定的信息。根據上述lcdif 節點中的信息得到compatible 屬性值為“fsl,imx6ul-lcdif”和“fsl,imx28-lcdif”,搜索這兩個字符串可以找到內核中LCD的驅動文件,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;
//host 結構體指針變量,表示 I.MX6ULL 的 LCD 的主控接口,mxsfb_info 結構
//體是 NXP 定義的針對 I.MX 系列 SOC 的 Framebuffer 設備結構體。也就是我們前面一直說的設
//備結構體,此結構體包含了 I.MX 系列 SOC 的 Framebuffer 設備詳細信息,比如時鐘、eLCDIF
//控制器寄存器基地址、fb_info 等。struct mxsfb_info *host;struct fb_info *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;if (gpio_is_valid(gpio)) {ret = devm_gpio_request_one(&pdev->dev, gpio, GPIOF_OUT_INIT_LOW, "lcd_pwr_en");if (ret) {dev_err(&pdev->dev, "faild to request gpio %d, ret = %d\n", gpio, ret);return ret;}}
//從設備樹中獲取 eLCDIF 接口控制器的寄存器首地址,設備樹中 lcdif 節點已
//經設置了 eLCDIF 寄存器首地址為 0X021C8000,因此 res=0X021C8000。res = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!res) {dev_err(&pdev->dev, "Cannot get memory IO resource\n");return -ENODEV;}
//給 host 申請內存,host 為 mxsfb_info 類型結構體指針。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,設置 fb_info 的 par 成員變量為
//host。通過這一步就將前面申請的 host 和 fb_info 聯系在了一起。host->fb_info = fb_info;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;}
//從設備樹中獲取到的寄存器首地址(res)進行內存映射,得到虛擬地址,并保
//存到 host 的 base 成員變量。因此通過訪問 host 的 base 成員即可訪問 I.MX6ULL 的整個 eLCDIF
//寄存器。其實在 mxsfb.c 中已經定義了 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;}host->pdev = pdev;platform_set_drvdata(pdev, host);host->devdata = &mxsfb_devdata[pdev->id_entry->driver_data];host->clk_pix = devm_clk_get(&host->pdev->dev, "pix");if (IS_ERR(host->clk_pix)) {host->clk_pix = NULL;ret = PTR_ERR(host->clk_pix);goto fb_release;}//........host->reg_lcd = devm_regulator_get(&pdev->dev, "lcd");if (IS_ERR(host->reg_lcd))host->reg_lcd = NULL;
//給 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);
//調用 mxsfb_init_fbinfo 函數初始化 fb_info,重點是 fb_info 的 var、 fix、 fbops,
//screen_base 和 screen_size。其中 fbops 是 Framebuffer 設備的操作集
//mxsfb_init_fbinfo 函數通過
//調用 mxsfb_init_fbinfo_dt 函數從設備樹中獲取到 LCD 的各個參數信息。
//最后mxsfb_init_fbinfo函數會調用 mxsfb_map_videomem 函數申請 LCD 的幀緩沖內存(也就是縣存)。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);mxsfb_enable_controller(fb_info);pm_runtime_get_sync(&host->pdev->dev);}
//向 Linux 內核注冊 fb_inforet = 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");return 0;//................return ret;
}
??????? 根據上述probe函數中的信息可以得到,Linux 內核將所有的 Framebuffer 抽象為一個叫做 fb_info 的結構體, fb_info 結構體包含了 Framebuffer 設備的完整屬性和操作集合,因此每一個 Framebuffer 設備都必須有一個 fb_info。LCD 的驅動就是構建 fb_info,并且向系統注冊 fb_info
的過程。fb_info結構體的內容如下所示:
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, 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 位調色板 */..........
};
2.LCD驅動修改
??????? LCD 驅動部分芯片廠家提供的內核中已有不需要去修改。我們需要做的就是按照所使用的 LCD 屏幕規格來修改設備樹。主要有三個地方需要注意:
①LCD 所使用的 IO 配置。
②LCD 屏幕節點修改,修改相應的屬性值,換成我們所使用的 LCD 屏幕參數。
③LCD 背光節點信息修改,要根據實際所使用的背光 IO 來修改相應的設備節點信息。
????????a.LCD 屏幕 IO 配置
????????設備樹中 LCD 所使用的 IO 配置在文件imx6ull-alientek-emmc.dts中,iomuxc節點中相關的配置有:
//子節點 pinctrl_lcdif_dat,為 RGB LCD 的 24 根數據線配置項。pinctrl_lcdif_dat: lcdifdatgrp {fsl,pins = <MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x49MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x49MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x49MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x49MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x49MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x49MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x49MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x49MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x49MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x49MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x49MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x49MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x49MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x49MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x49MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x49MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x49MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x49MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x49MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x49MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x49MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x49MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x49MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x49>;};
//子節點 pinctrl_lcdif_ctrl,RGB LCD 的 4 根控制線配置項,包括 CLK、ENABLE、VSYNC 和 HSYNC。pinctrl_lcdif_ctrl: lcdifctrlgrp {fsl,pins = <MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x49MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x49MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x49MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x49>;};
????????b.LCD 屏幕參數節點信息修改
????????在 imx6ull-alientek-emmc.dts 文件中找到 lcdif 節點,節點內容如下所示:
&lcdif {pinctrl-names = "default";pinctrl-0 = <&pinctrl_lcdif_dat //使用到的io&pinctrl_lcdif_ctrl>;display = <&display0>;status = "okay";display0: display { /* LCD 屬性信息 */bits-per-pixel = <24>; /* 一個像素占用 24bit */bus-width = <24>; /* 總線寬度 */display-timings {native-mode = <&timing0>; /* 時序信息 */timing0: timing0 {clock-frequency = <51200000>; /* LCD 像素時鐘,單位 Hz */hactive = <1024>; /* LCD X 軸像素個數 */vactive = <600>; /* LCD Y 軸像素個數 */hfront-porch = <160>; /* LCD hfp 參數 */hback-porch = <140>; /* LCD hbp 參數 */hsync-len = <20>; /* LCD hspw 參數 */vback-porch = <20>; /* LCD vbp 參數 */vfront-porch = <12>; /* LCD vfp 參數 */vsync-len = <3>; /* LCD vspw 參數 */hsync-active = <0>; /* hsync 數據線極性 */vsync-active = <0>; /* vsync 數據線極性 */de-active = <1>; /* de 數據線極性 *//* rgb to hdmi: pixelclk-ative should be set to 1 */pixelclk-active = <0>; /* clk 數據線先極性 */};};};
};
??????? c.LCD 屏幕背光節點信息
????????LCD 接口背光控制 IO 連接到了 I.MX6U 的 GPIO1_IO08 引腳上, GPIO1_IO08復用為 PWM1_OUT,通過 PWM 信號來控制 LCD 屏幕背光的亮度。GPIO1_IO08 這個 IO 的配置,在 imx6ull-alientek-emmc.dts 中可以找到:
pinctrl_pwm1: pwm1grp {fsl,pins = <MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0>;};
????????LCD 背光要用到 PWM1,因此也要設置 PWM1 節點,在 imx6ull.dtsi 文件中找到如下內
容:???????
pwm1: pwm@02080000 {compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";reg = <0x02080000 0x4000>;interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>;clocks = <&clks IMX6UL_CLK_PWM1>,<&clks IMX6UL_CLK_PWM1>;clock-names = "ipg", "per";#pwm-cells = <2>;};
????????在 imx6ull-alientek-emmc.dts 文件中找到向 pwm1追加的內容,設置 pwm1 所使用的 IO 為 pinctrl_pwm1(GPIO1_IO08),如下所示:
&pwm1 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_pwm1>;status = "okay";
};
????????還需要一個節點來將 LCD 背光和 PWM1_OUT連 接 起 來 。 這 個 節 點 就 是 backlight:
backlight {compatible = "pwm-backlight";pwms = <&pwm1 0 5000000>; //描述背光所使用的 PWM 以及 PWM 頻率brightness-levels = <0 4 8 16 32 64 128 255>;default-brightness-level = <6>;status = "okay";};