1,帧缓冲设备在Linux中也可以看做是一个完整的子系统,
大体由fbmem.c和xxxfb.c(对应我们的s3cfb.c) 组成。向上给应用程序提供完善的设备文件
操作接口(即对FrameBuffer设备进行read、 write、 ioctl等操作),接口在Linux提供的fbmem.c文件中实现;向下提供了硬件操作的接口,只是这些接口Linux并没有提供实现,因为这要根据具体的LCD控制器硬件进行设置,所以这就是我们要做的事情了(即s3c-fb.c部分的实现)。exynos 4412的驱动代码里,framebuffer主要代码在driver/video/,文件名是s3c-fb.c
exynos 4412显示控制器可以控制0~5个windows,代码中分给它们分别编号win0, win1,win2......
这里一个window就对应一个独立的驱动控制个体, 每个framebuffer有自己的一个FBI(fb_info)结构,struct fb_info {
int node;
int flags;
struct fb_var_screeninfo var;/*LCD可变参数结构体*/
struct fb_fix_screeninfo fix;/*LCD固定参数结构体*/
struct fb_monspecs monspecs; /*LCD显示器标准*/
struct work_struct queue; /*帧缓冲事件队列*/
struct fb_pixmap pixmap; /*图像硬件 mapper*/
struct fb_pixmap sprite; /*光标硬件 mapper*/
struct fb_cmap cmap; /*当前的颜色表*/
struct fb_videomode *mode; /*当前的显示模式*/
#ifdef CONFIG_FB_BACKLIGHT
struct backlight_device *bl_dev;/*对应的背光设备*/
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;
struct device *dev; /*fb设备*/
int class_flag;
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /*图块Blitting*/
#endif
char __iomem *screen_base; /*虚拟基地址*/
unsigned long screen_size; /*LCD IO映射的虚拟内存大小*/
void *pseudo_palette; /*伪 16色颜色表*/
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /*LCD的挂起或恢复状态*/
void *fbcon_par;
void *par;
};
struct fb_var_screeninfo {
__u32 xres; /*可见屏幕一行有多少个像素点*/
__u32 yres; /*可见屏幕一列有多少个像素点*/
__u32 xres_virtual; /*虚拟屏幕一行有多少个像素点*/
__u32 yres_virtual; /*虚拟屏幕一列有多少个像素点*/
__u32 xoffset; /*虚拟到可见屏幕之间的行偏移*/
__u32 yoffset; /*虚拟到可见屏幕之间的列偏移*/
__u32 bits_per_pixel; /*每个像素的位数即 BPP*/
__u32 grayscale; /*非 0时,指的是灰度*/
struct fb_bitfield red; /*fb缓存的 R位域*/
struct fb_bitfield green; /*fb缓存的 G位域*/
struct fb_bitfield blue; /*fb缓存的B位域*/
struct fb_bitfield transp; /*透明度*/
__u32 nonstd; /* != 0 非标准像素格式*/
__u32 activate;
__u32 height; /*高度*/
__u32 width; /*宽度*/
__u32 accel_flags;
/*定时:除了 pixclock本身外,其他的都以像素时钟为单位*/
__u32 pixclock; /*像素时钟(皮秒)*/
__u32 left_margin; /*行切换,从同步到绘图之间的延迟*/
__u32 right_margin; /*行切换,从绘图到同步之间的延迟*/
__u32 upper_margin; /*帧切换,从同步到绘图之间的延迟*/
__u32 lower_margin; /*帧切换,从绘图到同步之间的延迟*/
__u32 hsync_len; /*水平同步的长度*/
__u32 vsync_len; /*垂直同步的长度*/
__u32 sync;
__u32 vmode;
__u32 rotate;
__u32 reserved[5]; /*保留*/
};
struct fb_fix_screeninfo {
char id[16]; /*字符串形式的标示符 */
unsigned long smem_start; /*fb缓存的开始位置 */
__u32 smem_len; /*fb缓存的长度 */
__u32 type; /*看FB_TYPE_* */
__u32 type_aux; /*分界*/
__u32 visual; /*看FB_VISUAL_* */
__u16 xpanstep; /*如果没有硬件panning就赋值为0 */
__u16 ypanstep; /*如果没有硬件panning就赋值为0 */
__u16 ywrapstep; /*如果没有硬件ywrap就赋值为0 */
__u32 line_length; /*一行的字节数 */
unsigned long mmio_start; /*内存映射IO的开始位置*/
__u32 mmio_len; /*内存映射IO的长度*/
__u32 accel;
__u16 reserved[3]; /*保留*/
};
struct fb_ops {
struct module *owner;
//检查可变参数并进行设置
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
//根据设置的值进行更新,使之有效
int (*fb_set_par)(struct fb_info *info);
//设置颜色寄存器
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
//显示空白
int (*fb_blank)(int blank, struct fb_info *info);
//矩形填充
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
//复制数据
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
//图形填充
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
};
/* LCD Controller */
//LCD控制器的资源信息
static struct resource s5p_fimd0_resource[] = {
[0] = DEFINE_RES_MEM(S5P_PA_FIMD0, SZ_32K),
[1] = DEFINE_RES_IRQ(IRQ_FIMD0_VSYNC),
[2] = DEFINE_RES_IRQ(IRQ_FIMD0_FIFO),
[3] = DEFINE_RES_IRQ(IRQ_FIMD0_SYSTEM),
};
struct platform_device s5p_device_fimd0 = {
.name = "s5p-fb",
.id = 0,
.num_resources = ARRAY_SIZE(s5p_fimd0_resource),
.resource = s5p_fimd0_resource,
.dev = {
.dma_mask = &samsung_device_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
},
};
/* LCDdriver info */
//LCD硬件的配置信息,这些参数要根据具体的LCD屏进行设置
static struct s3cfb_lcd wvga_s70 = {
.width = 800,
.height = 480,
.p_width = 155,
.p_height = 93,
.bpp = 24,
.freq = 63,
.timing = {
.h_fp = 80,
.h_bp = 36,
.h_sw = 10,
.v_fp = 22,
.v_fpe = 1,
.v_bp = 15,
.v_bpe = 1,
.v_sw = 8,
},
.polarity = {
.rise_vclk = 1,
.inv_hsync = 1,
.inv_vsync = 1,
.inv_vden = 0,
},
};
static int __devinit s3c_fb_probe(struct platform_device *pdev)
{
platid = platform_get_device_id(pdev);
fbdrv = (struct s3c_fb_driverdata *)platid->driver_data;
pd = pdev->dev.platform_data;
sfb = devm_kzalloc(dev, sizeof(struct s3c_fb), GFP_KERNEL); //设置FB描述信息结构体
sfb->dev = dev;
sfb->pdata = pd;
sfb->variant = fbdrv->variant;
spin_lock_init(&sfb->slock);
#if defined(CONFIG_FB_ION_EXYNOS)
mutex_init(&sfb->output_lock);
INIT_LIST_HEAD(&sfb->update_regs_list);
mutex_init(&sfb->update_regs_list_lock);
init_kthread_worker(&sfb->update_regs_worker);
sfb->update_regs_thread = kthread_run(kthread_worker_fn,
&sfb->update_regs_worker, "s3c-fb");
init_kthread_work(&sfb->update_regs_work, s3c_fb_update_regs_handler); //将s3c_fb_update_regs_handler绑定到内核线程中。周期性调用。
sfb->timeline = sw_sync_timeline_create("s3c-fb"); //创建timeline,提供给releasefence使用
sfb->timeline_max = 1;
/* XXX need to cleanup on errors */
#endif
sfb->bus_clk = clk_get(dev, "lcd"); //为外部LCD 提供时钟
clk_enable(sfb->bus_clk);
if (!sfb->variant.has_clksel) {
sfb->lcd_clk = clk_get(dev, "sclk_fimd"); //为FIMD模块提供时钟
clk_enable(sfb->lcd_clk);
}
pm_runtime_enable(sfb->dev);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //得到地址,中断号信息
sfb->regs = devm_request_and_ioremap(dev, res);
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); //得到地址,中断号信息
sfb->irq_no = res->start;
ret = devm_request_irq(dev, sfb->irq_no, s3c_fb_irq, //设置中断
0, "s3c_fb", sfb);
platform_set_drvdata(pdev, sfb);
/* setup gpio and output polarity controls */
pd->setup_gpio(); //gpio设置
writel(pd->vidcon1, sfb->regs + VIDCON1); //设置VIDCON配置器的初始值
#if defined(CONFIG_FB_ION_EXYNOS)
sfb->fb_ion_client = ion_client_create(exynos_ion_dev,"fimd"); //创建ion客户端
#endif
s3c_fb_set_rgb_timing(sfb); //设置rgb lcd总线接口的时序
/* we have the register setup, start allocating framebuffers */
init_waitqueue_head(&sfb->vsync_info.wait);
for (win = 0; win < fbdrv->variant.nr_windows; win++) {
if (!pd->win[win])
continue;
ret = s3c_fb_probe_win(sfb, win, fbdrv->win[win], //设置每个window
&sfb->windows[win]);
}
#if defined(CONFIG_FB_ION_EXYNOS)
sfb->vsync_info.thread = kthread_run(s3c_fb_wait_for_vsync_thread, sfb, "s3c-fb-vsync"); //启动vsync的处理线程
#endif
platform_set_drvdata(pdev, sfb);
return 0;
}
static int __devinit s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no,
struct s3c_fb_win_variant *variant,
struct s3c_fb_win **res)
{
struct fb_var_screeninfo *var;
struct fb_videomode initmode;
struct s3c_fb_pd_win *windata;
struct s3c_fb_win *win;
struct fb_info *fbinfo;
int ret;
fbinfo = framebuffer_alloc(sizeof(struct s3c_fb_win) + palette_size * sizeof(u32), sfb->dev);
windata = sfb->pdata->win[win_no];
initmode = *sfb->pdata->vtiming;
win = fbinfo->par;
*res = win;
var = &fbinfo->var;
win->variant = *variant;
win->fbinfo = fbinfo;
win->parent = sfb;
win->windata = windata;
win->index = win_no; //设置各种win的信息。结构体指来指去。数据来回赋值。没啥好讲的。
ret = s3c_fb_alloc_memory(sfb, win); //为每个win分配buffer
/* setup the initial video mode from the window */
initmode.xres = windata->xres;//各种赋值
initmode.yres = windata->yres;//各种赋值
fb_videomode_to_var(&fbinfo->var, &initmode);//各种赋值
fbinfo->var.width = windata->width;//各种赋值
fbinfo->var.height = windata->height;//各种赋值
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;//各种赋值
fbinfo->fix.accel = FB_ACCEL_NONE;//各种赋值
fbinfo->var.activate = FB_ACTIVATE_NOW;//各种赋值
fbinfo->var.vmode = FB_VMODE_NONINTERLACED;//各种赋值
fbinfo->var.bits_per_pixel = windata->default_bpp; //各种赋值
fbinfo->fbops = &s3c_fb_ops;//各种赋值
fbinfo->flags = FBINFO_FLAG_DEFAULT;//各种赋值
/* prepare to actually start the framebuffer */
ret = s3c_fb_check_var(&fbinfo->var, fbinfo); //根据硬件能力来检查和计算各种参数
s3c_fb_set_par(fbinfo); //将参数设置给每个win的硬件寄存器
return 0;
}
static int __devinit s3c_fb_alloc_memory(struct s3c_fb *sfb, struct s3c_fb_win *win)
{
struct s3c_fb_pd_win *windata = win->windata;
unsigned int real_size, virt_size, size;
struct fb_info *fbi = win->fbinfo;
dma_addr_t map_dma;
#if defined(CONFIG_FB_ION_EXYNOS)
struct ion_handle *handle;
int fd;
int ret;
struct file *file;
#endif
real_size = windata->xres * windata->yres;
virt_size = windata->virtual_x * windata->virtual_y;
size = (real_size > virt_size) ? real_size : virt_size;
size *= (windata->max_bpp > 16) ? 32 : windata->max_bpp;
size /= 8;
fbi->fix.smem_len = size; //计算所需要的内存size
size = PAGE_ALIGN(size);
#if defined(CONFIG_FB_ION_EXYNOS)
handle = ion_alloc(sfb->fb_ion_client, (size_t)size, 0, ION_HEAP_SYSTEM_MASK, 0); //申请一块vmalloc的内存
fd = ion_share_dma_buf(sfb->fb_ion_client, handle); //设置这块内存为共享的方式
ret = s3c_fb_map_ion_handle(sfb, &win->dma_buf_data, handle, fd); //通过iommu,将这块内存映射给FIMD,使其看到的是虚拟地址,并且是连续内存,便于DMA
map_dma = win->dma_buf_data.dma_addr;
#else
fbi->screen_base = dma_alloc_writecombine(sfb->dev, size, &map_dma, GFP_KERNEL); //如果是非ION的内存管理方式,直接用dma的API申请大片连续内存
memset(fbi->screen_base, 0x0, size);
#endif
fbi->fix.smem_start = map_dma;//申请的内存基地址赋值。该地址值最重要送给寄存器
return 0;
}
static unsigned int s3c_fb_map_ion_handle(struct s3c_fb *sfb,
struct s3c_dma_buf_data *dma, struct ion_handle *ion_handle,
int fd)
{
dma->dma_buf = dma_buf_get(fd); //获得buffer信息
dma->attachment = dma_buf_attach(dma->dma_buf, sfb->dev); //将buffer和framebuffer设备绑定
dma->sg_table = dma_buf_map_attachment(dma->attachment, DMA_BIDIRECTIONAL); //获得这块buffer的散列表。
dma->dma_addr = iovmm_map(&s5p_device_fimd0.dev, dma->sg_table->sgl, 0, dma->dma_buf->size); //用散列表中的物理地址,在s5p_device_fimd0设备的IOMMU上做映射
return dma->dma_buf->size;
}
dma_addr_t iovmm_map(struct device *dev, struct scatterlist *sg, off_t offset,size_t size)
{
start = (dma_addr_t)gen_pool_alloc(vmm->vmm_pool, size); //从vmm_poll里面分配一块内存空间。注意,是内存空间,不是内存。只是一个空间而已。没有实际的可用内存与之对应
addr = start;
do {
phys_addr_t phys;
size_t len;
phys = sg_phys(sg);
len = sg_dma_len(sg);
/* if back to back sg entries are contiguous consolidate them */
while (sg_next(sg) &&
sg_phys(sg) + sg_dma_len(sg) == sg_phys(sg_next(sg))) {
len += sg_dma_len(sg_next(sg));
sg = sg_next(sg);
}
if (offset_in_page(phys)) {
len += offset_in_page(phys);
phys = round_down(phys, PAGE_SIZE);
}
len = PAGE_ALIGN(len);
if (len > (size - mapped_size))
len = size - mapped_size;
ret = iommu_map(vmm->domain, addr, phys, len, 0); //对每块内存逐条映射
if (ret)
break;
addr += len;
mapped_size += len;
} while ((sg = sg_next(sg)) && (mapped_size < size)); //对散列表里的每块内存逐条遍历
region->start = start + start_off;
region->size = size;
INIT_LIST_HEAD(®ion->node);
spin_lock(&vmm->lock);
list_add(®ion->node, &vmm->regions_list);
spin_unlock(&vmm->lock);
return region->start; //返回FIMD看到的经过IOMMU映射后的连续内存块的首地址。 这个首地址对FIMD来说是个虚拟地址。DMA操作就是按照这个地址来的。对于CPU来说可以理解成一个物理地址。这个“物理地址“要设置给FIMD的寄存器。
}
int iovmm_activate(struct device *dev)
{
struct exynos_iovmm *vmm = exynos_get_iovmm(dev);
return iommu_attach_device(vmm->domain, dev);
}
static int s3c_fb_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
...
case S3CFB_WIN_CONFIG:
if (copy_from_user(&p.win_data,
(struct s3c_fb_win_config_data __user *)arg,
sizeof(p.win_data))) {
ret = -EFAULT;
break;
}
ret = s3c_fb_set_win_config(sfb, &p.win_data);
if (copy_to_user((struct s3c_fb_win_config_data __user *)arg,
&p.win_data,
sizeof(p.user_ion_client))) {
ret = -EFAULT;
break;
}
break;
...
}
static int s3c_fb_set_win_config(struct s3c_fb *sfb,struct s3c_fb_win_config_data *win_data)
{
fd = get_unused_fd();
mutex_lock(&sfb->output_lock);
regs = kzalloc(sizeof(struct s3c_reg_data), GFP_KERNEL);
for (i = 0; i < sfb->variant.nr_windows && !ret; i++) { //逐个window计算并设置参数
struct s3c_fb_win_config *config = &win_config[i];
struct s3c_fb_win *win = sfb->windows[i];
bool enabled = 0;
u32 color_map = WINxMAP_MAP | WINxMAP_MAP_COLOUR(0);
switch (config->state) {
case S3C_FB_WIN_STATE_BUFFER:
ret = s3c_fb_set_win_buffer(sfb, win, config, regs);
//逐个window计算并设置参数,函数中把每个window对应的acquirefence设置给每个window, 各种信息都记录在regs里面
break;
}
}
mutex_lock(&sfb->update_regs_list_lock);
sfb->timeline_max++;
pt = sw_sync_pt_create(sfb->timeline, sfb->timeline_max);
fence = sync_fence_create("display", pt); //所有window都配置完成后,创建retirefence。 通知user space的app
sync_fence_install(fence, fd);
win_data->fence = fd;
list_add_tail(®s->list, &sfb->update_regs_list); //regs挂入队列
mutex_unlock(&sfb->update_regs_list_lock);
queue_kthread_work(&sfb->update_regs_worker,&sfb->update_regs_work);
//触发update_regs_worker线程中的钩子函数s3c_fb_update_regs_handler,进行最实质的显示操作
mutex_unlock(&sfb->output_lock);
return ret;
}
static void s3c_fb_update_regs_handler(struct kthread_work *work)
{
list_for_each_entry_safe(data, next, &saved_list, list) { //依次遍历之前挂入队列的regs任务
s3c_fb_update_regs(sfb, data);
list_del(&data->list);
kfree(data);
}
}
static void s3c_fb_update_regs(struct s3c_fb *sfb, struct s3c_reg_data *regs)
{
for (i = 0; i < sfb->variant.nr_windows; i++) {
old_dma_bufs[i] = sfb->windows[i]->dma_buf_data;
if (regs->dma_buf_data[i].fence) {
s3c_fd_fence_wait(sfb, regs->dma_buf_data[i].fence); //等待每个window,也就是surfaceflinger中的买个layer对应的acquirefence触发。
}
}
do {
__s3c_fb_update_regs(sfb, regs); //实质的设置寄存器。进行实质的显示
s3c_fb_wait_for_vsync(sfb, 0); //等待VSYNC,确保每次设置寄存器,都能和VSYNC同步
wait_for_vsync = false;
for (i = 0; i < sfb->variant.nr_windows; i++) {
u32 new_start = regs->vidw_buf_start[i];
u32 shadow_start = readl(sfb->regs + SHD_VIDW_BUF_START(i));
if (unlikely(new_start != shadow_start)) { //确保所有window实际显示的buffer地址和设置下去的地址一致。实际上就是保证设置给FIMD的显示地址已经正常显示
wait_for_vsync = true;
break;
}
}
} while (wait_for_vsync && count--);
sw_sync_timeline_inc(sfb->timeline, 1); //显示完毕,更新时间轴,从而触发已经等待在userspace端的 retire fence
}
static irqreturn_t s3c_fb_irq(int irq, void *dev_id)
{
spin_lock(&sfb->slock);
irq_sts_reg = readl(regs + VIDINTCON1);
if (irq_sts_reg & VIDINTCON1_INT_FRAME) {
/* VSYNC interrupt, accept it */ //检查中断号
writel(VIDINTCON1_INT_FRAME, regs + VIDINTCON1);
sfb->vsync_info.count++;
wake_up_interruptible(&sfb->vsync_info.wait); //触发等待线程
}
spin_unlock(&sfb->slock);
return IRQ_HANDLED;
}
static int s3c_fb_wait_for_vsync(struct s3c_fb *sfb, u32 crtc)
{
count = sfb->vsync_info.count;
#if defined(CONFIG_FB_ION_EXYNOS)
s3c_fb_activate_vsync(sfb);
#endif
ret = wait_event_interruptible_timeout(sfb->vsync_info.wait,
count != sfb->vsync_info.count, //等待被触发
msecs_to_jiffies(VSYNC_TIMEOUT_MSEC));
#if defined(CONFIG_FB_ION_EXYNOS)
s3c_fb_deactivate_vsync(sfb);
#endif
return 0;
}
由此图可以看出,实质是通过s3c_fb_set_win_config 函数来触发s3c_fb_updates_regs_handler函数,其中会进行等待acquire fence被触发。
被触发之后,就可以设置硬件寄存器进行实质的显示。
显示之后,等待VSYNC中断触发,来实现帧同步。
帧同步之后,表示一帧正常显示完毕,buffer不再使用,此时可以触发retire fence来通知生产者该buffer可用。
其中各种显示参数设置与获取,内存mmap,等操作,可以由图中的IOCTL云框中的函数进行单独的设置。