1 Application Layer Calls to LCD Driver Methods
Open the corresponding device node
app: open(“/dev/fb0”, …) Major device number: 29, Minor device number: 0
————————————————————–
kernel:
fb_open // fbmem.c
struct fb_info *info;
info = get_fb_info(fbidx);
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1); // Hardware-related driver
if (res)
module_put(info->fbops->owner);
}
Obtain variable information (including resolution, etc.)
app: ioctl(fd, FBIOGET_VSCREENINFO, &fb_info->var);
————————————————————————-
kernel:
fb_ioctl // fbmem.c
struct fb_info *info = file_fb_info(file);
do_fb_ioctl(info, cmd, arg);
var = info->var; // Hardware-related driver settings
ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
Obtain fixed information (including video memory information)
app: ioctl(fd, FBIOGET_FSCREENINFO, &fb_info->fix);
————————————————————————-
kernel:
fb_ioctl // fbmem.c
struct fb_info *info = file_fb_info(file);
do_fb_ioctl(info, cmd, arg);
fix = info->fix; // Hardware-related driver settings
ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
mmap function corresponding operations and implementations
app: void *ptr = mmap(0,
fb_info->var.yres_virtual * fb_info->fix.line_length,
PROT_WRITE | PROT_READ,
MAP_SHARED, fd, 0);
————————————————————————-
kernel:
fb_mmap // fbmem.c
struct fb_info *info = file_fb_info(file);
start = info->fix.smem_start;
len = info->fix.smem_len;
return vm_iomap_memory(vma, start, len);
2 TFT-RGB Interface Corresponding LCD Timing
TFT-RGB Interface Schematic

TFT-RGB Interface Timing Diagram

LCD Controller Timing Diagram


3 Linux Driver Framework and Implementation
Linux Driver = Driver Framework + Hardware Programming.
Previously, we have written an LCD driver based on QEMU, and the framework of the LCD driver has been clearly analyzed.
The core is:
* Allocate fb_info
* Set fb_info
* Register fb_info
* Hardware-related settings
Entry function registers platform_driver

Device tree has corresponding nodes


