[Linux driver]----LCD display driver

Table of contents

1. LCD driver framework

1.1LCD driver overview

1.2Fbmem.c analysis

1.3 Analyze the underlying hardware driver

3. Hardware related operations

3.1 Hardware pins

3.2 Configure hardware pins

2. Mapping physical address: ioremap()

3.Configuration pins

3.3 Palette

5. Reference links


1. LCD driver framework

1.1LCD driver overview

hypothesis

app: open("/dev/fb0", ...) Major device number: 29, minor device number: 0

--------------------------------------------------------------

kernel:

fb_open

int fbidx = iminor(inode);

struct fb_info *info = = registered_fb[0];

app: read()

---------------------------------------------------------------

kernel:

fb_read

int fbidx = iminor(inode);

struct fb_info *info = registered_fb[fbidx];

if (info->fbops->fb_read)

return info->fbops->fb_read(info, buf, count, ppos);

src = (u32 __iomem *) (info->screen_base + p);

dst = buffer;

*dst++ = fb_readl(src++);

copy_to_user(buf, buffer, c)

 

1.2Fbmem.c analysis

"Entry function"—— fbmem_init:

fbmem_init(void)
{
    create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);

    if (register_chrdev(FB_MAJOR,"fb",&fb_fops))  //创建设备
        printk("unable to get major %d for fb devs\n", FB_MAJOR);

    fb_class = class_create(THIS_MODULE, "graphics");//创建类
    if (IS_ERR(fb_class)) {
        printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
        fb_class = NULL;
    }
    return 0;
}

"fbmem.c" is a general file, so you cannot directly use the .read and other functions in the file_operations structure. The creation class does not create devices under the device class. Devices are created in this class only when there is real hardware. . The created device can be seen in "register_framebuffer()".

fb_info->dev = device_create(fb_class, fb_info->device,

        MKDEV(FB_MAJOR, i), "fb%d", i);

fb_open:

APP:open(): If you want to know what is in the memory, look at the file_operations fb_fops structure

“.read = fb_open”

static int
fb_open(struct inode *inode, struct file *file)
{
    int fbidx = iminor(inode); //次设备号
    struct fb_info *info;
    int res = 0;
    .........
    if (!(info = registered_fb[fbidx]))  //从registered_fb数组中赋值给info
        return -ENODEV;
    ......
    if (info->fbops->fb_open) {
        res = info->fbops->fb_open(info,1);
        if (res)
            module_put(info->fbops->owner);
    }
    return res;
}

fb_read:

APP:read(): If you want to know what is in the memory, look at the file_operations fb_fops structure

“.read = fb_read”

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    int fbidx = iminor(inode);
    struct fb_info *info = registered_fb[fbidx];
    ......
    if (info->fbops->fb_read)
        return info->fbops->fb_read(info, buf, count, ppos);
    /*没有read函数*/
    total_size = info->screen_size; //屏幕大小
    .....
    
    buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
             GFP_KERNEL); //分配缓冲区
    if (!buffer)
        return -ENOMEM;

    src = (u32 __iomem *) (info->screen_base + p);  /*screen_base 是指
    显存的基地址。这里是读源 src 等于显存的*/

    while (count) {
        .....
        dst = buffer;
        for (i = c >> 2; i--; )
            *dst++ = fb_readl(src++);/*读源(从显存基地址+P 偏移)那里读到一 
个数据放到目标“*dst++”里。dst 是 buffer,buffer 是 kmalloc()上面分 
配的空间。*/
        ..........
        if (copy_to_user(buf, buffer, c)) { //把数据拷贝到用户空间
            err = -EFAULT;
            break;
        }
        ......
    }

    kfree(buffer);

    return (err) ? err : cnt;
}

From the above, we know that ".open" and ".read" both depend on a structure, the fb_info structure. This structure is an fd_info structure obtained from the array "registered_fd[]" with the "minor device number" as the subscript.

struct fb_info *info = registered_fb[fbidx];

Q: Where is registered_fb set?

register_framebuffer

Analyze register_framebuffer:

