有关LCD接口的相关文档,已有许多优秀博文讲解,本文不再赘述,下面仅浅显地罗列出LCD驱动程序、测试程序等
LCD驱动程序的核心就是:
-
分配fb_info
-
设置fb_info
-
注册fb_info
-
硬件相关的设置
硬件相关的设置又可以分为3部分:
- 引脚设置
- 时钟设置
- LCD控制器设置
/**
* 文件 : lcd_drv.c
* 作者 : glen
* 描述 : lcd driver文件
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/errno.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/dmaengine.h>
#include <linux/dma-attrs.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/io.h>
#include <video/display_timing.h>
#include <video/of_display_timing.h>
#include <linux/gpio/consumer.h>
#include <asm/div64.h>
#include <asm/mach/map.h>
struct imx6ull_lcdif {
volatile unsigned int CTRL;
volatile unsigned int CTRL_SET;
volatile unsigned int CTRL_CLR;
volatile unsigned int CTRL_TOG;
volatile unsigned int CTRL1;
volatile unsigned int CTRL1_SET;
volatile unsigned int CTRL1_CLR;
volatile unsigned int CTRL1_TOG;
volatile unsigned int CTRL2;
volatile unsigned int CTRL2_SET;
volatile unsigned int CTRL2_CLR;
volatile unsigned int CTRL2_TOG;
volatile unsigned int TRANSFER_COUNT;
unsigned char RESERVED_0[12];
volatile unsigned int CUR_BUF;
unsigned char RESERVED_1[12];
volatile unsigned int NEXT_BUF;
unsigned char RESERVED_2[12];
volatile unsigned int TIMING;
unsigned char RESERVED_3[12];
volatile unsigned int VDCTRL0;
volatile unsigned int VDCTRL0_SET;
volatile unsigned int VDCTRL0_CLR;
volatile unsigned int VDCTRL0_TOG;
volatile unsigned int VDCTRL1;
unsigned char RESERVED_4[12];
volatile unsigned int VDCTRL2;
unsigned char RESERVED_5[12];
volatile unsigned int VDCTRL3;
unsigned char RESERVED_6[12];
volatile unsigned int VDCTRL4;
unsigned char RESERVED_7[12];
volatile unsigned int DVICTRL0;
unsigned char RESERVED_8[12];
volatile unsigned int DVICTRL1;
unsigned char RESERVED_9[12];
volatile unsigned int DVICTRL2;
unsigned char RESERVED_10[12];
volatile unsigned int DVICTRL3;
unsigned char RESERVED_11[12];
volatile unsigned int DVICTRL4;
unsigned char RESERVED_12[12];
volatile unsigned int CSC_COEFF0;
unsigned char RESERVED_13[12];
volatile unsigned int CSC_COEFF1;
unsigned char RESERVED_14[12];
volatile unsigned int CSC_COEFF2;
unsigned char RESERVED_15[12];
volatile unsigned int CSC_COEFF3;
unsigned char RESERVED_16[12];
volatile unsigned int CSC_COEFF4;
unsigned char RESERVED_17[12];
volatile unsigned int CSC_OFFSET;
unsigned char RESERVED_18[12];
volatile unsigned int CSC_LIMIT;
unsigned char RESERVED_19[12];
volatile unsigned int DATA;
unsigned char RESERVED_20[12];
volatile unsigned int BM_ERROR_STAT;
unsigned char RESERVED_21[12];
volatile unsigned int CRC_STAT;
unsigned char RESERVED_22[12];
volatile unsigned int STAT;
unsigned char RESERVED_23[76];
volatile unsigned int THRES;
unsigned char RESERVED_24[12];
volatile unsigned int AS_CTRL;
unsigned char RESERVED_25[12];
volatile unsigned int AS_BUF;
unsigned char RESERVED_26[12];
volatile unsigned int AS_NEXT_BUF;
unsigned char RESERVED_27[12];
volatile unsigned int AS_CLRKEYLOW;
unsigned char RESERVED_28[12];
volatile unsigned int AS_CLRKEYHIGH;
unsigned char RESERVED_29[12];
volatile unsigned int SYNC_DELAY;
};
struct lcd_regs {
volatile unsigned int fb_base_phys;
volatile unsigned int fb_xres;
volatile unsigned int fb_yres;
volatile unsigned int fb_bpp;
};
static struct lcd_regs *plcd_regs;
static struct fb_info *pfb_info;
static unsigned int pseudo_palette[32];
static struct gpio_desc *bl_gpio;
static struct clk *clk_pix;
static struct clk *clk_axi;
static void lcd_controller_enable(struct imx6ull_lcdif *lcdif)
{
lcdif->CTRL |= (1 << 0);
}
static int lcd_controller_init(struct imx6ull_lcdif *lcdif, struct display_timing *dt, int lcd_bpp, int fb_bpp, unsigned int fb_phy)
{
int bus_width;
int fb_width;
int vsync_pol;
int hsync_pol;
int de_pol;
int clk_pol;
if (dt->flags & DISPLAY_FLAGS_HSYNC_HIGH)
hsync_pol = 1;
if (dt->flags & DISPLAY_FLAGS_VSYNC_HIGH)
vsync_pol = 1;
if (dt->flags & DISPLAY_FLAGS_DE_HIGH)
de_pol = 1;
if (dt->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
clk_pol = 1;
if (lcd_bpp == 24)
bus_width = 0x3;
else if (lcd_bpp == 18)
bus_width = 0x2;
else if (lcd_bpp == 8)
bus_width = 0x1;
else if (lcd_bpp == 16)
bus_width = 0x0;
else
return -1;
if (fb_bpp == 24 || fb_bpp == 32)
fb_width = 0x3;
else if (fb_bpp == 18)
fb_width = 0x2;
else if (fb_bpp == 8)
fb_width = 0x1;
else if (fb_bpp == 16)
fb_width = 0x0;
else
return -1;
/**
* 初始化LCD控制器的CTRL寄存器
* [19] 1 : DOTCLK和DVI modes需要设置为1
* [17] 1 : 设置为1工作在DOTCLK模式
* [15:14] 00 : 输入数据不交换(小端模式)默认就为0,不需设置
* [13:12] 00 : CSC数据不交换(小端模式)默认就为0,不需设置
* [11:10] 11 : 数据总线为24bit
* [9:8] 根据显示屏资源文件bpp来设置:8位0x1, 16位0x0, 24位0x3
* [5] 1 : 设置elcdif工作在主机模式
* [1] 0 : 24位数据均是有效数据,默认就为0,不需设置
*/
lcdif->CTRL = (1 << 19) | (1 << 17) | (bus_width << 10) | \
(fb_width << 8) | (1 << 5);
/**
* 设置ELCDIF的寄存器CTRL1
* 根据bpp设置, bpp为24或32才设置
* [19:16]: 111表示传输24位无压缩数据, A通道不传输
*/
if (fb_bpp == 24 || fb_bpp == 32) {
lcdif->CTRL1 &= ~(0xf << 16);
lcdif->CTRL1 |= (0x7 << 16);
} else
lcdif->CTRL1 |= (0xf << 16);
/**
* 设置eLCDIF的寄存器TRANSFER_COUNT寄存器
* [31:16] : 垂直方向像素个数
* [15:0] : 水平方向像素个数
*/
lcdif->TRANSFER_COUNT = (dt->vactive.typ << 16) | (dt->hactive.typ << 0);
/**
* 设置eLCDIF的VDCTRL0寄存器
* [29] 0 : VSYNC输出, 默认为0,无需设置
* [28] 1 : 在DOTCLK模式下, 设置1硬件会产生使能ENABLE输出
* [27] 0 : VSYNC低电平有效, 根据屏幕配置文件将其设置为0
* [26] 0 : HSYNC低电平有效, 根据屏幕配置文件将其设置为0
* [25] 1 : DOTCLK下降沿有效, 根据屏幕配置文件将其设置为1
* [24] 1 : ENABLE信号高电平有效, 根据屏幕配置文件将其设置为1
* [21] 1 : 帧同步周期单位, DOTCLK mode设置为1
* [20] 1 : 帧同步脉冲宽度单位, DOTCLK mode设置为1
* [17:0] : vysnc脉冲宽度
*/
lcdif->VDCTRL0 = (1 << 28) | (vsync_pol << 27) | (hsync_pol << 26) \
| (clk_pol << 25) | (de_pol << 24) | (1 << 21) | (1 << 20) \
| (dt->vsync_len.typ << 0);
/**
* 设置eLCDIF的VDCTRL1寄存器
* 设置垂直方向的总周期: 上黑框tvb + 垂直同步脉冲tvp + 垂直有效高度yres + 下黑框tvf
*/
lcdif->VDCTRL1 = dt->vback_porch.typ + dt->vsync_len.typ + dt->vactive.typ + dt->vfront_porch.typ;
/**
* 设置eLCDIF的VDCTRL2寄存器
* [31:18] : 水平同步信号脉冲宽度
* [17:0] : 水平方向总周期
* 设置水平方向的总周期: 左黑框thb + 水平同步脉冲thp + 水平有效高度xres + 右黑框thf
*/
lcdif->VDCTRL2 = (dt->hsync_len.typ << 18) | (dt->hback_porch.typ + dt->hsync_len.typ + dt->hactive.typ + dt->hfront_porch.typ);
/**
* 设置eLCDIF的VDCTRL3寄存器
* [27:16] : 水平方向等待时钟数 = thb + thp
* [15: 0] : 垂直方向等待时钟数 = tvb + tvp
*/
lcdif->VDCTRL3 = ((dt->hback_porch.typ + dt->hsync_len.typ) << 16) | (dt->vback_porch.typ + dt->vsync_len.typ);
/**
* 设置eLCDIF的VDCTRL4寄存器
* [18] : 使用VSHYNC、HSYNC、DOTCLK模式此位置1
* [17:0] : 水平方向的宽度
*/
lcdif->VDCTRL4 = (1 << 18) | (dt->hactive.typ);
/**
* 设置eLCDIF的CUR_BUF和NEXT_BUT寄存器
* CUR_BUF : 当前显存地址
* NEXT_BUF : 下一帧显存地址
*/
lcdif->CUR_BUF = fb_phy;
lcdif->NEXT_BUF = fb_phy;
return 0;
}
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 lcd_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
unsigned int val;
/**
* dprintk("setcol:regno=%d, rgb=%d,%d,%d\n", regno, red, green, blue);
*/
switch (info->fix.visual) {
case FB_VISUAL_TRUECOLOR:
/* true-color, use pseudo - palette */
if (regno < 16) {
u32 *pal = info->pseudo_palette;
val = chan_to_field(red, &info->var.red);
val |= chan_to_field(green, &info->var.green);
val |= chan_to_field(blue, &info->var.blue);
pal[regno] = val;
}
break;
default:
return 1;
}
return 0;
}
static struct fb_ops fbops = {
.owner = THIS_MODULE,
.fb_setcolreg = lcd_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
static int lcd_probe(struct platform_device *pdev)
{
struct device_node *display_np;
dma_addr_t phy_addr;
int ret;
int width;
int bpp;
struct display_timings *timings = NULL;
struct display_timing *dt = NULL;
struct imx6ull_lcdif *lcdif;
struct resource *res;
/**
* 从设备树获取LCD显示设备相关信息
*/
display_np = of_parse_phandle(pdev->dev.of_node, "display", 0);
/* get common info */
ret = of_property_read_u32(display_np, "bus-width", &width);
ret = of_property_read_u32(display_np, "bits-per-pixel", &bpp);
/* get timming */
timings = of_get_display_timings(display_np);
dt = timings->timings[timings->native_mode];
/* get gpio from device tree */
bl_gpio = gpiod_get(&pdev->dev, "backlight", 0);
/* config bl_gpio as output */
gpiod_direction_output(bl_gpio, 1);
/* set val: gpiod_set_value(bl_gpio, status); */
/* get clk from device tree */
clk_pix = devm_clk_get(&pdev->dev, "pix");
clk_axi = devm_clk_get(&pdev->dev, "axi");
/* set clk rate */
clk_set_rate(clk_pix, dt->pixelclock.typ);
/* enable clk */
clk_prepare_enable(clk_pix);
clk_prepare_enable(clk_axi);
/* 分配fb_info */
pfb_info = framebuffer_alloc(0, NULL);
/* 设置fb_info */
/* a. var */
pfb_info->var.xres_virtual = pfb_info->var.xres = dt->hactive.typ;
pfb_info->var.yres_virtual = pfb_info->var.yres = dt->vactive.typ;
pfb_info->var.bits_per_pixel = 24;
pfb_info->var.red.offset = 16;
pfb_info->var.red.length = 8;
pfb_info->var.green.offset = 8;
pfb_info->var.green.length = 8;
pfb_info->var.blue.offset = 0;
pfb_info->var.blue.length = 8;
/* b. fix */
strcpy(pfb_info->fix.id, "fb_lcd");
pfb_info->fix.smem_len = pfb_info->var.xres * pfb_info->var.yres * \
pfb_info->var.bits_per_pixel / 8;
if (pfb_info->var.bits_per_pixel == 24)
pfb_info->fix.smem_len = pfb_info->var.xres * pfb_info->var.yres * 4;
/* fb virtual address */
pfb_info->screen_base = /*dma_alloc_wc(NULL, pfb_info->fix.smem_len, &phy_addr,
GFP_KERNEL); */
dma_alloc_writecombine(pfb_info->device, pfb_info->fix.smem_len, (dma_addr_t *)&pfb_info->fix.smem_start, GFP_DMA | GFP_KERNEL);
/* pfb_info->fix.smem_start = phy_addr; */
phy_addr = pfb_info->fix.smem_start;
pfb_info->fix.type = FB_TYPE_PACKED_PIXELS;
pfb_info->fix.visual = FB_VISUAL_TRUECOLOR;
pfb_info->fix.line_length = pfb_info->var.xres * pfb_info->var.bits_per_pixel / 8;
if (pfb_info->var.bits_per_pixel == 24)
pfb_info->fix.line_length = pfb_info->var.xres * 4;
/* c. fbops */
pfb_info->fbops = &fbops;
pfb_info->pseudo_palette = pseudo_palette;
/* 注册fb_info */
register_framebuffer(pfb_info);
/* 硬件操作 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
lcdif = devm_ioremap_resource(&pdev->dev, res);
lcd_controller_init(lcdif, dt, bpp, 24, phy_addr);
lcd_controller_enable(lcdif);
gpiod_set_value(bl_gpio, 1);
return 0;
}
static int lcd_remove(struct platform_device *pdev)
{
/* 反过来操作 */
/* 反注册fb_info */
unregister_framebuffer(pfb_info);
/* 释放fb_info */
framebuffer_release(pfb_info);
iounmap(plcd_regs);
return 0;
}
static const struct of_device_id lcd_of_match[] = {
{.compatible = "glen,lcd_drv", },
{ },
};
MODULE_DEVICE_TABLE(of, lcd_of_match);
static struct platform_driver lcd_driver = {
.driver = {
.name = "lcd",
.of_match_table = lcd_of_match,
},
.probe = lcd_probe,
.remove = lcd_remove,
};
static int __init lcd_drv_init(void)
{
int ret = 0;
ret = platform_driver_register(&lcd_driver);
return ret;
return 0;
}
static void __exit lcd_drv_exit(void)
{
platform_driver_unregister(&lcd_driver);
}
module_init(lcd_drv_init);
module_exit(lcd_drv_exit);
MODULE_DESCRIPTION("Framebuffer driver for the Linux");
/* insert author information for module */
MODULE_AUTHOR("glen");
/* insert license for module */
MODULE_LICENSE("GPL");
fb_lcd {
compatible = "glen,lcd_drv";
reg = <0x021c8000 0x4000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lcdif_dat
&pinctrl_lcdif_ctrl
>;
backlight-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>;
clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
<&clks IMX6UL_CLK_LCDIF_APB>;
clock-names = "pix", "axi";
display = <&displaya>;
displaya: display {
bits-per-pixel = <24>;
bus-width = <24>;
display-timings {
native-mode = <&timinga>;
timinga: timinga_1024x600 {
clock-frequency = <51200000>;
hactive = <1024>;
vactive = <600>;
hfront-porch = <160>;
hback-porch = <140>;
hsync-len = <20>;
vback-porch = <20>;
vfront-porch = <12>;
vsync-len = <3>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <1>;
pixelclk-active = <0>;
};
};
};
};
/*
* 文件名 : lcd_drv_test.c
* 作者 : glen
* 描述 : lcd_drv_test应用程序
*/
#include "stdio.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include <signal.h>
#include <sys/select.h>
#include <sys/time.h>
#include <linux/input.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <linux/fb.h>
typedef struct _lcd_color {
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char alpha;
} lcd_color;
/**
* @brief : main函数
* @par : argc argv数组元素的个数
* argv 参数数组
* @retval : 0 成功 其它 失败
*/
int main(int argc, char *argv[])
{
int fd = 0;
int ret = 0;
long screensize = 0;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
char *fbp = 0;
int x = 0, y = 0;
long location = 0;
fd = open("/dev/fb0", O_RDWR);
if (fd < 0) {
printf("ERROR: Can't open framebuffer device \n");
exit(1);
}
if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo)) {
printf("Error reading fixed information \n");
exit(2);
}
if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo)) {
printf("Error reading variable information \n");
exit(3);
}
screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if ((int)fbp == -1) {
printf("ERROR: failed to map framebuffer device to memory.\n");
exit(4);
}
int i = 0;
lcd_color clear_color = {0, 0, 255, 0};
clear_color.red = 255;
clear_color.green = 0;
clear_color.blue = 0;
for (i = 0; i < screensize; i += 4)
*((lcd_color *)(fbp + i)) = clear_color;
usleep(1000*2000);
clear_color.red = 0;
clear_color.green = 255;
clear_color.blue = 0;
for (i = 0; i < screensize; i += 4)
*((lcd_color *)(fbp + i)) = clear_color;
usleep(1000*2000);
clear_color.red = 0;
clear_color.green = 0;
clear_color.blue = 255;
for (i = 0; i < screensize; i += 4)
*((lcd_color *)(fbp + i)) = clear_color;
usleep(1000*2000);
munmap(fbp, screensize);
/* 关闭文件 */
ret = close(fd);
return 0;
}
基于正点原子Linux Alpha开发板平台,执行下面操作,按应用程序执行屏幕被刷成红、绿、蓝色
/drv_module # ls
btn_drv_test imx6_io_drv.ko lcd_drv_test
button_drv.ko lcd_drv.ko
/drv_module # insmod lcd_drv.ko
Console: switching to colour frame buffer device 128x37
/drv_module # ./lcd_drv_test