3 Implementation of Corresponding Driver Code
#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/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 *mylcd_regs;
static struct fb_info *myfb_info;
static unsigned int pseudo_palette[16];
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 lcd_data_bus_width;
int fb_width;
int vsync_pol = 0;
int hsync_pol = 0;
int de_pol = 0;
int clk_pol = 0;
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)
lcd_data_bus_width = 0x3;
else if (lcd_bpp == 18)
lcd_data_bus_width = 0x2;
else if (lcd_bpp == 8)
lcd_data_bus_width = 0x1;
else if (lcd_bpp == 16)
lcd_data_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;
/*
* Initialize the CTRL register of the LCD controller
* [19] : 1 : DOTCLK and DVI modes need to be set to 1
* [17] : 1 : Set to 1 to work in DOTCLK mode
* [15:14] : 00 : Input data not swapped (little-endian mode) defaults to 0, no need to set
* [13:12] : 00 : CSC data not swapped (little-endian mode) defaults to 0, no need to set
* [11:10] : 11 : Data bus is 24bit
* [9:8] Based on the display resource file bpp: 8-bit 0x1, 16-bit 0x0, 24-bit 0x3
* [5] : 1 : Set elcdif to work in host mode
* [1] : 0 : 24-bit data are all valid data, defaults to 0, no need to set
*/
lcdif->CTRL = (0<<30) | (0<<29) | (0<<28) | (1<<19) | (1<<17) | (lcd_data_bus_width << 10) |
(fb_width << 8) | (1<<5);
/*
* Set the CTRL1 register of ELCDIF
* Based on bpp settings, only set when bpp is 24 or 32
* [19:16] : 111 : indicates ARGB transmission format mode, transmitting 24-bit uncompressed data, A channel does not need to be transmitted)
*/
if(fb_bpp == 24 || fb_bpp == 32)
{
lcdif->CTRL1 &= ~(0xf << 16);
lcdif->CTRL1 |= (0x7 << 16);
}
else
lcdif->CTRL1 |= (0xf << 16);
/*
* Set the TRANSFER_COUNT register of ELCDIF
* [31:16] : Number of pixels in the vertical direction
* [15:0] : Number of pixels in the horizontal direction
*/
lcdif->TRANSFER_COUNT = (dt->vactive.typ << 16) | (dt->hactive.typ << 0);
/*
* Set the VDCTRL0 register of ELCDIF
* [29] 0 : VSYNC output, defaults to 0, no need to set
* [28] 1 : In DOTCLK mode, set to 1 to enable hardware to generate ENABLE output
* [27] 0 : VSYNC low level valid, set to 0 according to screen configuration file
* [26] 0 : HSYNC low level valid, set to 0 according to screen configuration file
* [25] 1 : DOTCLK falling edge valid, set to 1 according to screen configuration file
* [24] 1 : ENABLE signal high level valid, set to 1 according to screen configuration file
* [21] 1 : Frame synchronization cycle unit, set to 1 in DOTCLK mode
* [20] 1 : Frame synchronization pulse width unit, set to 1 in DOTCLK mode
* [17:0] : vysnc pulse width
*/
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);
/*
* Set the VDCTRL1 register of ELCDIF
* Set the total cycle in the vertical direction: upper black frame tvb + vertical synchronization pulse tvp + vertical effective height yres + lower black frame tvf
*/
lcdif->VDCTRL1 = dt->vback_porch.typ + dt->vsync_len.typ + dt->vactive.typ + dt->vfront_porch.typ;
/*
* Set the VDCTRL2 register of ELCDIF
* [18:31] : Width of horizontal synchronization signal pulse
* [17: 0] : Total cycle in the horizontal direction
* Set the total cycle in the horizontal direction: left black frame thb + horizontal synchronization pulse thp + horizontal effective height xres + right black frame thf
*/
lcdif->VDCTRL2 = (dt->hsync_len.typ << 18) | (dt->hback_porch.typ + dt->hsync_len.typ + dt->hactive.typ + dt->hfront_porch.typ);
/*
* Set the VDCTRL3 register of ELCDIF
* [27:16] :Number of waiting clocks in the horizontal direction = thb + thp
* [15:0] : Number of waiting clocks in the vertical direction = tvb + tvp
*/
lcdif->VDCTRL3 = ((dt->hback_porch.typ + dt->hsync_len.typ) << 16) | (dt->vback_porch.typ + dt->vsync_len.typ);
/*
* Set the VDCTRL4 register of ELCDIF
* [18] Use VSHYNC, HSYNC, DOTCLK mode this is set to 1
* [17:0] : Width in the horizontal direction
*/
lcdif->VDCTRL4 = (1<<18) | (dt->hactive.typ);
/*
* Set the CUR_BUF and NEXT_BUF registers of ELCDIF
* CUR_BUF : Current video memory address
* NEXT_BUF : Next frame video memory address
* For convenience, both are set to the same video memory address
*/
lcdif->CUR_BUF = fb_phy;
lcdif->NEXT_BUF = fb_phy;
return 0;
}
/* from pxafb.c */
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 mylcd_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-colour, 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; /* unknown type */
}
return 0;
}
static struct fb_ops myfb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = mylcd_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
static int mylcd_probe(struct platform_device *pdev)
{
struct device_node *display_np;
dma_addr_t phy_addr;
int ret;
int width;
int bits_per_pixel;
struct display_timings *timings = NULL;
struct display_timing *dt = NULL;
struct imx6ull_lcdif *lcdif;
struct resource *res;
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”,
&bits_per_pixel);
/* get timing */
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);
/* 1.1 Allocate fb_info */
myfb_info = framebuffer_alloc(0, NULL);
/* 1.2 Set fb_info */
/* a. var : LCD resolution, color format */
myfb_info->var.xres_virtual = myfb_info->var.xres = dt->hactive.typ;
myfb_info->var.yres_virtual = myfb_info->var.yres = dt->vactive.typ;
myfb_info->var.bits_per_pixel = 16; /* rgb565 */
myfb_info->var.red.offset = 11;
myfb_info->var.red.length = 5;
myfb_info->var.green.offset = 5;
myfb_info->var.green.length = 6;
myfb_info->var.blue.offset = 0;
myfb_info->var.blue.length = 5;
/* b. fix */
strcpy(myfb_info->fix.id, “100ask_lcd”);
myfb_info->fix.smem_len = myfb_info->var.xres * myfb_info->var.yres * myfb_info->var.bits_per_pixel / 8;
if (myfb_info->var.bits_per_pixel == 24)
myfb_info->fix.smem_len = myfb_info->var.xres * myfb_info->var.yres * 4;
/* fb’s virtual address */
myfb_info->screen_base = dma_alloc_writecombine(NULL, myfb_info->fix.smem_len, &phy_addr,
GFP_KERNEL);
myfb_info->fix.smem_start = phy_addr; /* fb’s physical address */
myfb_info->fix.type = FB_TYPE_PACKED_PIXELS;
myfb_info->fix.visual = FB_VISUAL_TRUECOLOR;
myfb_info->fix.line_length = myfb_info->var.xres * myfb_info->var.bits_per_pixel / 8;
if (myfb_info->var.bits_per_pixel == 24)
myfb_info->fix.line_length = myfb_info->var.xres * 4;
/* c. fbops */
myfb_info->fbops = &myfb_ops;
myfb_info->pseudo_palette = pseudo_palette;
/* 1.3 Register fb_info */
register_framebuffer(myfb_info);
/* 1.4 Hardware operations */
//lcdif = ioremap(0x021C8000, sizeof(*lcdif));
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
lcdif = devm_ioremap_resource(&pdev->dev, res);
lcd_controller_init(lcdif, dt, bits_per_pixel, 16, phy_addr);
lcd_controller_enable(lcdif);
gpiod_set_value(bl_gpio, 1);
return 0;
}
static int mylcd_remove(struct platform_device *pdev)
{
/* Reverse operations */
/* 2.1 Unregister fb_info */
unregister_framebuffer(myfb_info);
/* 2.2 Free fb_info */
framebuffer_release(myfb_info);
iounmap(mylcd_regs);
return 0;
}
static const struct of_device_id mylcd_of_match[] = {
{ .compatible = “100ask,lcd_drv”, },
{ },
};
MODULE_DEVICE_TABLE(of, mylcd_of_match);
static struct platform_driver mylcd_driver = {
.driver = {
.name = “mylcd”,
.of_match_table = mylcd_of_match,
},
.probe = mylcd_probe,
.remove = mylcd_remove,
};
static int __init lcd_drv_init(void)
{
int ret;
ret = platform_driver_register(&mylcd_driver);
if (ret)
return ret;
return 0;
}
/* 2. Exit */
static void __exit lcd_drv_exit(void)
{
platform_driver_unregister(&mylcd_driver);
}
module_init(lcd_drv_init);
module_exit(lcd_drv_exit);
MODULE_AUTHOR(“www.100ask.net”);
MODULE_DESCRIPTION(“Framebuffer driver for Linux”);
MODULE_LICENSE(“GPL”);
4 Specific Loading Phenomena
