#include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/dma-mapping.h> #include <linux/slab.h> #include <linux/init.h> #include <linux/clk.h> #include <linux/fb.h> #include <linux/io.h> #include <mach/map.h> #include <mach/regs-fb.h> #include <plat/fb.h> struct s3c_fb; struct s3c_fb_win { struct s3c_fb_pd_win *windata; struct s3c_fb *parent; struct fb_info *fbinfo; struct s3c_fb_palette palette; u32 *palette_buffer; u32 pseudo_palette[16]; unsigned int index; }; struct s3c_fb { struct device *dev; struct resource *regs_res; struct clk *bus_clk; void __iomem *regs; unsigned char enabled; struct s3c_fb_platdata *pdata; struct s3c_fb_win *windows[S3C_FB_MAX_WIN]; }; static int s3c_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { /* always ensure these are zero, for drop through cases below */ var->transp.offset = 0; var->transp.length = 0; /* 16 bpp, 565 format */ var->red.offset = 11; var->green.offset = 5; var->blue.offset = 0; var->red.length = 5; var->green.length = 6; var->blue.length = 5; return 0; } static int s3c_fb_calc_pixclk(struct s3c_fb *sfb, unsigned int pixclk) { unsigned long clk = clk_get_rate(sfb->bus_clk); unsigned long long tmp; unsigned int result; tmp = (unsigned long long)clk; tmp *= pixclk; do_div(tmp, 1000000000UL); result = (unsigned int)tmp / 1000; dev_dbg(sfb->dev, "pixclk=%u, clk=%lu, div=%d (%lu)\n", pixclk, clk, result, clk / result); return result; } static int s3c_fb_align_word(unsigned int bpp, unsigned int pix) { int pix_per_word; if (bpp > 16) return pix; pix_per_word = (8 * 32) / bpp; return ALIGN(pix, pix_per_word); } static int s3c_fb_set_par(struct fb_info *info) { struct fb_var_screeninfo *var = &info->var; struct s3c_fb_win *win = info->par; struct s3c_fb *sfb = win->parent; void __iomem *regs = sfb->regs; int win_no = win->index; u32 osdc_data = 0; u32 data; u32 pagewidth; int clkdiv; info->fix.visual = FB_VISUAL_TRUECOLOR;//true color info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8; /* disable the window whilst we update it */ writel(0, regs + WINCON(win_no)); if (win_no == 0) { clkdiv = s3c_fb_calc_pixclk(sfb, var->pixclock); data = sfb->pdata->vidcon0; data &= ~(VIDCON0_CLKVAL_F_MASK | VIDCON0_CLKDIR); if (clkdiv > 1) data |= VIDCON0_CLKVAL_F(clkdiv-1) | VIDCON0_CLKDIR; else data &= ~VIDCON0_CLKDIR; /* 1:1 clock */ /* write the timing data to the panel */ data |= VIDCON0_ENVID | VIDCON0_ENVID_F; writel(data, regs + VIDCON0); data = VIDTCON0_VBPD(var->upper_margin - 1) | VIDTCON0_VFPD(var->lower_margin - 1) | VIDTCON0_VSPW(var->vsync_len - 1); writel(data, regs + VIDTCON0); data = VIDTCON1_HBPD(var->left_margin - 1) | VIDTCON1_HFPD(var->right_margin - 1) | VIDTCON1_HSPW(var->hsync_len - 1); writel(data, regs + VIDTCON1); data = VIDTCON2_LINEVAL(var->yres - 1) | VIDTCON2_HOZVAL(var->xres - 1); writel(data, regs + VIDTCON2); } /* write the buffer address */ writel(info->fix.smem_start, regs + VIDW_BUF_START(win_no)); data = info->fix.smem_start + info->fix.line_length * var->yres; writel(data, regs + VIDW_BUF_END(win_no)); pagewidth = (var->xres * var->bits_per_pixel) >> 3; data = VIDW_BUF_SIZE_OFFSET(info->fix.line_length - pagewidth) | VIDW_BUF_SIZE_PAGEWIDTH(pagewidth); writel(data, regs + VIDW_BUF_SIZE(win_no)); /* write 'OSD' registers to control position of framebuffer */ data = VIDOSDxA_TOPLEFT_X(0) | VIDOSDxA_TOPLEFT_Y(0); writel(data, regs + VIDOSD_A(win_no)); data = VIDOSDxB_BOTRIGHT_X(s3c_fb_align_word(var->bits_per_pixel, var->xres - 1)) | VIDOSDxB_BOTRIGHT_Y(var->yres - 1); writel(data, regs + VIDOSD_B(win_no)); data = var->xres * var->yres; osdc_data = VIDISD14C_ALPHA1_R(0xf) | VIDISD14C_ALPHA1_G(0xf) | VIDISD14C_ALPHA1_B(0xf); if (s3c_fb_has_osd_d(win_no)) { writel(data, regs + VIDOSD_D(win_no)); writel(osdc_data, regs + VIDOSD_C(win_no)); } else writel(data, regs + VIDOSD_C(win_no)); data = WINCONx_ENWIN; data |= WINCON0_BPPMODE_16BPP_565; data |= WINCONx_HAWSWP; data |= WINCONx_BURSTLEN_16WORD; writel(data, regs + WINCON(win_no)); writel(0x0, regs + WINxMAP(win_no)); 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; } static int s3c_fb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info) { struct s3c_fb_win *win = info->par; struct s3c_fb *sfb = win->parent; unsigned int val; //regno 2-255 switch (info->fix.visual) { case FB_VISUAL_TRUECOLOR: if (regno < 16) { u32 *pal = info->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); pal[regno] = val; } break; default: return 1; } return 0; } static int s3c_fb_blank(int blank_mode, struct fb_info *info) { return 0; } static struct fb_ops s3c_fb_ops = { .owner = THIS_MODULE, .fb_check_var = s3c_fb_check_var,//检测可变参数,并调整到支持的值*/ .fb_set_par = s3c_fb_set_par,//根据info->var设置video模式*/ .fb_blank = s3c_fb_blank,//显示空白*/ .fb_setcolreg = s3c_fb_setcolreg,//设置color寄存器*/ .fb_fillrect = cfb_fillrect,//矩形填充drivers/video/cfblillrect.c里实现,把FB_CIRRUS打开*/ .fb_copyarea = cfb_copyarea,//数据复制drivers/video/cfbcopyarea.c*/ .fb_imageblit = cfb_imageblit,//图形填充drivers/video/cfbimgblt.c*/ }; static int __devinit s3c_fb_alloc_memory(struct s3c_fb *sfb, struct s3c_fb_win *win) { struct s3c_fb_pd_win *windata = win->windata; dma_addr_t map_dma;//帧缓冲物理地址*/ unsigned int real_size, virt_size, size; struct fb_info *fbi = win->fbinfo; real_size = windata->win_mode.xres * windata->win_mode.yres * windata->default_bpp; virt_size = windata->virtual_x * windata->virtual_y * windata->default_bpp; size = real_size / 4; fbi->fix.smem_len = size; printk("the size is %d, fbi->fix.smem_len = %d\n", size, windata->win_mode.xres * windata->win_mode.yres * windata->max_bpp); //the size is 522240, fbi->fix.smem_len = 4177920*/ //PAGE_ALIGN()1k边界对齐*/ size = PAGE_ALIGN(size); /**通过dma_alloc_writecombine()分配的显示缓冲区不会出现cache一致性问题*/ fbi->screen_base = dma_alloc_writecombine(sfb->dev, size, &map_dma, GFP_KERNEL); printk( "mapped %x to %p\n", (unsigned int)map_dma, fbi->screen_base); memset(fbi->screen_base, 0x0, size); //smem_start:帧缓冲设备显示缓冲区首地址*/ fbi->fix.smem_start = map_dma; return 0; } /** *s3c_fb_probe_win():注册一个硬件窗口,帧缓冲显示缓冲区的申请 *@sfb:硬件基础资源 *@res:指向产生窗口的指针 *对硬件图形窗口进行分配和执行基本的初始化 */ static int __devinit s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no, struct s3c_fb_win **res) { struct fb_var_screeninfo *var;//记录用户可修改的显示控制器参数*/ struct fb_videomode *initmode;//include/linux/fb.h里定义*/ struct s3c_fb_pd_win *windata;//自定义的平台数据接收bsp参数*/ struct s3c_fb_win *win;//驱动里定义的结构体包括fb_info*/ struct fb_info *fbinfo;//fb_info,帧缓冲关键结构*/ int palette_size; int ret; palette_size = 256;//调色板大小*/ /**分配fb_info结构*/ fbinfo = framebuffer_alloc(sizeof(struct s3c_fb_win) + palette_size * sizeof(u32), sfb->dev); //获得bsp的平台数据.win[0] = &smdk6410_fb_win0, windata = sfb->pdata->win[win_no]; initmode = &windata->win_mode; win = fbinfo->par; var = &fbinfo->var; win->fbinfo = fbinfo; win->parent = sfb; win->windata = windata; win->index = win_no; win->palette_buffer = (u32 *)(win + 1); /**帧缓冲显示缓冲区的申请,系统通过DMA方式搬移显示数据*/ ret = s3c_fb_alloc_memory(sfb, win); //初始化调色板plat-samsung/include/plat/regs-fb-v4.h /* setup the r/b/g positions for the window's palette */ s3c_fb_init_palette(win_no, &win->palette); /* setup the initial video mode from the window */ fb_videomode_to_var(&fbinfo->var, initmode); /*给FBI设置参数*/ fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;/* Packed Pixels */ fbinfo->fix.accel = FB_ACCEL_NONE;/* no hardware accelerator */ fbinfo->var.activate = FB_ACTIVATE_NOW; fbinfo->var.vmode = FB_VMODE_NONINTERLACED; fbinfo->var.bits_per_pixel = windata->default_bpp;//每个点用多个字节表示BPP fbinfo->fbops = &s3c_fb_ops;//指向底层操作的函数指针 fbinfo->flags = FBINFO_FLAG_DEFAULT;//linux/fb.h定义0x0001 Low-level driver is a module */ fbinfo->pseudo_palette = &win->pseudo_palette; /* prepare to actually start the framebuffer */ ret = s3c_fb_check_var(&fbinfo->var, fbinfo);//检查framebuffer的可变参数 /* create initial colour map 分配颜色空间*/ ret = fb_alloc_cmap(&fbinfo->cmap, s3c_fb_win_pal_size(win_no), 1); fb_set_cmap(&fbinfo->cmap, fbinfo);//设置颜色空间drivers/video/fbcmap.c里定 s3c_fb_set_par(fbinfo);//framebuffer request to set new framebuffer state. //注册帧缓冲设设备 ret = register_framebuffer(fbinfo); *res = win; return 0; } static int s3c_fb_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct s3c_fb_platdata *pd;//plat-samsung/include/plat/fb.h平台数据 struct s3c_fb *sfb;/*s3c_fb 自定义的全局结构体一个最重要的数据结据, *它代表了一个显示控制器,显示控制器的所有东东都放在这里了。 *但这里把它做成一个局部变量了*/ struct resource *res; int win; int ret = 0; pd = pdev->dev.platform_data;//get bsp's platform_data sfb = kzalloc(sizeof(struct s3c_fb), GFP_KERNEL);//给结构体分配内存fb_info sfb->dev = dev; sfb->pdata = pd;//向sfb填入显示控制器的dev,pd //获得lcd的时钟并使能 sfb->bus_clk = clk_get(dev, "lcd"); clk_enable(sfb->bus_clk); /**获得LCD平台设备IO内存资源*/ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); /**申请IO内存*/ sfb->regs_res = request_mem_region(res->start, resource_size(res), pdev->name); /**将IO内存空间映射到内核虚拟空间*/ sfb->regs = ioremap(res->start, resource_size(res)); //执行回调函数来设置gpio引脚,回调函数的实现是在 //arch/arm/mach-s3c64xx/setup-fb-24bpp.c里的 pd->setup_gpio(); //设置寄存器,把平台数据 .vidcon1 = VIDCON1_INV_HSYNC | VIDCON1_INV_VSYNC, //写到寄存器VIDCON1的地址0x7710_0004,即HSYNC和VSYNC为1,脉冲极性反转 writel(pd->vidcon1, sfb->regs + VIDCON1); for (win = 0; win < S3C_FB_MAX_WIN; win++) { if (!pd->win[win]) continue;//win=0 //分配 及 注册framebuffer的重要函数s3c_fb_probe_win ret = s3c_fb_probe_win(sfb, win, &sfb->windows[win]); } //可以将sfb保存成平台总线设备的私有数据,以后要使用它时只需调用 //platform_get_drvdata()就可以了 platform_set_drvdata(pdev, sfb); return 0; } static int s3c_fb_remove(struct platform_device *pdev) { return 0; } #define s3c_fb_suspend NULL #define s3c_fb_resume NULL static struct platform_driver s3c_fb_driver = { .probe = s3c_fb_probe, .remove = s3c_fb_remove, .suspend = s3c_fb_suspend, .resume = s3c_fb_resume, .driver = { .name = "s3c-fb", .owner = THIS_MODULE, }, }; static int __init s3c_fb_init(void) { return platform_driver_register(&s3c_fb_driver); } static void __exit s3c_fb_cleanup(void) { platform_driver_unregister(&s3c_fb_driver); } module_init(s3c_fb_init); module_exit(s3c_fb_cleanup); MODULE_AUTHOR("Ben Dooks <[email protected]>"); MODULE_DESCRIPTION("Samsung S3C SoC Framebuffer driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:s3c-fb");