说明:
在学习linux驱动的过程中,绕不开的就是LCD驱动,网上大部分教程都是教大家如果使用分辨率比较高的LCD作为Linux开发板的显示终端,但并没有讲过如果驱动spi或者iic这种低分辨率低帧率的液晶屏。正好手头上有一个ssd1306的OLED,分辨率是128×64,于是就像用这个作为Linux终端,顺便也学习了如果开发Linux的framebuffer驱动。
硬件:imx6ul + oledssd1306
应用
Linux 驱动是基于FrameBuffer框架的,网上关于FrameBuffer的介绍很多,我这里就简单的说明一下。
帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备fb,他允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。通常我们在dev下目录中能查找/dev/fb*。
通过/dev/fb,应用程序的操作主要有这几种:
1.读/写(read/write)/dev/fb:相当于读/写屏幕缓冲区。
2. 映射(map)操作:由于Linux工作在保护模式,每个应用程序都有自己的虚拟地址空间,在应用程序中是不能直接访问物理缓冲区地址的。而帧缓冲设备可以通过mmap()映射操作将屏幕缓冲区的物理地址映射到用户空间的一段虚拟地址上,然后用户就可以通过读写这段虚拟地址访问屏幕缓冲区,在屏幕上绘图了。
3. I/O控制:对于帧缓冲设备,对设备文件的ioctl操作可读取/设置显示设备及屏幕的参数,如分辨率,屏幕大小等相关参数。ioctl的操作是由底层的驱动程序来完成的。
在应用程序中,操作/dev/fb的一般步骤如下:
1. 打开/dev/fb设备文件。
2. 用ioctl操作取得当前显示屏幕的参数,根据屏幕参数可计算屏幕缓冲区的大小。
3. 将屏幕缓冲区映射到用户空间。
4. 映射后即可直接读写屏幕缓冲区,进行绘图和图片显示。
原理分析
帧缓冲设备为标准的字符型设备,在Linux中主设备号29,定义在/include/linux/major.h中的FB_MAJOR,次设备号定义帧缓冲的个数,最大允许有32个FrameBuffer,定义在/include/linux/fb.h中的FB_MAX。
帧缓冲设备结构图
帧缓冲设备抽象层
对于驱动开发来说,内核已经在fbmem.c中实现了.
fbmem.c位于/driver/video/fbdev/core/fbmem.c,在这个文件中内核创建了fb设备。实现了fb_ops 操作函数,当我们打开某一个fb设备时,实际上是调用此文件中的fb_open(),fb_read()等函数。
fbmem.c实际上是一个抽象层,当调用fb_open 等函数时实际上会调用更加底层的操作函数去操作驱动LCD控制器。而这些更加底层的函数就是在xxxfb.c中实现的。
在framebuffer驱动开发过程中驱动开发者只需要将LCD驱动的相关信息填入到fb_info 结构体即可.最后在通过register_framebuffer函数将fb_info 注册到内核,内核通过调用这个函数内核自动创建fbx设备,过程可以参考上图。
int
帧缓冲设备实现
在/driver/video/fbdev下会发现大量xxfb.c文件,这些文件就是上文说的驱动具体实现。主要完成对功能就实现fb_info 结构体。
struct fb_info {
atomic_t count;
int node;
int flags;
struct mutex lock; /* Lock for open/release/ioctl funcs */
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
struct fb_var_screeninfo var; /* Current var 可变参数*/
struct fb_fix_screeninfo fix; /* Current fix 固定参数*/
struct fb_monspecs monspecs; /* Current Monitor specs 显示器标准*/
struct work_struct queue; /* Framebuffer event queue 事件队列*/
struct fb_pixmap pixmap; /* Image hardware mapper */
struct fb_pixmap sprite; /* Cursor hardware mapper */
struct fb_cmap cmap; /* Current cmap 当前颜色表*/
struct list_head modelist; /* mode list 模式列表*/
struct fb_videomode *mode; /* current mode 当前显示模式*/
#ifdef CONFIG_FB_BACKLIGHT
/* assigned backlight device */
/* set before framebuffer registration,
remove after unregister */
struct backlight_device *bl_dev;//分配的背光设备,使用fb之前注册,
/* Backlight level curve */
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
struct fb_deferred_io *fbdefio;
#endif
struct fb_ops *fbops; //操作函数
struct device *device; /* 父设备节点This is the parent */
struct device *dev; /* 当前帧缓冲设备This is this fb device */
int class_flag; /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /* Tile Blitting */
#endif
char __iomem *screen_base; /* Virtual address 虚拟地址*/
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 虚拟地址的大小 */
void *pseudo_palette; /* Fake palette of 16 colors */
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /* Hardware state i.e suspend */
void *fbcon_par; /* fbcon use-only private area */
/* From here on everything is device dependent */
void *par;
/* we need the PCI or similar aperture base/size not
smem_start/size as smem_start may just be an object
allocated inside the aperture so may not actually overlap */
struct apertures_struct {
unsigned int count;
struct aperture {
resource_size_t base;
resource_size_t size;
} ranges[0];
} *apertures;
bool skip_vt_switch; /* no VT switch on suspend/resume required */
};
在这个结构体中最主要的是两个
struct fb_var_screeninfo var; /* Current var 可变参数*/
struct fb_fix_screeninfo fix; /* Current fix 固定参数*/
struct fb_fix_screeninfo fix; 定义了显示相关的参数,一般这些参数都是固定不变的。
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */
unsigned long smem_start; /* Start of frame buffer mem 缓存的开始地址*/
/* (physical address) */
__u32 smem_len; /* Length of frame buffer mem 缓冲区内存大小*/
__u32 type; /* see FB_TYPE_* 查看FB_TYPE_宏定义 */
__u32 type_aux; /* Interleave for interleaved Planes */
__u32 visual; /* see FB_VISUAL_* 查看FB_VISUAL_宏定义 */
__u16 xpanstep; /* zero if no hardware panning */
__u16 ypanstep; /* zero if no hardware panning */
__u16 ywrapstep; /* zero if no hardware ywrap */
__u32 line_length; /* length of a line in bytes 一行的字节长度 */
unsigned long mmio_start; /* Start of Memory Mapped I/O 内存映射 IO的开始位置 */
/* (physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O 内存映射 IO的内存的长度 */
__u32 accel; /* Indicate to driver which */
/* specific chip/card we have */
__u16 capabilities; /* see FB_CAP_* */
__u16 reserved[2]; /* Reserved for future compatibility */
};
struct fb_var_screeninfo var
struct fb_var_screeninfo {
__u32 xres; /* visible resolution 可视区域,一行多少个像素 */
__u32 yres; //可视区域 一列多少个像素
__u32 xres_virtual; /* virtual resolution 虚拟区域 一行多少个像素*/
__u32 yres_virtual; //虚拟区域 一列多少个像素
__u32 xoffset; /* offset from virtual to visible 虚拟和可视之间的偏移量*/
__u32 yoffset; /* resolution */
__u32 bits_per_pixel; /* guess what 每个像素的 bit 数 */
__u32 grayscale; /* 0 = color, 1 = grayscale, */
/* >1 = FOURCC */
struct fb_bitfield red; /* bitfield in fb mem if true color, */
struct fb_bitfield green; /* else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp; /* transparency */
__u32 nonstd; /* != 0 Non standard pixel format */
__u32 activate; /* see FB_ACTIVATE_* */
__u32 height; /* height of picture in mm 内存中的图像高 */
__u32 width; /* width of picture in mm 内存中的图像宽度 */
__u32 accel_flags; /* (OBSOLETE) see fb_info.flags */
/* Timing: All values in pixclocks, except pixclock (of course) */
__u32 pixclock; /* pixel clock in ps (pico seconds) 像素时钟*/
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 upper_margin; /* time from sync to picture */
__u32 lower_margin;
__u32 hsync_len; /* length of horizontal sync */
__u32 vsync_len; /* length of vertical sync */
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 rotate; /* angle we rotate counter clockwise */
__u32 colorspace; /* colorspace for FOURCC-based modes */
__u32 reserved[4]; /* Reserved for future compatibility */
};
最后是fb_ops 操作函数
struct fb_ops {
/* open/release and usage marking */
struct module *owner;
int (*fb_open)(struct fb_info *info, int user);//打开
int (*fb_release)(struct fb_info *info, int user);//释放
/* For framebuffers with strange non linear layouts or that do not
* work with normal memory mapped access
*/
//这两个函数对于非线性布局的/常规内存映射无法工作的帧缓冲设备需要
ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos);
/* checks var and eventually tweaks it to something supported,
* DO NOT MODIFY PAR */
//检测可变参数,并调整到支持的值
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
//设置视频模式
/* set the video mode according to info->var */
int (*fb_set_par)(struct fb_info *info);
/* set color register */
//设置color寄存器的值
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
/* set color registers in batch */
//批量设置color寄存器,设置颜色表
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
/* blank display */
//显示空白
int (*fb_blank)(int blank, struct fb_info *info);
/* pan display */
//pan 显示
int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
/* Draws a rectangle */
//填充矩形
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
/* Copy data from area to another */
//数据复制
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
/* Draws a image to the display */
//图形填充
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
/* Draws cursor */
//绘制光标
int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
/* Rotates the display */
//旋转显示
void (*fb_rotate)(struct fb_info *info, int angle);
/* wait for blit idle, optional */
//等待blit空闲
int (*fb_sync)(struct fb_info *info);
/* perform fb specific ioctl (optional) */
//fb特定的ioctl操作
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);
/* Handle 32bit compat ioctl (optional) */
//处理32兼容的ioctl操作
int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
unsigned long arg);
/* perform fb specific mmap */
//fb特定的mmap操作
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
/* get capability given var */
//通过fb_info获得framebuffer的能力
void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
struct fb_var_screeninfo *var);
/* teardown any resources to do with this framebuffer */
void (*fb_destroy)(struct fb_info *info);
/* called at KDB enter and leave time to prepare the console */
int (*fb_debug_enter)(struct fb_info *info);
int (*fb_debug_leave)(struct fb_info *info);
};
把以上结构体实现了,最后在通过“register_framebuffer(info);”注册到内核即可。
代码实现
搜索内核代码发现,Linux 内核已经实现了对ssd1306的驱动,代码位置:
/driver/staging/fbtft/fb_ssd1306.c
/driver/video/fbdev/ssd1307fb.c
其中fb_ssd1306是基于spi驱动,内核定义了一种新的设备结构fbtft,主要用于spi接口的tftlcd,例如ili9341,ili9320等,
ssd1307fb.c 实现了基于iic 接口驱动,同时支持ssd1306和ssd1307两种芯片。驱动代码通过设备树来判断到底是ssd1306还是ssd1307.
1,将ssd1307fb编译到内核。
在内核源码目录中执行make menuconfig
Device Drivers
----------->Graphics support
------>Frame buffer Devices
--->Solomon SSD1306 framebuffer support
保存退出
2.修改设备树
vim arch/arm/boot/dts/imx6ul-14x14-evk.dts
需要注意到是imx6的官方默认已经写好了lcd的驱动,只不过这个驱动不是我想要的,所以在修改设备树的时候需要把原来的驱动注释掉。
同时在设备树i2c接口中添加ssd1603的硬件定义。
值得注意的是,我使用的液晶屏并没有reset ,但是源码调用了这个接口,因此必须定义。修改完成保存退出。
3.编译
执行:make zImage
将新的内核和设备树文件烧写到板子中。
4.问题
液晶屏成功被点量,但是发现显示有问题,只能显示半屏,经过分析发现,ssd1307fb.c 在初始化ssd1603时配置寄存器和我用的不一样,于是修改ssd1307fb.c中的static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par)函数。
static
重新编译,烧录到板子,
成功!
后记
之前自己也写了一版驱动程序,但是和内核自带的驱动相比差的太远了,也就不献丑了。
不过个人认为在学习驱动时应该多动手练习,只有不断出错然后查找问题,才能从这个过程当中不断提高,最后学会驱动开发。