Detailed Explanation of LCD Driver in Embedded Linux Kernel

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

Detailed Explanation of LCD Driver in Embedded Linux Kernel

TFT-RGB Interface Timing Diagram

Detailed Explanation of LCD Driver in Embedded Linux Kernel

LCD Controller Timing Diagram

Detailed Explanation of LCD Driver in Embedded Linux KernelDetailed Explanation of LCD Driver in Embedded Linux Kernel

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

Detailed Explanation of LCD Driver in Embedded Linux Kernel

Device tree has corresponding nodes

Detailed Explanation of LCD Driver in Embedded Linux KernelDetailed Explanation of LCD Driver in Embedded Linux Kernel

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

Detailed Explanation of LCD Driver in Embedded Linux Kernel

Leave a Comment