int
register_framebuffer(struct fb_info *fb_info)
{
    int i;
    struct fb_event event;
    struct fb_videomode mode;
    ............
    for (i = 0 ; i < FB_MAX; i++) //FB_MAX:32
        if (!registered_fb[i]) //找出一个空项
            break;
    fb_info->node = i;

    fb_info->dev = device_create(fb_class, fb_info->device,
                     MKDEV(FB_MAJOR, i), "fb%d", i);/*在fb_class类下
   创建设备*/
      ..........
}

1.3 Analyze the underlying hardware driver

path:/drivers/video/s3c2440.c as an example (from the entry function)

int __devinit s3c2410fb_init(void)
{
    //注册一个平台驱动(总线设备驱动模型)
    return platform_driver_register(&s3c2410fb_driver);
}

s3c2410fb_probe:

static int __init s3c2410fb_probe(struct platform_device *pdev)
{
    struct s3c2410fb_info *info;
    struct fb_info     *fbinfo;
    struct s3c2410fb_hw *mregs;
    int ret;
    int irq;
    int i;
    u32 lcdcon1;

    mach_info = pdev->dev.platform_data;//根据pdev获得"mach_info"信息

    mregs = &mach_info->regs;

    irq = platform_get_irq(pdev, 0);//根据pdev获得"irq "中断信息

    fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), 
                                &pdev->dev);
    if (!fbinfo) {
        return -ENOMEM;
    }
     /*硬件相关设置*/
    info = fbinfo->par;
    info->fb = fbinfo;
    info->dev = &pdev->dev;

    ......

    /* Stop the video and unset ENVID if set */
    info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
    lcdcon1 = readl(S3C2410_LCDCON1);
    writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
    /*硬件相关的操作:*/
    s3c2410_gpio_setpin(S3C2410_GPB0, 0); // back light control 

    info->mach_info         = pdev->dev.platform_data;

    fbinfo->fix.type        = FB_TYPE_PACKED_PIXELS;
    fbinfo->fix.type_aux        = 0;
    fbinfo->fix.xpanstep        = 0;
    fbinfo->fix.ypanstep        = 0;
    fbinfo->fix.ywrapstep       = 0;
    fbinfo->fix.accel       = FB_ACCEL_NONE;

    fbinfo->var.nonstd      = 0;
    fbinfo->var.activate        = FB_ACTIVATE_NOW;
    fbinfo->var.height      = mach_info->height;
    fbinfo->var.width       = mach_info->width;
    fbinfo->var.accel_flags     = 0;
    fbinfo->var.vmode       = FB_VMODE_NONINTERLACED;

    fbinfo->fbops           = &s3c2410fb_ops;
    fbinfo->flags           = FBINFO_FLAG_DEFAULT;
    fbinfo->pseudo_palette      = &info->pseudo_pal;

    fbinfo->var.xres        = mach_info->xres.defval;
    fbinfo->var.xres_virtual    = mach_info->xres.defval;
    fbinfo->var.yres        = mach_info->yres.defval;
    fbinfo->var.yres_virtual    = mach_info->yres.defval;
    fbinfo->var.bits_per_pixel  = mach_info->bpp.defval;

    fbinfo->var.upper_margin    = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) + 1;
    fbinfo->var.lower_margin    = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) + 1;
    fbinfo->var.vsync_len       = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;

    fbinfo->var.left_margin     = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
    fbinfo->var.right_margin    = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
    fbinfo->var.hsync_len       = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;

    fbinfo->var.red.offset      = 11;
    fbinfo->var.green.offset    = 5;
    fbinfo->var.blue.offset     = 0;
    fbinfo->var.transp.offset   = 0;
    fbinfo->var.red.length      = 5;
    fbinfo->var.green.length    = 6;
    fbinfo->var.blue.length     = 5;
    fbinfo->var.transp.length   = 0;
    fbinfo->fix.smem_len        =   mach_info->xres.max *
                    mach_info->yres.max *
                    mach_info->bpp.max / 8;
    ........

    /* Initialize video memory */
    ret = s3c2410fb_map_video_memory(info);
    if (ret) {
        printk( KERN_ERR "Failed to allocate video RAM: %d\n", ret);
        ret = -ENOMEM;
        goto release_clock;
    }
    .........
    return 0;
    .........
