/* * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ /*! * @defgroup Framebuffer Driver for DCU4 */ /*! * @file mvf_dcu4_fb.c * * @brief MVF Frame buffer driver for DCU4 * * @ingroup Framebuffer */ /*! * Include files */ #include "mvf_dispdrv.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Driver name */ #define MVFFB_NAME "mvf_dcu4_fb" /*! * Structure containing the MVF specific framebuffer information. */ //TODO: How are multiple overlays handled? struct mvffb_info { int default_bpp; int cur_blank; int next_blank; int dcu_id; u32 dcu_pix_fmt; bool layer; uint32_t dcu_layer_transfer_finish; struct fb_info *ovfbi[32]; void *dcu; struct mvf_dispdrv_handle *dispdrv; bool alpha_en; dma_addr_t alpha_phy_addr0; dma_addr_t alpha_phy_addr1; void *alpha_virt_addr0; void *alpha_virt_addr1; uint32_t alpha_mem_len; uint32_t cur_dcu_buf; uint32_t cur_dcu_alpha_buf; u32 pseudo_palette[16]; struct semaphore flip_sem; struct semaphore alpha_flip_sem; }; struct mvffb_alloc_list { struct list_head list; dma_addr_t phy_addr; void *cpu_addr; u32 size; }; static bool g_dp_in_use[2]; LIST_HEAD(fb_alloc_list); static uint32_t bpp_to_pixfmt(struct fb_info *fbi) { //FIXME: For now Supporting 3 formats as given below uint32_t pixfmt = 0; if (fbi->var.nonstd) return fbi->var.nonstd; switch (fbi->var.bits_per_pixel) { case 24: pixfmt = V4L2_PIX_FMT_RGB24; break; case 32: pixfmt = V4L2_PIX_FMT_RGB32; break; case 16: pixfmt = V4L2_PIX_FMT_RGB565; break; } return pixfmt; } static bool dcu_usage[2]; static int dcu_set_usage(int dcu) { if (dcu_usage[dcu]) return -EBUSY; else dcu_usage[dcu] = true; return 0; } static void dcu_clear_usage(int dcu) { dcu_usage[dcu] = false; } static struct fb_info *found_registered_fb(int layer_id, int dcu_id) { int i; struct mvffb_info *mvf_fbi; struct fb_info *fbi = NULL; for (i = 0; i < num_registered_fb; i++) { mvf_fbi = ((struct mvffb_info *)(registered_fb[i]->par)); if ((mvf_fbi->dcu_id == dcu_id) && (registered_fb[i]->node == layer_id)) { fbi = registered_fb[i]; break; } } return fbi; } static int mvffb_blank(int blank, struct fb_info *info); static int mvffb_map_video_memory(struct fb_info *fbi); static int mvffb_unmap_video_memory(struct fb_info *fbi); /* * Set fixed framebuffer parameters based on variable settings. * * @param info framebuffer information pointer */ static int mvffb_set_fix(struct fb_info *info) { struct fb_fix_screeninfo *fix = &info->fix; struct fb_var_screeninfo *var = &info->var; fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; fix->type = FB_TYPE_PACKED_PIXELS; fix->accel = FB_ACCEL_NONE; fix->visual = FB_VISUAL_TRUECOLOR; fix->xpanstep = 0; fix->ywrapstep = 0; fix->ypanstep = 0; return 0; } /* * Set framebuffer parameters and change the operating mode. * * @param info framebuffer information pointer */ static int mvffb_set_par(struct fb_info *fbi) { int retval = 0; u32 mem_len; dcu_di_signal_cfg_t sig_cfg; struct mvffb_info *mvf_fbi = (struct mvffb_info *)fbi->par; dev_dbg(fbi->device, "Reconfiguring framebuffer\n"); if (mvf_fbi->dispdrv && mvf_fbi->dispdrv->drv->setup) { retval = mvf_fbi->dispdrv->drv->setup(mvf_fbi->dispdrv, fbi); if (retval < 0) { dev_err(fbi->device, "setup error, dispdrv:%s.\n", mvf_fbi->dispdrv->drv->name); return -EINVAL; } } if(dcu_is_layer_enabled(mvf_fbi->dcu, fbi->node)) { retval = dcu_disable_layer(mvf_fbi->dcu, fbi->node, true); if (retval < 0) { dev_err(fbi->device, "Error disabling layer, %d\n", fbi->node); return -EINVAL; } dcu_uninit_layer(mvf_fbi->dcu, fbi->node); } mvffb_set_fix(fbi); mem_len = fbi->var.yres_virtual * fbi->fix.line_length; if (!fbi->fix.smem_start || (mem_len > fbi->fix.smem_len)) { if (fbi->fix.smem_start) mvffb_unmap_video_memory(fbi); if (mvffb_map_video_memory(fbi) < 0) return -ENOMEM; } if (mvf_fbi->next_blank != FB_BLANK_UNBLANK) return retval; if (!mvf_fbi->layer) { printk("Framebuffer: Configuring layer in set_par"); memset(&sig_cfg, 0, sizeof(sig_cfg)); if (fbi->var.vmode & FB_VMODE_INTERLACED) sig_cfg.interlaced = true; else sig_cfg.interlaced = false; if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) sig_cfg.Hsync_pol = true; else sig_cfg.Hsync_pol = false; if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) sig_cfg.Vsync_pol = true; else sig_cfg.Vsync_pol = false; if (fbi->var.sync & FB_SYNC_INV_PIX_CLK) sig_cfg.clk_pol = true; else sig_cfg.clk_pol = false; if (fbi->var.sync & FB_SYNC_DATA_INVERT) sig_cfg.data_pol = true; else sig_cfg.data_pol = false; printk("Framebuffer: FB Params are Resolution x = %d, y = %d, left_margin = %d, hsync_len= %d, right_margin = %d, upper_margin = %d, vsync_len = %d lower_margin = %d bpp = %d\n", \ fbi->var.xres, fbi->var.yres, fbi->var.left_margin, fbi->var.hsync_len, fbi->var.right_margin, \ fbi->var.upper_margin, fbi->var.vsync_len, fbi->var.lower_margin, fbi->var.bits_per_pixel); dev_dbg(fbi->device, "pixclock = %ul Hz\n", (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); if (dcu_init_panel(mvf_fbi->dcu, (PICOS2KHZ(fbi->var.pixclock)) * 1000UL, fbi->var.xres, fbi->var.yres, fbi->var.left_margin, fbi->var.hsync_len, fbi->var.right_margin, fbi->var.upper_margin, fbi->var.vsync_len, fbi->var.lower_margin, 0, sig_cfg) != 0) { dev_err(fbi->device, "mvffb: Error initializing panel.\n"); return -EINVAL; } dcu_enable(mvf_fbi->dcu); } fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var, &fbi->modelist); dcu_init_layer(mvf_fbi->dcu, fbi->node, bpp_to_pixfmt(fbi), fbi->var.xres, fbi->var.yres, fbi->fix.smem_start, 0, 0); dcu_update_layer_buffer(mvf_fbi->dcu, fbi->node, fbi->fix.smem_start); dcu_enable_layer(mvf_fbi->dcu, fbi->node); dcu_set_layer_position(mvf_fbi->dcu, fbi->node, 0,0); if (mvf_fbi->dispdrv && mvf_fbi->dispdrv->drv->enable) { retval = mvf_fbi->dispdrv->drv->enable(mvf_fbi->dispdrv); if (retval < 0) { dev_err(fbi->device, "enable error, dispdrv:%s.\n", mvf_fbi->dispdrv->drv->name); return -EINVAL; } } return retval; } /* * Check framebuffer variable parameters and adjust to valid values. * * @param var framebuffer variable parameters * * @param info framebuffer information pointer */ static int mvffb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { u32 vtotal; u32 htotal; /* DCU does not support rotation, TODO: add software implementation? if (var->rotate > IPU_ROTATE_VERT_FLIP) var->rotate = IPU_ROTATE_NONE; */ if (var->xres_virtual < var->xres) var->xres_virtual = var->xres; /* TODO: Remove magic number 2 and replace with double buffer or not */ if (var->yres_virtual < var->yres) var->yres_virtual = var->yres * 2; if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && (var->bits_per_pixel != 16) && (var->bits_per_pixel != 8)) var->bits_per_pixel = 16; switch (var->bits_per_pixel) { case 8: var->red.length = 3; var->red.offset = 5; var->red.msb_right = 0; var->green.length = 3; var->green.offset = 2; var->green.msb_right = 0; var->blue.length = 2; var->blue.offset = 0; var->blue.msb_right = 0; var->transp.length = 0; var->transp.offset = 0; var->transp.msb_right = 0; break; case 16: var->red.length = 5; var->red.offset = 11; var->red.msb_right = 0; var->green.length = 6; var->green.offset = 5; var->green.msb_right = 0; var->blue.length = 5; var->blue.offset = 0; var->blue.msb_right = 0; var->transp.length = 0; var->transp.offset = 0; var->transp.msb_right = 0; break; case 24: var->red.length = 8; var->red.offset = 16; var->red.msb_right = 0; var->green.length = 8; var->green.offset = 8; var->green.msb_right = 0; var->blue.length = 8; var->blue.offset = 0; var->blue.msb_right = 0; var->transp.length = 0; var->transp.offset = 0; var->transp.msb_right = 0; break; case 32: var->red.length = 8; var->red.offset = 16; var->red.msb_right = 0; var->green.length = 8; var->green.offset = 8; var->green.msb_right = 0; var->blue.length = 8; var->blue.offset = 0; var->blue.msb_right = 0; var->transp.length = 8; var->transp.offset = 24; var->transp.msb_right = 0; break; } if (var->pixclock < 1000) { htotal = var->xres + var->right_margin + var->hsync_len + var->left_margin; vtotal = var->yres + var->lower_margin + var->vsync_len + var->upper_margin; var->pixclock = (vtotal * htotal * 6UL) / 100UL; var->pixclock = KHZ2PICOS(var->pixclock); dev_dbg(info->device, "pixclock set for 60Hz refresh = %u ps\n", var->pixclock); } var->height = -1; var->width = -1; var->grayscale = 0; return 0; } /* * Function to handle custom ioctls for MVF framebuffer. * * @param inode inode struct * * @param file file struct * * @param cmd Ioctl command to handle * * @param arg User pointer to command arguments * * @param fbi framebuffer information pointer */ static int mvffb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) { int retval = 0; int __user *argp = (void __user *)arg; struct mvffb_info *mvf_fbi = (struct mvffb_info *)fbi->par; switch (cmd) { case MVFFB_SET_LAYER_ALPHA: { struct mvffb_layer_alpha ga; if (copy_from_user(&ga, (void *)arg, sizeof(ga))) { retval = -EFAULT; break; } if(dcu_config_layer_alpha(mvf_fbi->dcu, fbi->node, ga.alpha, ga.blend_enable)) { retval = -EINVAL; break; } printk("Framebuffer: Set Layer Alpha of %s to %d\n", fbi->fix.id, ga.alpha); dev_dbg(fbi->device, "Set Layer Alpha of %s to %d\n", fbi->fix.id, ga.alpha); break; } case FBIO_ALLOC: { int size; struct mvffb_alloc_list *mem; mem = kzalloc(sizeof(*mem), GFP_KERNEL); if (mem == NULL) return -ENOMEM; if (get_user(size, argp)) return -EFAULT; mem->size = PAGE_ALIGN(size); mem->cpu_addr = dma_alloc_coherent(fbi->device, size, &mem->phy_addr, GFP_DMA); if (mem->cpu_addr == NULL) { kfree(mem); return -ENOMEM; } list_add(&mem->list, &fb_alloc_list); dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n", mem->size, mem->phy_addr); if (put_user(mem->phy_addr, argp)) return -EFAULT; break; } case FBIO_FREE: { unsigned long offset; struct mvffb_alloc_list *mem; if (get_user(offset, argp)) return -EFAULT; retval = -EINVAL; list_for_each_entry(mem, &fb_alloc_list, list) { if (mem->phy_addr == offset) { list_del(&mem->list); dma_free_coherent(fbi->device, mem->size, mem->cpu_addr, mem->phy_addr); kfree(mem); retval = 0; break; } } break; } case MVFFB_GET_FB_BLANK: { struct mvffb_info *mvf_fbi = (struct mvffb_info *)fbi->par; if (put_user(mvf_fbi->cur_blank, argp)) return -EFAULT; break; } case MVFFB_SETUP_LAYER: { struct mvffb_layer_pos pos; struct fb_info *bg_fbi = NULL; struct mvffb_info *bg_mvffbi = NULL; if (copy_from_user(&pos, (void *)arg, sizeof(pos))) { retval = -EFAULT; break; } bg_fbi = found_registered_fb(0, mvf_fbi->dcu_id); if (bg_fbi) bg_mvffbi = ((struct mvffb_info *)(bg_fbi->par)); if (bg_fbi == NULL) { printk("Framebuffer: Cannot find the background framebuffer\n"); dev_err(fbi->device, "Cannot find the " "background framebuffer \n"); retval = -ENOENT; break; } // if fb is unblank, check if the pos fit the display if (mvf_fbi->cur_blank == FB_BLANK_UNBLANK) { if (fbi->var.xres + pos.x > bg_fbi->var.xres) { if (bg_fbi->var.xres < fbi->var.xres) pos.x = 0; else pos.x = bg_fbi->var.xres - fbi->var.xres; } if (fbi->var.yres + pos.y > bg_fbi->var.yres) { if (bg_fbi->var.yres < fbi->var.yres) pos.y = 0; else pos.y = bg_fbi->var.yres - fbi->var.yres; } } retval = dcu_set_layer_position(mvf_fbi->dcu, fbi->node, pos.x, pos.y); if(retval != 0) { break; } if (copy_to_user((void *)arg, &pos, sizeof(pos))) { retval = -EFAULT; break; } break; } default: retval = -EINVAL; } return retval; } /* * mvffb_blank(): * Blank the display. */ static int mvffb_blank(int blank, struct fb_info *info) { struct mvffb_info *mvf_fbi = (struct mvffb_info *)info->par; int ret = 0; dev_dbg(info->device, "blank = %d\n", blank); if (mvf_fbi->cur_blank == blank) return 0; mvf_fbi->next_blank = blank; switch (blank) { case FB_BLANK_POWERDOWN: case FB_BLANK_VSYNC_SUSPEND: case FB_BLANK_HSYNC_SUSPEND: case FB_BLANK_NORMAL: if (mvf_fbi->dispdrv && mvf_fbi->dispdrv->drv->disable) mvf_fbi->dispdrv->drv->disable(mvf_fbi->dispdrv); //TODO: Remove / understand true. Not used in dcu driver. dcu_disable_layer(mvf_fbi->dcu, info->node, true); dcu_uninit_layer(mvf_fbi->dcu, info->node); dcu_uninit_panel(mvf_fbi->dcu); break; case FB_BLANK_UNBLANK: ret = mvffb_set_par(info); break; } if (!ret) mvf_fbi->cur_blank = blank; return ret; } /* * Pan or Wrap the Display * * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag * * @param var Variable screen buffer information * @param info Framebuffer information pointer */ static int mvffb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { /* struct mvffb_info *mvf_fbi = (struct mvffb_info *)info->par, *mvf_graphic_fbi = NULL; u_int y_bottom; unsigned int fr_xoff, fr_yoff, fr_w, fr_h; unsigned long base, active_alpha_phy_addr = 0; bool loc_alpha_en = false; int fb_stride; int i; if (info->var.yoffset == var->yoffset) return 0; // No change, do nothing // no pan display during fb blank if (mvf_fbi->cur_blank != FB_BLANK_UNBLANK) return -EINVAL; y_bottom = var->yoffset; if (y_bottom > info->var.yres_virtual) return -EINVAL; fb_stride = info->fix.line_length; base = info->fix.smem_start; fr_xoff = var->xoffset; fr_w = info->var.xres_virtual; if (!(var->vmode & FB_VMODE_YWRAP)) { dev_dbg(info->device, "Y wrap disabled\n"); fr_yoff = var->yoffset % info->var.yres; fr_h = info->var.yres; base += info->fix.line_length * info->var.yres * (var->yoffset / info->var.yres); } else { dev_dbg(info->device, "Y wrap enabled\n"); fr_yoff = var->yoffset; fr_h = info->var.yres_virtual; } base += fr_yoff * fb_stride + fr_xoff; // Check if DP local alpha is enabled and find the graphic fb for (i = 0; i < num_registered_fb; i++) { char *bg_id = "DISP3 BG"; char *fg_id = "DISP3 FG"; char *idstr = registered_fb[i]->fix.id; bg_id[4] += mvf_fbi->dcu_id; fg_id[4] += mvf_fbi->dcu_id; if ((strcmp(idstr, bg_id) == 0 || strcmp(idstr, fg_id) == 0) && ((struct mvffb_info *) (registered_fb[i]->par))->alpha_chan_en) { loc_alpha_en = true; mvf_graphic_fbi = (struct mvffb_info *) (registered_fb[i]->par); active_alpha_phy_addr = mvf_fbi->cur_dcu_alpha_buf ? mvf_graphic_fbi->alpha_phy_addr1 : mvf_graphic_fbi->alpha_phy_addr0; dev_dbg(info->device, "Updating SDC alpha " "buf %d address=0x%08lX\n", !mvf_fbi->cur_dcu_alpha_buf, active_alpha_phy_addr); break; } } if (down_timeout(&mvf_fbi->flip_sem, HZ/2)) { dev_err(info->device, "timeout when waiting for flip irq\n"); return -ETIMEDOUT; } ++mvf_fbi->cur_dcu_buf; mvf_fbi->cur_dcu_buf %= 3; mvf_fbi->cur_dcu_alpha_buf = !mvf_fbi->cur_dcu_alpha_buf; dev_dbg(info->device, "Updating SDC %s buf %d address=0x%08lX\n", info->fix.id, mvf_fbi->cur_dcu_buf, base); if (dcu_update_layer_buffer(mvf_fbi->dcu, mvf_fbi->dcu_ch, IPU_INPUT_BUFFER, mvf_fbi->cur_dcu_buf, base) == 0) { // Update the DP local alpha buffer only for graphic plane if (loc_alpha_en && mvf_graphic_fbi == mvf_fbi && dcu_update_layer_buffer(mvf_graphic_fbi->dcu, mvf_graphic_fbi->dcu_ch, IPU_ALPHA_IN_BUFFER, mvf_fbi->cur_dcu_alpha_buf, active_alpha_phy_addr) == 0) { dcu_select_buffer(mvf_graphic_fbi->dcu, mvf_graphic_fbi->dcu_ch, IPU_ALPHA_IN_BUFFER, mvf_fbi->cur_dcu_alpha_buf); } // update u/v offset dcu_update_layer_offset(mvf_fbi->dcu, mvf_fbi->dcu_ch, IPU_INPUT_BUFFER, bpp_to_pixfmt(info), fr_w, fr_h, fr_w, 0, 0, fr_yoff, fr_xoff); dcu_select_buffer(mvf_fbi->dcu, mvf_fbi->dcu_ch, IPU_INPUT_BUFFER, mvf_fbi->cur_dcu_buf); dcu_clear_irq(mvf_fbi->dcu, mvf_fbi->dcu_ch_irq); dcu_enable_irq(mvf_fbi->dcu, mvf_fbi->dcu_ch_irq); } else { dev_err(info->device, "Error updating SDC buf %d to address=0x%08lX, " "current buf %d, buf0 ready %d, buf1 ready %d, " "buf2 ready %d\n", mvf_fbi->cur_dcu_buf, base, dcu_get_cur_buffer_idx(mvf_fbi->dcu, mvf_fbi->dcu_ch, IPU_INPUT_BUFFER), dcu_check_buffer_ready(mvf_fbi->dcu, mvf_fbi->dcu_ch, IPU_INPUT_BUFFER, 0), dcu_check_buffer_ready(mvf_fbi->dcu, mvf_fbi->dcu_ch, IPU_INPUT_BUFFER, 1), dcu_check_buffer_ready(mvf_fbi->dcu, mvf_fbi->dcu_ch, IPU_INPUT_BUFFER, 2)); ++mvf_fbi->cur_dcu_buf; mvf_fbi->cur_dcu_buf %= 3; ++mvf_fbi->cur_dcu_buf; mvf_fbi->cur_dcu_buf %= 3; mvf_fbi->cur_dcu_alpha_buf = !mvf_fbi->cur_dcu_alpha_buf; dcu_clear_irq(mvf_fbi->dcu, mvf_fbi->dcu_ch_irq); dcu_enable_irq(mvf_fbi->dcu, mvf_fbi->dcu_ch_irq); return -EBUSY; } dev_dbg(info->device, "Update complete\n"); info->var.yoffset = var->yoffset; */ return 0; } /* * Function to handle custom mmap for MVF framebuffer. * * @param fbi framebuffer information pointer * * @param vma Pointer to vm_area_struct */ static int mvffb_mmap(struct fb_info *fbi, struct vm_area_struct *vma) { bool found = false; u32 len; unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; struct mvffb_alloc_list *mem; struct mvffb_info *mvf_fbi = (struct mvffb_info *)fbi->par; if (offset < fbi->fix.smem_len) { /* mapping framebuffer memory */ len = fbi->fix.smem_len - offset; vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT; } else if ((vma->vm_pgoff == (mvf_fbi->alpha_phy_addr0 >> PAGE_SHIFT)) || (vma->vm_pgoff == (mvf_fbi->alpha_phy_addr1 >> PAGE_SHIFT))) { len = mvf_fbi->alpha_mem_len; } else { list_for_each_entry(mem, &fb_alloc_list, list) { if (offset == mem->phy_addr) { found = true; len = mem->size; break; } } if (!found) return -EINVAL; } len = PAGE_ALIGN(len); if (vma->vm_end - vma->vm_start > len) return -EINVAL; /* make buffers bufferable */ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); vma->vm_flags |= VM_IO | VM_RESERVED; if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, vma->vm_end - vma->vm_start, vma->vm_page_prot)) { dev_dbg(fbi->device, "mmap remap_pfn_range failed\n"); return -ENOBUFS; } return 0; } /*! * This structure contains the pointers to the control functions that are * invoked by the core framebuffer driver to perform operations like * blitting, rectangle filling, copy regions and cursor definition. */ static struct fb_ops mvffb_ops = { .owner = THIS_MODULE, .fb_set_par = mvffb_set_par, .fb_check_var = mvffb_check_var, .fb_pan_display = mvffb_pan_display, .fb_ioctl = mvffb_ioctl, .fb_mmap = mvffb_mmap, //.fb_fillrect = cfb_fillrect, //.fb_copyarea = cfb_copyarea, //.fb_imageblit = cfb_imageblit, .fb_blank = mvffb_blank, }; /* * Suspends the framebuffer and blanks the screen. Power management support */ static int mvffb_suspend(struct platform_device *pdev, pm_message_t state) { struct fb_info *fbi = platform_get_drvdata(pdev); struct mvffb_info *mvf_fbi = (struct mvffb_info *)fbi->par; int saved_blank; console_lock(); fb_set_suspend(fbi, 1); saved_blank = mvf_fbi->cur_blank; mvffb_blank(FB_BLANK_POWERDOWN, fbi); mvf_fbi->next_blank = saved_blank; console_unlock(); return 0; } /* * Resumes the framebuffer and unblanks the screen. Power management support */ static int mvffb_resume(struct platform_device *pdev) { struct fb_info *fbi = platform_get_drvdata(pdev); struct mvffb_info *mvf_fbi = (struct mvffb_info *)fbi->par; console_lock(); mvffb_blank(mvf_fbi->next_blank, fbi); fb_set_suspend(fbi, 0); console_unlock(); return 0; } /* * Main framebuffer functions */ /*! * Allocates the DRAM memory for the frame buffer. This buffer is remapped * into a non-cached, non-buffered, memory region to allow palette and pixel * writes to occur without flushing the cache. Once this area is remapped, * all virtual memory access to the video memory should occur at the new region. * * @param fbi framebuffer information pointer * * @return Error code indicating success or failure */ static int mvffb_map_video_memory(struct fb_info *fbi) { if (fbi->fix.smem_len < fbi->var.yres_virtual * fbi->fix.line_length) fbi->fix.smem_len = fbi->var.yres_virtual * fbi->fix.line_length; fbi->screen_base = dma_alloc_writecombine(fbi->device, fbi->fix.smem_len, (dma_addr_t *)&fbi->fix.smem_start, GFP_DMA); if (fbi->screen_base == 0) { dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); fbi->fix.smem_len = 0; fbi->fix.smem_start = 0; return -EBUSY; } dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n", (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len); fbi->screen_size = fbi->fix.smem_len; /* Clear the screen */ memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); return 0; } /*! * De-allocates the DRAM memory for the frame buffer. * * @param fbi framebuffer information pointer * * @return Error code indicating success or failure */ static int mvffb_unmap_video_memory(struct fb_info *fbi) { dma_free_writecombine(fbi->device, fbi->fix.smem_len, fbi->screen_base, fbi->fix.smem_start); fbi->screen_base = 0; fbi->fix.smem_start = 0; fbi->fix.smem_len = 0; return 0; } /*! * Initializes the framebuffer information pointer. After allocating * sufficient memory for the framebuffer structure, the fields are * filled with custom information passed in from the configurable * structures. This includes information such as bits per pixel, * color maps, screen width/height and RGBA offsets. * * @return Framebuffer structure initialized with our information */ static struct fb_info *mvffb_init_fbinfo(struct device *dev, struct fb_ops *ops) { struct fb_info *fbi; struct mvffb_info *mvffbi; /* * Allocate sufficient memory for the fb structure */ fbi = framebuffer_alloc(sizeof(struct mvffb_info), dev); if (!fbi) return NULL; mvffbi = (struct mvffb_info *)fbi->par; fbi->var.activate = FB_ACTIVATE_NOW; fbi->fbops = ops; fbi->flags = FBINFO_MODULE; fbi->pseudo_palette = mvffbi->pseudo_palette; /* * Allocate colormap */ fb_alloc_cmap(&fbi->cmap, 16, 0); return fbi; } static int mvffb_dispdrv_init(struct platform_device *pdev, struct fb_info *fbi) { struct dcuv4_fb_platform_data *plat_data = pdev->dev.platform_data; struct mvffb_info *mvffbi = (struct mvffb_info *)fbi->par; struct mvf_dispdrv_setting setting; char disp_dev[32], *default_dev = "lcd"; int ret = 0; setting.if_fmt = plat_data->interface_pix_fmt; setting.dft_mode_str = plat_data->mode_str; setting.default_bpp = plat_data->default_bpp; //TODO: What should be our default bpp? if (!setting.default_bpp) setting.default_bpp = 16; setting.fbi = fbi; if (!strlen(plat_data->disp_dev)) { memcpy(disp_dev, default_dev, strlen(default_dev)); disp_dev[strlen(default_dev)] = '\0'; } else { memcpy(disp_dev, plat_data->disp_dev, strlen(plat_data->disp_dev)); disp_dev[strlen(plat_data->disp_dev)] = '\0'; } dev_info(&pdev->dev, "Register mvf display driver %s\n", disp_dev); mvffbi->dispdrv = mvf_dispdrv_gethandle(disp_dev, &setting); if (IS_ERR(mvffbi->dispdrv)) { ret = PTR_ERR(mvffbi->dispdrv); dev_err(&pdev->dev, "NO mvf display driver found!\n"); return ret; } else { // fix-up mvffbi->dcu_pix_fmt = setting.if_fmt; mvffbi->default_bpp = setting.default_bpp; // setting - set default setting of dev_id if plat_data dcu_id is DCU0 if(!plat_data->dcu_id) mvffbi->dcu_id = setting.dev_id; } return ret; } /* * Parse user specified options (`video=trident:') * example: * orig - video=mvffb0:dev=lcd,800x480M-16@55,if=RGB565,bpp=16,noaccel * TODO: video=mvffb0:dev=lcd,if=BGRA8888,dcu=0,NEC-WQVGA,primary video=mvffb1:dev=lcd,if=BGRA8888,dcu=1,NEC-WQVGA * Proposed: video=mvfdcu0fb:BGRA8888,NEC-WQVGA dcu0_primary * Supported Below: video=mvffb0:dev=lcd,if=BGRA8888,dcu=0,NEC-WQVGA * TODO: re-visit the supported formats below to enhance the list */ static int mvffb_option_setup(struct platform_device *pdev) { struct dcuv4_fb_platform_data *pdata = pdev->dev.platform_data; char *options, *opt, *fb_mode_str = NULL; char name[] = "mvffb0"; name[5] += pdev->id; fb_get_options(name, &options); if (!options || !*options) return 0; while ((opt = strsep(&options, ",")) != NULL) { if (!*opt) continue; if (!strncmp(opt, "dev=", 4)) { memcpy(pdata->disp_dev, opt + 4, strlen(opt) - 4); pdata->disp_dev[strlen(opt) - 4] = '\0'; continue; } if (!strncmp(opt, "if=", 3)) { if (!strncmp(opt+3, "RGB24", 5)) { pdata->interface_pix_fmt = V4L2_PIX_FMT_RGB24; continue; } if (!strncmp(opt+3, "RGB565", 6)) { pdata->interface_pix_fmt = V4L2_PIX_FMT_RGB565; continue; } if (!strncmp(opt+3, "RGB32", 5)) { pdata->interface_pix_fmt = V4L2_PIX_FMT_RGB32; continue; } /*if (!strncmp(opt+6, "BGR24", 5)) { pdata->interface_pix_fmt = IPU_PIX_FMT_BGR24; continue; } if (!strncmp(opt+3, "GBR24", 5)) { pdata->interface_pix_fmt = IPU_PIX_FMT_GBR24; continue; } if (!strncmp(opt+3, "RGB666", 6)) { pdata->interface_pix_fmt = IPU_PIX_FMT_RGB666; continue; } if (!strncmp(opt+3, "YUV444", 6)) { pdata->interface_pix_fmt = IPU_PIX_FMT_YUV444; continue; } if (!strncmp(opt+3, "LVDS666", 7)) { pdata->interface_pix_fmt = IPU_PIX_FMT_LVDS666; continue; } if (!strncmp(opt+3, "YUYV16", 6)) { pdata->interface_pix_fmt = IPU_PIX_FMT_YUYV; continue; } if (!strncmp(opt+3, "UYVY16", 6)) { pdata->interface_pix_fmt = IPU_PIX_FMT_UYVY; continue; } if (!strncmp(opt+3, "YVYU16", 6)) { pdata->interface_pix_fmt = IPU_PIX_FMT_YVYU; continue; } if (!strncmp(opt+3, "VYUY16", 6)) { pdata->interface_pix_fmt = IPU_PIX_FMT_VYUY; continue; } */ } if(!strncmp(opt,"dcu=", 4)) pdata->dcu_id = simple_strtoul(opt + 4, NULL, 0); if (!strncmp(opt, "bpp=", 4)) pdata->default_bpp = simple_strtoul(opt + 4, NULL, 0); else fb_mode_str = opt; } if (fb_mode_str) pdata->mode_str = fb_mode_str; return 0; } static int mvffb_register(struct fb_info *fbi) { struct mvffb_info *mvffbi = (struct mvffb_info *)fbi->par; struct fb_videomode m; int ret = 0; char disp_id[] = "DCU0 BG LAYER"; char layer_id[] = "DCU0 FG LAYER"; if(!mvffbi->layer) { disp_id[3] += mvffbi->dcu_id; strcpy(fbi->fix.id, disp_id); } else { layer_id[3] += mvffbi->dcu_id; strcpy(fbi->fix.id, layer_id); } mvffb_check_var(&fbi->var, fbi); mvffb_set_fix(fbi); /*added first mode to fbi modelist*/ if (!fbi->modelist.next || !fbi->modelist.prev) INIT_LIST_HEAD(&fbi->modelist); fb_var_to_videomode(&m, &fbi->var); fb_add_videomode(&m, &fbi->modelist); fbi->var.activate |= FB_ACTIVATE_FORCE; console_lock(); fbi->flags |= FBINFO_MISC_USEREVENT; ret = fb_set_var(fbi, &fbi->var); fbi->flags &= ~FBINFO_MISC_USEREVENT; console_unlock(); if (mvffbi->next_blank == FB_BLANK_UNBLANK) { console_lock(); fb_blank(fbi, FB_BLANK_UNBLANK); console_unlock(); } ret = register_framebuffer(fbi); return ret; } static void mvffb_unregister(struct fb_info *fbi) { unregister_framebuffer(fbi); } //Setting up the overlay frame buffer and registering the same. static int mvffb_setup_overlay(struct platform_device *pdev, struct fb_info *fbi_bg, struct resource *res) { struct fb_info *ovfbi; struct mvffb_info *mvffbi_bg = (struct mvffb_info *)fbi_bg->par; struct mvffb_info *mvffbi_fg; int ret = 0; ovfbi = mvffb_init_fbinfo(&pdev->dev, &mvffb_ops); if (!ovfbi) { ret = -ENOMEM; goto init_ovfbinfo_failed; } mvffbi_fg = (struct mvffb_info *)ovfbi->par; mvffbi_fg->dcu = dcu_get_soc(mvffbi_bg->dcu_id); if (IS_ERR(mvffbi_fg->dcu)) { ret = -ENODEV; goto get_dcu_failed; } mvffbi_fg->dcu_id = mvffbi_bg->dcu_id; mvffbi_fg->dcu_pix_fmt = mvffbi_bg->dcu_pix_fmt; mvffbi_fg->layer = true; mvffbi_fg->cur_blank = mvffbi_fg->next_blank = FB_BLANK_POWERDOWN; // Need dummy values until real panel is configured ovfbi->var.xres = 240; ovfbi->var.yres = 320; if (res && res->start && res->end) { ovfbi->fix.smem_len = res->end - res->start + 1; ovfbi->fix.smem_start = res->start; ovfbi->screen_base = ioremap( ovfbi->fix.smem_start, ovfbi->fix.smem_len); } ret = mvffb_register(ovfbi); if (ret < 0) goto register_ov_failed; mvffbi_bg->ovfbi[ovfbi->node] = ovfbi; return ret; register_ov_failed: get_dcu_failed: fb_dealloc_cmap(&ovfbi->cmap); framebuffer_release(ovfbi); init_ovfbinfo_failed: return ret; } static void mvffb_unsetup_overlay(struct fb_info *fbi_bg, int layer_num) { struct mvffb_info *mvffbi_bg = (struct mvffb_info *)fbi_bg->par; struct fb_info *ovfbi = mvffbi_bg->ovfbi[layer_num]; mvffb_unregister(ovfbi); if (&ovfbi->cmap) fb_dealloc_cmap(&ovfbi->cmap); framebuffer_release(ovfbi); } /*! * Probe routine for the framebuffer driver. It is called during the * driver binding process. The following functions are performed in * this routine: Framebuffer initialization, Memory allocation and * mapping, Framebuffer registration, DCU initialization. * * @return Appropriate error code to the kernel common code */ static int mvffb_probe(struct platform_device *pdev) { //struct dcuv4_fb_platform_data *plat_data = pdev->dev.platform_data; struct fb_info *fbi; struct mvffb_info *mvffbi; struct resource *res; int ret = 0, i = 0; printk("Framebuffer: Probe Entered!!!!\n"); /* * Initialize FB structures */ fbi = mvffb_init_fbinfo(&pdev->dev, &mvffb_ops); if (!fbi) { ret = -ENOMEM; goto init_fbinfo_failed; } printk("Framebuffer: init complete!!!!\n"); mvffb_option_setup(pdev); printk("Framebuffer: option setup complete!!!!\n"); mvffbi = (struct mvffb_info *)fbi->par; ret = mvffb_dispdrv_init(pdev, fbi); if (ret < 0) goto init_dispdrv_failed; ret = dcu_set_usage(mvffbi->dcu_id); if (ret < 0) { dev_err(&pdev->dev, "dcu%d already in use\n", mvffbi->dcu_id); goto dcu_in_busy; } printk("Framebuffer: disp drv init complete!!!!\n"); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res && res->start && res->end) { fbi->fix.smem_len = res->end - res->start + 1; fbi->fix.smem_start = res->start; fbi->screen_base = ioremap(fbi->fix.smem_start, fbi->fix.smem_len); memset(fbi->screen_base, 0, fbi->fix.smem_len); } mvffbi->dcu = dcu_get_soc(mvffbi->dcu_id); if (IS_ERR(mvffbi->dcu)) { ret = -ENODEV; goto get_dcu_failed; } printk("Framebuffer: got dcu soc handle!!!!\n"); /* first user uses DP(display processor) with alpha feature */ if (!g_dp_in_use[mvffbi->dcu_id]) { mvffbi->cur_blank = mvffbi->next_blank = FB_BLANK_UNBLANK; if(dcu_config_layer_alpha(mvffbi->dcu, fbi->node, 0x80, ALPHA_BLEND_ENABLED)) { ret = -EINVAL; goto mvffb_register_failed; } //dcu_disp_set_color_key(mvffbi->dcu, mvffbi->dcu_ch, false, 0); ret = mvffb_register(fbi); if (ret < 0) goto mvffb_register_failed; printk("Framebuffer: registered fb with alpha complete!!!!\n"); res = platform_get_resource(pdev, IORESOURCE_MEM, 1); for(i = 1; i <= (CONFIG_FB_MVF_NUM_FBS-1); i++) { printk("Framebuffer: Registering Layer %d\n", i); ret = mvffb_setup_overlay(pdev, fbi, res); if (ret < 0) { mvffb_unregister(fbi); goto mvffb_setupoverlay_failed; } } g_dp_in_use[mvffbi->dcu_id] = true; } else { mvffbi->cur_blank = mvffbi->next_blank = FB_BLANK_POWERDOWN; ret = mvffb_register(fbi); if (ret < 0) goto mvffb_register_failed; printk("Framebuffer: registred fb complete!!!!\n"); } platform_set_drvdata(pdev, fbi); #ifdef CONFIG_LOGO printk("Framebuffer: showing logo...!!!!\n"); fb_prepare_logo(fbi, 0); fb_show_logo(fbi, 0); #endif return 0; mvffb_setupoverlay_failed: mvffb_register_failed: get_dcu_failed: dcu_clear_usage(mvffbi->dcu_id); dcu_in_busy: init_dispdrv_failed: fb_dealloc_cmap(&fbi->cmap); framebuffer_release(fbi); init_fbinfo_failed: return ret; } static int mvffb_remove(struct platform_device *pdev) { struct fb_info *fbi = platform_get_drvdata(pdev); struct mvffb_info *mvf_fbi = fbi->par; int i = 0; if (!fbi) return 0; mvffb_blank(FB_BLANK_POWERDOWN, fbi); mvffb_unregister(fbi); mvffb_unmap_video_memory(fbi); for(i = 1; i <= (CONFIG_FB_MVF_NUM_FBS-1); i++) { if (mvf_fbi->ovfbi[i]) { mvffb_blank(FB_BLANK_POWERDOWN, mvf_fbi->ovfbi[i]); mvffb_unsetup_overlay(fbi, i); mvffb_unmap_video_memory(mvf_fbi->ovfbi[i]); } } dcu_clear_usage(mvf_fbi->dcu_id); if (&fbi->cmap) fb_dealloc_cmap(&fbi->cmap); framebuffer_release(fbi); return 0; } /*! * This structure contains pointers to the power management callback functions. */ static struct platform_driver mvffb_driver = { .driver = { .name = MVFFB_NAME, }, .probe = mvffb_probe, .remove = mvffb_remove, .suspend = mvffb_suspend, .resume = mvffb_resume, }; /*! * Main entry function for the framebuffer. The function registers the power * management callback functions with the kernel and also registers the MVFFB * callback functions with the core Linux framebuffer driver \b fbmem.c * * @return Error code indicating success or failure */ int __init mvffb_init(void) { return platform_driver_register(&mvffb_driver); } void mvffb_exit(void) { platform_driver_unregister(&mvffb_driver); } module_init(mvffb_init); module_exit(mvffb_exit); MODULE_AUTHOR("Freescale Semiconductor, Inc."); MODULE_DESCRIPTION("MVF frame buffer driver"); MODULE_LICENSE("GPL"); MODULE_SUPPORTED_DEVICE("fb");