[Linux驱动]----LCD显示驱动

目录

一、LCD驱动框架

1.1LCD驱动概述

1.2Fbmem.c分析

1.3分析底层硬件驱动程序

三、硬件相关操作

3.1硬件引脚

3.2配置硬件引脚

2.映射物理地址:ioremap()

3.配置引脚

3.3调色板

五、参考链接


一、LCD驱动框架

1.1LCD驱动概述

假设

app: open("/dev/fb0", ...) 主设备号: 29, 次设备号: 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分析

"入口函数"——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”是通用的文件,故并不能直接使用这个 file_operations 结构中的.read 等函数,其中创建类并没有在设备类下创建设备,只有在真正有硬件的时候才在这个类创建设备。“register_framebuffer()”中可以看到创建设备。

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

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

fb_open:

APP:open():想知道内存上有什么内容,看file_operations fb_fops结构中

“.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():想知道内存上有什么内容,看file_operations fb_fops结构中

“.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;
}

从上面知道“.open”和“.read”都依赖一个结构体 fb_info 结构体。这个结构体是从数组“registered_fd[]”经“次设备号”为下标得到一项为 fd_info结构体。

struct fb_info *info = registered_fb[fbidx];

问:registered_fb在哪里被设置?

register_framebuffer

分析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分析底层硬件驱动程序

path:/drivers/video/s3c2440.c为例(从入口函数看)

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);
}

要得到LCD的分辨率信息:从上往下看(fbmem.c)

fbmem.c看".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;
        ......
}

info->var中存在屏幕的信息;

 

 二、LCD硬件参数设置

正式开始写驱动之前,先了解硬件的具体操作,如下;

设置LCD控制器时,不同的LCD的"VCLK"不一样,要调整LCD控制器来设置频率的快和慢,硬件操作过程:

1,根据 LCD 手册设置 LCD 控制器。(时钟参数等)

2,分配显存,并将显存的地址告诉 LCD 控制器。 还要说明颜色格式。一个像素用多少个字节来表示等。

3, 2440 相关的操作:配置引脚用于 LCD,芯片的管脚可以配置成输入输出,还可以配置为 LCD 引脚。

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.1硬件引脚

屏幕使用的接口如下:

 

 上面的原理图可知LCD使用的寄存器:GPCx GPGx GPDx,配置引脚使用

背光电路:

“KEYBOARD”引脚:KEYBOARD --- J6 TOUT0/GPB0 (GPB0 要配置成输出引脚:GPBCON |=

GPB0_out;)

3.2配置硬件引脚

1.定义映射引脚

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.映射物理地址:ioremap()

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

ioremap()将一个 IO 地址空间映射到内核的虚拟地址空间上去,便于访问;

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

3.配置引脚

    *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.配置LCD控制器

封装LCD控制器:

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.分配显存

从内存里分配显存(framebuffer),并把地址告诉 LCD 控制器.设置好了 LCD 控制器后,就会自动从显存里取一个像素的值通过 VD0~VD23 发送出像素数据到 LCD 屏上去。然后再取下一个,周而复始,取到显存中最后一个时(显存就那么大)就再到显存开始处取。显存的地址需要连续,不能使用kmolloc()分配显存->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调色板

调色板是一块内存,LCD 控制器从显存里得到的 8bit 数据,此时并不是直接发给 LCD 屏。而是发给调色板,调色板是块内存,里面存放了真正的 16bit 数据。

LCD 控制器从显存得到 8bit 数据后,以它作为 索引,假如一个 8 位是 100,这时就从“调色板”(像个数组)中找到 100 项,把这个第 100 项处的 16bit 取出来。发送给 LCD 屏。

调色板就像一个里面放好颜色的颜料盒,要取哪种颜色就通过 索引来检索出来哪个颜色在调色板这个颜色盒的哪里,找到后拿出来。这里调色板是块内存,事先配置好几种颜色,LCD 控制器要用时,从显存中读到一个 8 bit 数据,以此 8bit 数据作为一个“索引”值,从“调色板”这个数组里取出一个真正的颜色,再把这个真正的颜色发送给 LCD 硬件。

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;
}

五、参考链接

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

猜你喜欢

转载自blog.csdn.net/weixin_45281868/article/details/126736261