release_clock:
    clk_disable(info->clk);
    clk_put(info->clk);
}

To get the resolution information of the LCD: look from top to bottom (fbmem.c)

fbmem.c looks at ".ioctl"

fbmem_init->fb_ops->ioctl(fb_ioctl)

static int 
fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
     unsigned long arg)
{
        int fbidx = iminor(inode);
        struct fb_info *info = registered_fb[fbidx];
        ......
        case FBIOGET_VSCREENINFO:/*GET 获得.V(var)可变的.SCREEN 屏幕.INFO 信 
息*/
        return copy_to_user(argp, &info->var,
                    sizeof(var)) ? -EFAULT : 0;
        ......
}

There is screen information in info->var;

 

 2. LCD hardware parameter settings

Before officially starting to write the driver, first understand the specific operations of the hardware, as follows;

 

When setting the LCD controller, the "VCLK" of different LCDs is different. You need to adjust the LCD controller to set the frequency fast or slow. The hardware operation process is:

1. Set up the LCD controller according to the LCD manual. (clock parameters, etc.)

2. Allocate video memory and tell the LCD controller the address of the video memory. Also explain the color format. How many bytes are used to represent a pixel, etc.

3. 2440 related operations: Configure pins for LCD. The pins of the chip can be configured as input and output, and can also be configured as LCD pins.

static struct fb_info *s3c_led;

static void lcd_setting(void)
{
    /* 2.1 设置固定的参数 */
    strcpy(s3c_led->fix.id,"mylcd");       //名字
    s3c_led->fix.smem_len = 240*320*16/8;  //显存的长度
    s3c_led->fix.typ      = FB_TYPE_PACKED_PIXELS;
    s3c_led->fix.visual   = FB_VISUAL_TRUECOLOR;  /*TFT*/
    s3c_led->fix.line_length = 240*16/8; //固定信息里面,一行的长度

    /* 2.2 设置可变的参数 */
    s3c_led->var.xres           = 240; //x方向的分辨率240
    s3c_led->var.yres           = 320; //y方向的分辨率240
    s3c_led->var.xres_virtual   = 240; //x方向的虚拟分辨率240
    s3c_led->var.yres_virtual   = 320; //y方向的虚拟分辨率240
    s3c_led->bits_per_pixel     = 16;  //BPP

    /* RGB:565 */
    s3c_lcd->var.red.offset     = 11; //RGB---565:红色的偏移值从bit11开始
    s3c_lcd->var.red.length     = 5;  //RGB---565,红色占5位
    
    s3c_lcd->var.green.offset   = 5; //RGB---565:红色的偏移值从bit5开始
    s3c_lcd->var.green.length   = 6; //RGB---565,红色占6位

    s3c_lcd->var.blue.offset    = 0; //RGB---565:红色的偏移值从bit0开始
    s3c_lcd->var.blue.length    = 5; //RGB---565,红色占5位
    
    s3c_lcd->var.activate = FB_ACTIVATE_NOW;  //默认值
    /* 2.3 设置操作函数 */
    s3c_lcd->fbops        = &s3c_lcdfb_ops; 
    /* 2.4 其他的设置 */
    //s3c_lcd->pseudo_palette =; //
    //s3c_lcd->screen_base  = ;  /* 显存的虚拟地址 */ 
    s3c_lcd->screen_size   = 240*324*16/8;  //显存的大小
}

3. Hardware related operations

3.1 Hardware pins

The interface used by the screen is as follows:

 

 The above schematic diagram shows that the registers used by the LCD are: GPCx GPGx GPDx, and the configuration pins are used

Backlight circuit:

"KEYBOARD" pin: KEYBOARD --- J6 TOUT0/GPB0 (GPB0 should be configured as an output pin: GPBCON |=

GPB0_out;)

3.2 Configure hardware pins

1. Define mapping pins

static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;

2. Mapping physical address: ioremap()

void * __ioremap(unsigned long phys_addr, unsigned long size)

ioremap() maps an IO address space to the kernel's virtual address space for easy access;

    gpbcon = ioremap(0x56000010, 8);
    gpbdat = gpbcon+1;
    gpccon = ioremap(0x56000020, 4);
    gpdcon = ioremap(0x56000030, 4);
    gpgcon = ioremap(0x56000060, 4);

