嵌入式Linux ------ 一次简单的FrameBuffer驱动开发

发布时间 2023-08-10 15:36:31作者: IotaHydrae

Linux 一次简单的FrameBuffer驱动开发

设施 版本
CPU Allwinner F1C200s
linux 6.4.0-rc4
显示器 1.28 inch 16-grayscale OLED 128x128 驱动IC SSD1327
Orangepi 5

声明

本驱动仓库位于:https://github.com/AllwinnerSuniv/suniv-epd/tree/main/ssd1327

本驱动代码采样自 fbtft(linux/drivers/staging/fbtft) 项目

本案例用到的显示器可从如下链接购买:

模组:
https://item.taobao.com/item.htm?spm=a1z10.3-c-s.w4002-24706531953.9.7b7d6a4bKhv5j9&id=665635617961

面板:
https://item.taobao.com/item.htm?spm=a1z10.3-c-s.w4002-24706531953.11.7b7d6a4bKhv5j9&id=666350647617

思路

这块显示屏是16灰度格式的,这意味着一个像素点需要用4bit来表示,同理1byte表示2像素

我们在fb驱动中获取 rgb565 数据 转成16灰度 发给显示屏即可,转换函数如下:

#define RED(a)      ((((a) & 0xf800) >> 11) << 3)
#define GREEN(a)    ((((a) & 0x07e0) >> 5) << 2)
#define BLUE(a)     (((a) & 0x001f) << 3)
static inline u8 rgb565_to_4bit_grayscale(u16 rgb565)
{
        int r, g, b;
        __maybe_unused int level;
        u16 gray;

        /* get each channel and expand them to 8 bit */
        r = RED(rgb565);
        g = GREEN(rgb565);
        b = BLUE(rgb565);

        /* convert rgb888 to grayscale */
        gray = ((r * 77 + g * 151 + b * 28) >> 8); // 0 ~ 255
        if (gray == 0)
                return gray;

        /*
         * so 4-bit grayscale like:
         * B3  B2  B1  B0
         * 0   0   0   0
         * which means have 16 kind of gray
         */
        gray /= 16;

        return gray;
}

驱动流程如下

  1. 根据显示器宽高、bpp申请 vmem
  2. 申请 fbops fbinfo 结构体
  3. 配置 fbops 中的函数
        fbops->fb_read      = fb_sys_read;
        fbops->fb_write     = ssd1327_fb_write;
        fbops->fb_fillrect  = ssd1327_fb_fillrect;
        fbops->fb_copyarea  = ssd1327_fb_copyarea;
        fbops->fb_imageblit = ssd1327_fb_imageblit;
        fbops->fb_setcolreg = ssd1327_fb_setcolreg;
        fbops->fb_blank     = ssd1327_fb_blank;
  1. 配置 fbinfo 结构体
        snprintf(info->fix.id, sizeof(info->fix.id), "%s", dev->driver->name);
        info->fix.type            =       FB_TYPE_PACKED_PIXELS;
        info->fix.visual          =       FB_VISUAL_TRUECOLOR;
        // info->fix.visual          =       FB_VISUAL_MONO10;
        info->fix.xpanstep        =       0;
        info->fix.ypanstep        =       0;
        info->fix.ywrapstep       =       0;
        info->fix.line_length     =       width * bpp / BITS_PER_BYTE;
        info->fix.accel           =       FB_ACCEL_NONE;                                       
        info->fix.smem_len        =       vmem_size;

        info->var.rotate          =       rotate;
        info->var.xres            =       width;
        info->var.yres            =       height;
        info->var.xres_virtual    =       info->var.xres;
        info->var.yres_virtual    =       info->var.yres;

        info->var.bits_per_pixel  =       bpp;
        info->var.nonstd          =       1;
        info->var.grayscale       =       1;

        switch (info->var.bits_per_pixel) {
        case 1:
        case 2:
        case 4:
        case 8:
                info->var.red.offset = info->var.green.offset = info->var.blue.offset = 0;
                info->var.red.length = info->var.green.length = info->var.blue.length = 8;
                break;

        case 16:
                info->var.red.offset      =       11;
                info->var.red.length      =       5;
                info->var.green.offset    =       5;
                info->var.green.length    =       6;
                info->var.blue.offset     =       0;
                info->var.blue.length     =       5;
                info->var.transp.offset   =       0;
                info->var.transp.length   =       0;
                break;
        default:
                dev_err(dev, "color depth %d not supported\n",
                        info->var.bits_per_pixel);
                break;
        }

        info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB;

  1. 我们用到了fbdeferred_io 机制,配置如下
        fbdefio->delay = HZ / display.fps;
        fbdefio->deferred_io = ssd1327_deferred_io;
        fb_deferred_io_init(info);
  1. 调色板的配置,对于rgb565
info->pseudo_palette = &par->pseudo_palette;

/* from pxafb.c */
static unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
        chan &= 0xffff;
        chan >>= 16 - bf->length;
        return chan << bf->offset;
}

static int ssd1327_fb_setcolreg(unsigned int regno, unsigned int red,
                                unsigned int green, unsigned int blue,
                                unsigned int transp, struct fb_info *info)
{
		...
		
        switch (info->fix.visual) {
        case FB_VISUAL_TRUECOLOR:
                if (regno < 16) {
                        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;
                        ret = 0;
                }
                break;
        default:
                dev_err(info->dev, "unsupported color format!");
                ret = -1;
                break;
        }
		
		...
}
  1. 其他接口的初始化,例如锁等
  2. 初始化显示屏硬件,init_display 序列等
  3. 注册 fbinfo 结构体
  4. sysfs的管理

fbtft驱动的显示刷新流程

        fbops->fb_write     = ssd1327_fb_write;
        fbops->fb_fillrect  = ssd1327_fb_fillrect;
        fbops->fb_copyarea  = ssd1327_fb_copyarea;
        fbops->fb_imageblit = ssd1327_fb_imageblit;

上述接口会根据传入的宽高调用mk_dirty标记脏数据,然后通过schdule deferred_io work调用 update_display 完成区域更新