韦东山2440-学习笔记-framebuffer

1. framebuffer框架简述

1.1 init

init完成主设备号申请,cdev创建并加入系统,设置了fops,但这个fops只是虚函数

fbmem_init
  register_chrdev

1.2 open

fb_open
  fbidx = iminor(inode);
  info = registered_fb[fbidx];
  info->fbops->fb_open(info,1);

可见 registered_fb[] 记录了真正的驱动

1.3 read

fb_read
  fbidx = iminor(inode);
  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);
  while (count) {
    *dst++ = fb_readl(src++);
  }

如果具体的驱动没有实现read,则使用通用 的read,即直接拷贝frame_buffer的数据

1.4 注册 framebuffer

由上分析可知,fbmem只是中间层,具体的驱动在 registered_fb[]。
谁写 registered_fb[] ?

register_framebuffer(struct fb_info *fb_info)
  fb_info->dev = device_create(fb_class, fb_info->device,
                       MKDEV(FB_MAJOR, i), "fb%d", i);  // 添加设备,就会创建设备节点/dev/fb0 /dev/fdb1 之类
  registered_fb[i] = fb_info;  // 注册到  registered_fb[] ,应用层就可以调用

所以驱动的写步骤是:

  1. 分配fb_info
  2. 设置fb_info
  3. 注册register_framebuffer

2. LCD硬件知识


涉及的硬件有:
LCD控制器:通过SoC寄存器设置,让LCD控制器满足LCD能支持的时序,等
LCD:只需要设置引脚保证供电
LCD背光:需要设置引脚保证供电
DMA:Linux有模块会设置,写LCD驱动不需要管
GPIO控制器:

2.2 引脚

首先看LCD的手册

  • 所有引脚都需要设置为LCD相关
  • SoC使用了 16 根数据线,说明一个像素点最多能用16bit描述
    背光的开关

    LCD的开关

2.1 时序

LCD 控制的时序

LCD的时序

需要将LCD的时序和LCD控制器的时序结合看,找到两者关系,设置LCD控制器的时序让其满足LCD的时序。

LCD需要那种时序值,根据LCD手册

2.3 极性

还是结合LCD和控制器的时序图,可知极性是否需翻转

2.4 像素格式

根据LCD芯片手册知道,LCD支持32bpp,但是根据电路图,可知只是用了16位,所以最多使用 16bpp。
像素数据在内存的存放格式,只与LCD控制器有关,所以看SoC手册。
发现16bpp支持两种排布,可按照需要选择一种,并设置相关寄存器,

2. 代码

LCD寄存器设置太多,具体描述都放到代码中

#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/fb.h>
#include <linux/bitops.h>
#include <linux/input.h>
#include <linux/spinlock.h>
#include <asm/atomic.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm-arm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

struct s3c_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;

        unsigned long reserve[9];

        unsigned long dithmode;
        unsigned long tpal;
        unsigned long lcdintpnd;
        unsigned long lcdsrcpnd;
        unsigned long lcdintmsk;
        unsigned long tconsel;
};

static struct fb_info *s3c_info;

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

struct s3c_lcd_regs *s3c_lcd_regs;

static unsigned int pseudo_palette[16];

static int s3c_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
                   u_int trans, struct fb_info *info);


// 显存操作函数,使用其他模块的实现
static struct fb_ops s3c_ops = {
        .owner          = THIS_MODULE,
        .fb_setcolreg = s3c_setcolreg,
        .fb_fillrect    = cfb_fillrect,
        .fb_copyarea    = cfb_copyarea,
        .fb_imageblit   = cfb_imageblit,
};

static inline unsigned int
chan_to_field(unsigned int chan, const struct fb_bitfield *bf)
{
        chan &= 0xffff;
        chan >>= 16 - bf->length;
        return chan << bf->offset;
}

// 设置调色板
static int
s3c_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
                   u_int trans, struct fb_info *info)
{
        unsigned int val;

        if (regno > 16)
                return -1;
        //pal = fbi->fb.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);

        pseudo_palette[regno] = val;

        return 0;
}

#define SCREEN_W        480
#define SCREEN_H        272