3.Configuration pins

    *gpccon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
    *gpdcon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[23:8] */
    
    *gpbcon &= ~(3);  /* GPB0设置为输出引脚 */
    *gpbcon |= 1;
    *gpbdat &= ~1;     /* 输出低电平,不开启背光 */

    *gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */

a. Configure LCD controller

Packaged LCD controller:

struct lcd_regs {
    unsigned long   lcdcon1;
    unsigned long   lcdcon2;
    unsigned long   lcdcon3;
    unsigned long   lcdcon4;
    unsigned long   lcdcon5;
    unsigned long   lcdsaddr1;
    unsigned long   lcdsaddr2;
    unsigned long   lcdsaddr3;
    unsigned long   redlut;
    unsigned long   greenlut;
    unsigned long   bluelut;     // 0X4D000028 STN:蓝色查找表
    unsigned long   reserved[9]; //保留字节
    unsigned long   dithmode;    // 0X4D00004C STN:抖动模式
    unsigned long   tpal;
    unsigned long   lcdintpnd;
    unsigned long   lcdsrcpnd;
    unsigned long   lcdintmsk;
    unsigned long   lpcsel;
};
static volatile struct lcd_regs *lcd_regs; //结构体指针
lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));

b. Allocate video memory

Allocate video memory (framebuffer) from the memory and tell the address to the LCD controller. After setting up the LCD controller, it will automatically take a pixel value from the video memory and send the pixel data to the LCD screen through VD0~VD23. Then take out the next one, and start over again. When you get the last one in the video memory (the video memory is only that big), you go to the beginning of the video memory. The addresses of the video memory need to be consecutive. You cannot use kmolloc() to allocate video memory ->dma_alloc_writecombine()

static inline 
void dma_alloc_writecombine(struct device *dev, size_t size,dma_addr_t *dma_addr, gfp_t gfp)
{
return dma_alloc_attrs(dev, size, dma_addr, gfp,
                        DMA_ATTR_WRITE_COMBINE);
}
s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, 
                    &s3c_lcd->fix.smem_start, GFP_KERNEL);  //分配显存

lcd_regs->lcdsaddr1  = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);//lcdsaddr1显存地址
lcd_regs->lcdsaddr2  = ((s3c_lcd->fix.smem_start + 
                        s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;lcdsaddr2帧缓冲器地址
lcd_regs->lcdsaddr3 = (240*16/16); 

/* 启动LCD */
lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器  */
lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
*gpbdat |= 1;     /* 输出高电平, 使能背光 */

3.3 Palette

The palette is a piece of memory, and the 8-bit data obtained by the LCD controller from the video memory is not directly sent to the LCD screen at this time. Instead, it is sent to the palette, which is a block of memory that stores real 16bit data.

After the LCD controller gets the 8-bit data from the display memory, it uses it as an index. If an 8-bit value is 100, then it finds the 100th item from the "palette" (like an array) and takes the 16bit at the 100th item. come out. Sent to LCD screen.

The palette is like a paint box with colors in it. Which color you want to pick is retrieved through the index to find out which color is in the color box of the palette, and take it out after finding it. The palette here is a block of memory, with several colors configured in advance. When the LCD controller needs to use it, it reads an 8-bit data from the display memory, and uses this 8-bit data as an "index" value, from the "palette" array Take a real color out of it, and then send this real color to the LCD hardware.

static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
                 unsigned int green, unsigned int blue,
                 unsigned int transp, struct fb_info *info)
{
    unsigned int val;
    
    if (regno > 16)
        return 1;

    /* 用red,green,blue三原色构造出val */
    val  = chan_to_field(red,   &info->var.red);
    val |= chan_to_field(green, &info->var.green);
    val |= chan_to_field(blue,  &info->var.blue);
    
    //((u32 *)(info->pseudo_palette))[regno] = val;
    pseudo_palette[regno] = val;
    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;
}

5. Reference links

https://gitee.com/change-ly/linux_project

Guess you like

Origin blog.csdn.net/weixin_45281868/article/details/126736261