static int __init
lcd_dev_init(void)
{
        // 1. 分配fb_info
        s3c_info = framebuffer_alloc(0, NULL);

        // 2. 设置fb_info
        // 2.1 设置固定参数
        strcpy(s3c_info->fix.id, "mylcd");
        s3c_info->fix.smem_len = SCREEN_W * SCREEN_H * 2;        // framebuffer 的总长度, 一共 480 * 272 个像素,每个像素使用 16bit表示,也就是 2Byte
        s3c_info->fix.type_aux = FB_TYPE_PACKED_PIXELS; // 确定像素点的存储方式,存储方式决定了存储和传输速度和图像处理性能消耗, 优化时考虑,所以使用默认值
        s3c_info->fix.visual = FB_VISUAL_TRUECOLOR; // 确定可视化类型定义,使用真彩色
        s3c_info->fix.xpanstep = 0; // 表示每次水平滚动时,显示器位置相对于显存位置的增量,可以设为水平像素行占用多少字节,以实现显存相对显示器的滚动,不使用时设置为0
        s3c_info->fix.ypanstep = 0; // 同上
        s3c_info->fix.line_length = 2* SCREEN_W; // 一行像素占用的字节数, 使用 16bpp 所以一个像素占据2Byte,分辨率为 480*272,所以一行占用字节为 2*272
        //s3c_info->fix.mmio_start = ; // 显卡的显存基地址,是物理地址,没有显卡所以不设置
        //s3c_info->fix.mmio_len = ; //
        //s3c_info->fix.accel = ; // 像驱动表明我们使用的硬件

        // 2.2 设置可变参数
        s3c_info->var.xres = SCREEN_W; // 硬件分辨率
        s3c_info->var.yres = SCREEN_H;
        s3c_info->var.xres_virtual = SCREEN_W; // 在虚拟内存空间中,一行有多少像素, xres_virtual 可以大于 xres(如实现屏幕缩放和滚动) 也可以小于 xres (实现分页和双缓冲)
        s3c_info->var.yres_virtual = SCREEN_H;
        s3c_info->var.xoffset = 0; // 表示在显存中的显示区域, 其实位置为 (xoffset, yoffset), 结束位置为 (xoffset + xres, yoffset + yres)
        s3c_info->var.yoffset = 0;
        s3c_info->var.bits_per_pixel = 16; // 也就是 bpp,我们使用16bpp即一个像素占用16位
        s3c_info->var.grayscale = 0; // 是否为灰度模式,非0,为灰度模式,显示黑白,0为彩色模式

        // 颜色分量在像素中的位数和位置
        // [5:6:5]
        // [11:5:0]
        s3c_info->var.red.offset = 11;
        s3c_info->var.red.length = 5;
        s3c_info->var.red.msb_right = 0;

        s3c_info->var.green.offset = 5;
        s3c_info->var.green.length = 6;
        s3c_info->var.green.msb_right = 0;

        s3c_info->var.blue.offset = 0;
        s3c_info->var.blue.length = 5;
        s3c_info->var.blue.msb_right = 0;

        //s3c_info->var.transp = ; // 透明度分量

        s3c_info->var.nonstd = 0; // 是否是非标准格式RGB,不是,我们使用 RGB5:6:5,是标准的

        /*
         * FB_ACTIVATE_NOW:立即激活新的帧缓冲区,不等待垂直同步信号。这种方式可以实现无闪烁的页面刷新,但可能会导致图像出现撕裂现象。
        * FB_ACTIVATE_FORCE:强制激活新的帧缓冲区,无论是否已经接收到垂直同步信号。这种方式可以避免图像出现撕裂现象,但可能会导致屏幕闪烁。
        * FB_ACTIVATE_VBL:在下一次垂直同步信号到来时激活新的帧缓冲区。这种方式可以保证图像不会出现撕裂现象,但可能会导致页面刷新速度较慢。
        */
//      s3c_info->var.activate = FB_ACTIVATE_NOW; // 控制显示设备的激活状态
        s3c_info->var.activate = FB_ACTIVATE_VBL; // 控制显示设备的激活状态

        // MARK
        s3c_info->var.height = SCREEN_H; // 帧缓冲区高度
        s3c_info->var.width = SCREEN_W;

        //s3c_info->var. timing 部分设置了也没用

        // 2.3 设置操作函数
        s3c_info->fbops = &s3c_ops;

        // 2.4 其他设置
        //s3c_info->screen_base = ;  // 显存的虚拟地址
        s3c_info->screen_size = SCREEN_W * SCREEN_H * 2;
        s3c_info->pseudo_palette = pseudo_palette;


        // 3. 硬件相关
        // 3.1 设置GPIO支持LCD
        // VD3-VD7   GPC11-GPC15
        // VD15-VD23 GPD7-GPD15

        gpccon = ioremap(0x56000020, sizeof(unsigned long));
        gpdcon = ioremap(0x56000030, sizeof(unsigned long));

        *gpccon &= ~((3 << 11*2) | (3 << 12*2) | (3 << 13*2) | (3 << 14*2) | (3 << 15*2));
        *gpccon |= (2 << 11*2) | (2 << 12*2) | (2 << 13*2) | (2 << 14*2) | (2 << 15*2);


        *gpdcon &= ~(((3 << 7*2) | (3 << 8*2) | (3 << 9*2) | (3 << 10*2) |
                                                (3 << 11*2) | (3 << 12*2) | (3 << 13*2) | (3 << 14*2) |
                                                (3 << 15 *2)));
        *gpdcon |= ((2 << 7*2) | (2 << 8*2) | (2 << 9*2) | (2 << 10*2) |
                                                (2 << 11*2) | (2 << 12*2) | (2 << 13*2) | (2 << 14*2) |
                                                (2 << 15 *2));

        // 像素点移动信号       VCLK            GPC1
        *gpccon &= ~(3 << 1*2);
        *gpccon |= (2 << 1*2);

        // 行同步信号           VLINE           GPC2
        *gpccon &= ~(3 << 2*2);
        *gpccon |= (2 << 2*2);

        // 数据有效信号         VM                      GPC4
        *gpccon &= ~(3 << 4*2);
        *gpccon |= (2 << 4*2);

        // 垂直同步信号         VFRAME          GPC3
        *gpccon &= ~(3 << 3*2);
        *gpccon |= (2 << 3*2);

        // 背光电路                     KEYBOARD        GPB0
        gpbcon = ioremap(0x56000010, sizeof(unsigned long) * 2);
        gpbdat = gpbcon + 1;

        // 设置为输出模式,输出低电平,关闭LCD控制器
        *gpbcon &= ~(3 << 0);
        *gpbcon |= (1 << 0);

        *gpbdat &= ~(1 << 0);

        // LCD供电                      LCD_PWREN       GPG4
        gpgcon = ioremap(0x56000060, sizeof(unsigned long ));

        // 设置为LCD供电模式
        *gpgcon |= (3 << 4*2);

        // 3.2 设置lcd 控制器
        s3c_lcd_regs = ioremap(0X4D000000, sizeof(*s3c_lcd_regs));

        /*
         * [27:18]                 read only
         * [17:8]  : 0b101         决定VCLK速度
         *                                  VCLK = HCLK / [ (CLKVAL +1) * 2]
         *                                  clkval = hclk/(vclk*2) - 1
         *                                                 = 100/18 -1 = 4.5
         * [7]     : 0          决定VM的翻转速率 , 不懂 设置
         * [6:5]   : 0b11               显示模式
         * [4:1]   : 0b1100             bpp模式 16bpp for TFT
         * [0]     : 0b0                禁止LCD控制器
         */
        s3c_lcd_regs->lcdcon1 = 0x00000578;

        /*
         * 时序
         * [31:24] : 1      vpbd + 1 = 2(tvb)
         * [23:14] : 271    lineval + 1 = 272(tvd)
         * [13:6]  : 1      vfpd + 1 = 2(tvf)
         * [5:0]   : 9      VSPW + 1 = 10(tvp)
         */
        s3c_lcd_regs->lcdcon2 = (1 << 24) | (271 << 14) | (1 << 6) | (9 << 0) ;

        /*
         * 时序
         * [25:19] : 1       HBPD + 1 = 2(thb)
         * [18:8]  : 479     HOZVAL + 1 = 480(thd)
         * [7:0]   : 1       HFPD + 1 = 2(thf)
         *
         */
        s3c_lcd_regs->lcdcon3 = (1 << 19) | (479 << 8) | (1 << 0);

        /*
         * 时序
         * [15:8]            STN显示器设置,忽略
         * [7:0]  : 40       HSPW + 1 = 41(thp)
         */
        s3c_lcd_regs->lcdcon4 = 40 << 0;

        /*
         * 极性
         * [31:17]
         * [16:15]
         * [14:13]
         * [12]              决定24bpp的顺序,使用16bpp不考虑
         * [11]    : 1       决定16bpp的格式 5:6:5
         * [10]    : 0       决定VCLK极性 数据在下降沿获取
         * [9]     : 1       决定HSYNC脉冲是否反正 要
         * [8]     : 1       决定VSYNC是否反正 要
         * [7]     : 0       决定VD是否翻转     不
         * [6]     : 0       VDEN翻转
         * [5]     : 0       PWREN翻转  高电平有效
         * [4]     : 0       LEND输出信号禁止   没找到连线
         * [3]     : 0       LCD_PWREN 输出LCD
         * [2]     : 0       BSWP
         * [1]     : 0       HWSWP
         */
        s3c_lcd_regs->lcdcon5 = 0x00000b01;


        // 3.3 分配 framebuffer
        // void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)
        // 返回 : 虚拟地址
        // dev :
        // size :  物理地址大小
        // handle : 物理地址
        // 3.3.1 分配framebuffer内存, 为了提高性能,此内存需要连续且和物理地址映射关系简单
        s3c_info->screen_base = dma_alloc_writecombine(NULL, s3c_info->fix.smem_len,
                                               (dma_addr_t *)&s3c_info->fix.smem_start, GFP_KERNEL);
        s3c_info->screen_size = s3c_info->fix.smem_len;

        // 3.3.2 设置LCD控制器显存部分
        // [29:21]  : 设置显存基地址的[30:22]位部分
        // [20:0]   : 设置显存基地址的[21:1]位部分
        // 所以显存基地址需要设置 [30:1]位 放到寄存器的 [29:0] ,共30为
        s3c_lcd_regs->lcdsaddr1 = (s3c_info->fix.smem_start >> 1) & 0x3fffffff;

        // [20:0]   : 设置结束地址的[21:1]
        s3c_lcd_regs->lcdsaddr2 = ((s3c_info->fix.smem_start + s3c_info->fix.smem_len) >> 1) & 0x1fffff;

        // 设置虚拟屏幕
        // [21:11]  : 虚拟屏幕偏移大小,半字单位 , 无偏移
        // [10:0]   : 虚拟屏幕页宽度大小,半字单位,半字16bit
        s3c_lcd_regs->lcdsaddr3 = SCREEN_W * 16 / 16;


        // 使能LCD
        // 启动LCD控制器信号输出
        s3c_lcd_regs->lcdcon5 |= (1 << 3);
        // 启动LCD
        s3c_lcd_regs->lcdcon1 |= (1 << 0);
        // 启动背光
        *gpbdat |= 1;

        // 4. 注册
        register_framebuffer(s3c_info);

        return 0;
}

static void __exit
lcd_dev_exit(void)
{
        unregister_framebuffer(s3c_info);

        dma_free_writecombine(NULL, s3c_info->fix.smem_len,
                        s3c_info->screen_base, s3c_info->fix.smem_start);

        iounmap(gpbcon);
        iounmap(gpgcon);
        iounmap(s3c_lcd_regs);
        iounmap(gpccon);
        iounmap(gpdcon);

        framebuffer_release(s3c_info);
}

module_init(lcd_dev_init);
module_exit(lcd_dev_exit);

MODULE_LICENSE("GPL");

3. 测试

ls /dev/fb* 出现设备
echo "aaa"> /dev/tty1 屏幕出现字

4. s3c2440的实现

使用 platform
drivers/video/s3c2410fb.c 实现 platform_driver
arch/arm/mach-s3c2440/mach-*.c 实现 platform_device
将lcd参数定义在 struct s3c2410fb_mach_info 中
使用 s3c24xx_fb_set_platdata 把 s3c2440_platform_device->dev.platform_data = s3c2410fb_mach_info_320_272
如此 在 s3c2440_platform_driver时,就可以获得该LCD的参数

posted on 2023-02-24 16:07  开心种树  阅读(141)  评论(0编辑  收藏  举报