From 77e196752bdd76a0c58ab082658d28c6a90fa40e Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Tue, 16 Dec 2008 11:54:34 +0800 Subject: [ARM] pxafb: allow video memory size to be configurable The amount of video memory size is decided according to the following order: 1. x x by default, which is the backward compatible way 2. size specified in platform data 3. size specified in module parameter 'options' string or specified in kernel boot command line (see updated Documentation/fb/pxafb.txt) And now since the memory is allocated from system memory, the pxafb_mmap can be removed and the default fb_mmap() should be working all right. Also, since we now have introduced the 'struct pxafb_dma_buff' for DMA descriptors and palettes, the allocation can be separated cleanly. NOTE: the LCD DMA actually supports chained transfer (i.e. page-based transfers), to simplify the logic and keep the performance (with less TLB misses when accessing from memory mapped user space), the memory is allocated by alloc_pages_*() to ensures it's physical contiguous. Signed-off-by: Eric Miao Signed-off-by: Eric Miao --- drivers/video/pxafb.c | 131 +++++++++++++++++++++----------------------------- drivers/video/pxafb.h | 17 ++----- 2 files changed, 58 insertions(+), 90 deletions(-) (limited to 'drivers/video') diff --git a/drivers/video/pxafb.c b/drivers/video/pxafb.c index ab689597f259..25bf4b8b6b53 100644 --- a/drivers/video/pxafb.c +++ b/drivers/video/pxafb.c @@ -72,6 +72,8 @@ static int pxafb_activate_var(struct fb_var_screeninfo *var, struct pxafb_info *); static void set_ctrlr_state(struct pxafb_info *fbi, u_int state); +static unsigned long video_mem_size = 0; + static inline unsigned long lcd_readl(struct pxafb_info *fbi, unsigned int off) { @@ -498,20 +500,6 @@ static int pxafb_blank(int blank, struct fb_info *info) return 0; } -static int pxafb_mmap(struct fb_info *info, - struct vm_area_struct *vma) -{ - struct pxafb_info *fbi = (struct pxafb_info *)info; - unsigned long off = vma->vm_pgoff << PAGE_SHIFT; - - if (off < info->fix.smem_len) { - vma->vm_pgoff += fbi->video_offset / PAGE_SIZE; - return dma_mmap_writecombine(fbi->dev, vma, fbi->map_cpu, - fbi->map_dma, fbi->map_size); - } - return -EINVAL; -} - static struct fb_ops pxafb_ops = { .owner = THIS_MODULE, .fb_check_var = pxafb_check_var, @@ -521,7 +509,6 @@ static struct fb_ops pxafb_ops = { .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, .fb_blank = pxafb_blank, - .fb_mmap = pxafb_mmap, }; /* @@ -614,7 +601,7 @@ static int setup_frame_dma(struct pxafb_info *fbi, int dma, int pal, dma_desc = &fbi->dma_buff->dma_desc[dma]; dma_desc_off = offsetof(struct pxafb_dma_buff, dma_desc[dma]); - dma_desc->fsadr = fbi->screen_dma + offset; + dma_desc->fsadr = fbi->video_mem_phys + offset; dma_desc->fidr = 0; dma_desc->ldcmd = size; @@ -1267,69 +1254,30 @@ static int pxafb_resume(struct platform_device *dev) #define pxafb_resume NULL #endif -/* - * pxafb_map_video_memory(): - * 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. - */ -static int __devinit pxafb_map_video_memory(struct pxafb_info *fbi) +static int __devinit pxafb_init_video_memory(struct pxafb_info *fbi) { - /* - * We reserve one page for the palette, plus the size - * of the framebuffer. - */ - fbi->video_offset = PAGE_ALIGN(sizeof(struct pxafb_dma_buff)); - fbi->map_size = PAGE_ALIGN(fbi->fb.fix.smem_len + fbi->video_offset); - fbi->map_cpu = dma_alloc_writecombine(fbi->dev, fbi->map_size, - &fbi->map_dma, GFP_KERNEL); - - if (fbi->map_cpu) { - /* prevent initial garbage on screen */ - memset(fbi->map_cpu, 0, fbi->map_size); - fbi->fb.screen_base = fbi->map_cpu + fbi->video_offset; - fbi->screen_dma = fbi->map_dma + fbi->video_offset; - - /* - * FIXME: this is actually the wrong thing to place in - * smem_start. But fbdev suffers from the problem that - * it needs an API which doesn't exist (in this case, - * dma_writecombine_mmap) - */ - fbi->fb.fix.smem_start = fbi->screen_dma; - fbi->palette_size = fbi->fb.var.bits_per_pixel == 8 ? 256 : 16; - - fbi->dma_buff = (void *) fbi->map_cpu; - fbi->dma_buff_phys = fbi->map_dma; - fbi->palette_cpu = (u16 *) fbi->dma_buff->palette; + int size = PAGE_ALIGN(fbi->video_mem_size); - pr_debug("pxafb: palette_mem_size = 0x%08x\n", fbi->palette_size*sizeof(u16)); - } - - return fbi->map_cpu ? 0 : -ENOMEM; -} + fbi->video_mem = alloc_pages_exact(size, GFP_KERNEL | __GFP_ZERO); + if (fbi->video_mem == NULL) + return -ENOMEM; -static void pxafb_decode_mode_info(struct pxafb_info *fbi, - struct pxafb_mode_info *modes, - unsigned int num_modes) -{ - unsigned int i, smemlen; + fbi->video_mem_phys = virt_to_phys(fbi->video_mem); + fbi->video_mem_size = size; - pxafb_setmode(&fbi->fb.var, &modes[0]); + fbi->fb.fix.smem_start = fbi->video_mem_phys; + fbi->fb.fix.smem_len = fbi->video_mem_size; + fbi->fb.screen_base = fbi->video_mem; - for (i = 0; i < num_modes; i++) { - smemlen = modes[i].xres * modes[i].yres * modes[i].bpp / 8; - if (smemlen > fbi->fb.fix.smem_len) - fbi->fb.fix.smem_len = smemlen; - } + return fbi->video_mem ? 0 : -ENOMEM; } static void pxafb_decode_mach_info(struct pxafb_info *fbi, struct pxafb_mach_info *inf) { unsigned int lcd_conn = inf->lcd_conn; + struct pxafb_mode_info *m; + int i; fbi->cmap_inverse = inf->cmap_inverse; fbi->cmap_static = inf->cmap_static; @@ -1371,7 +1319,22 @@ static void pxafb_decode_mach_info(struct pxafb_info *fbi, fbi->lccr3 |= (lcd_conn & LCD_PCLK_EDGE_FALL) ? LCCR3_PCP : 0; decode_mode: - pxafb_decode_mode_info(fbi, inf->modes, inf->num_modes); + pxafb_setmode(&fbi->fb.var, &inf->modes[0]); + + /* decide video memory size as follows: + * 1. default to mode of maximum resolution + * 2. allow platform to override + * 3. allow module parameter to override + */ + for (i = 0, m = &inf->modes[0]; i < inf->num_modes; i++, m++) + fbi->video_mem_size = max_t(size_t, fbi->video_mem_size, + m->xres * m->yres * m->bpp / 8); + + if (inf->video_mem_size > fbi->video_mem_size) + fbi->video_mem_size = inf->video_mem_size; + + if (video_mem_size > fbi->video_mem_size) + fbi->video_mem_size = video_mem_size; } static struct pxafb_info * __devinit pxafb_init_fbinfo(struct device *dev) @@ -1499,7 +1462,9 @@ static int __devinit parse_opt(struct device *dev, char *this_opt) s[0] = '\0'; - if (!strncmp(this_opt, "mode:", 5)) { + if (!strncmp(this_opt, "vmem:", 5)) { + video_mem_size = memparse(this_opt + 5, NULL); + } else if (!strncmp(this_opt, "mode:", 5)) { return parse_opt_mode(dev, this_opt); } else if (!strncmp(this_opt, "pixclock:", 9)) { mode->pixclock = simple_strtoul(this_opt+9, NULL, 0); @@ -1736,12 +1701,20 @@ static int __devinit pxafb_probe(struct platform_device *dev) goto failed_free_res; } - /* Initialize video memory */ - ret = pxafb_map_video_memory(fbi); + fbi->dma_buff_size = PAGE_ALIGN(sizeof(struct pxafb_dma_buff)); + fbi->dma_buff = dma_alloc_coherent(fbi->dev, fbi->dma_buff_size, + &fbi->dma_buff_phys, GFP_KERNEL); + if (fbi->dma_buff == NULL) { + dev_err(&dev->dev, "failed to allocate memory for DMA\n"); + ret = -ENOMEM; + goto failed_free_io; + } + + ret = pxafb_init_video_memory(fbi); if (ret) { dev_err(&dev->dev, "Failed to allocate video RAM: %d\n", ret); ret = -ENOMEM; - goto failed_free_io; + goto failed_free_dma; } irq = platform_get_irq(dev, 0); @@ -1811,8 +1784,10 @@ failed_free_cmap: failed_free_irq: free_irq(irq, fbi); failed_free_mem: - dma_free_writecombine(&dev->dev, fbi->map_size, - fbi->map_cpu, fbi->map_dma); + free_pages_exact(fbi->video_mem, fbi->video_mem_size); +failed_free_dma: + dma_free_coherent(&dev->dev, fbi->dma_buff_size, + fbi->dma_buff, fbi->dma_buff_phys); failed_free_io: iounmap(fbi->mmio_base); failed_free_res: @@ -1847,8 +1822,10 @@ static int __devexit pxafb_remove(struct platform_device *dev) irq = platform_get_irq(dev, 0); free_irq(irq, fbi); - dma_free_writecombine(&dev->dev, fbi->map_size, - fbi->map_cpu, fbi->map_dma); + free_pages_exact(fbi->video_mem, fbi->video_mem_size); + + dma_free_writecombine(&dev->dev, fbi->dma_buff_size, + fbi->dma_buff, fbi->dma_buff_phys); iounmap(fbi->mmio_base); diff --git a/drivers/video/pxafb.h b/drivers/video/pxafb.h index d8eb93fa03a3..0981938682ef 100644 --- a/drivers/video/pxafb.h +++ b/drivers/video/pxafb.h @@ -69,24 +69,15 @@ struct pxafb_info { void __iomem *mmio_base; struct pxafb_dma_buff *dma_buff; + size_t dma_buff_size; dma_addr_t dma_buff_phys; dma_addr_t fdadr[DMA_MAX]; - /* - * These are the addresses we mapped - * the framebuffer memory region to. - */ - /* raw memory addresses */ - dma_addr_t map_dma; /* physical */ - u_char * map_cpu; /* virtual */ - u_int map_size; - - /* addresses of pieces placed in raw buffer */ - u_char * screen_cpu; /* virtual address of frame buffer */ - dma_addr_t screen_dma; /* physical address of frame buffer */ + void __iomem *video_mem; /* virtual address of frame buffer */ + unsigned long video_mem_phys; /* physical address of frame buffer */ + size_t video_mem_size; /* size of the frame buffer */ u16 * palette_cpu; /* virtual address of palette memory */ u_int palette_size; - ssize_t video_offset; u_int lccr0; u_int lccr3; -- cgit v1.2.3 From 7e4b19c95c8632b543bd510ec6c710bebb53b840 Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Wed, 17 Dec 2008 14:56:54 +0800 Subject: [ARM] pxafb: allow pxafb_set_par() to start from arbitrary yoffset Note the var->yres_virtual is only re-calculated from the fix.smem_len when text mode acceleration is enabled (which is default), this is due to the issue as Russell suggested below: Previous experience of doing this with the X server and acornfb is that it causes all sorts of problems - it seems to force the X server into assuming that the framebuffer should be panned no matter what settings you ask it for. The recommended workaround (implemented in acornfb) is to only do these kinds of adjustments if text mode acceleration is enabled. IIRC, the X server should be disabling text mode acceleration when it maps the framebuffer. I seem to remember that there are X servers which forget to do that though. Signed-off-by: Eric Miao Signed-off-by: Eric Miao --- drivers/video/pxafb.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) (limited to 'drivers/video') diff --git a/drivers/video/pxafb.c b/drivers/video/pxafb.c index 25bf4b8b6b53..ab816cadb470 100644 --- a/drivers/video/pxafb.c +++ b/drivers/video/pxafb.c @@ -306,8 +306,6 @@ static void pxafb_setmode(struct fb_var_screeninfo *var, var->lower_margin = mode->lower_margin; var->sync = mode->sync; var->grayscale = mode->cmap_greyscale; - var->xres_virtual = var->xres; - var->yres_virtual = var->yres; } /* @@ -345,10 +343,14 @@ static int pxafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) return -EINVAL; } - var->xres_virtual = - max(var->xres_virtual, var->xres); - var->yres_virtual = - max(var->yres_virtual, var->yres); + /* we don't support xpan, force xres_virtual to be equal to xres */ + var->xres_virtual = var->xres; + + if (var->accel_flags & FB_ACCELF_TEXT) + var->yres_virtual = fbi->fb.fix.smem_len / + (var->xres_virtual * var->bits_per_pixel / 8); + else + var->yres_virtual = max(var->yres_virtual, var->yres); /* * Setup the RGB parameters for this display. @@ -878,7 +880,7 @@ static int pxafb_activate_var(struct fb_var_screeninfo *var, struct pxafb_info *fbi) { u_long flags; - size_t nbytes; + size_t nbytes, offset; #if DEBUG_VAR if (!(fbi->lccr0 & LCCR0_LCDT)) { @@ -939,17 +941,18 @@ static int pxafb_activate_var(struct fb_var_screeninfo *var, fbi->reg_lccr3 |= pxafb_bpp_to_lccr3(var); - nbytes = var->yres * fbi->fb.fix.line_length; + nbytes = fbi->fb.fix.line_length * var->yres; + offset = fbi->fb.fix.line_length * var->yoffset; if ((fbi->lccr0 & LCCR0_SDS) == LCCR0_Dual) { nbytes = nbytes / 2; - setup_frame_dma(fbi, DMA_LOWER, PAL_NONE, nbytes, nbytes); + setup_frame_dma(fbi, DMA_LOWER, PAL_NONE, offset + nbytes, nbytes); } if ((var->bits_per_pixel >= 16) || (fbi->lccr0 & LCCR0_LCDT)) - setup_frame_dma(fbi, DMA_BASE, PAL_NONE, 0, nbytes); + setup_frame_dma(fbi, DMA_BASE, PAL_NONE, offset, nbytes); else - setup_frame_dma(fbi, DMA_BASE, PAL_BASE, 0, nbytes); + setup_frame_dma(fbi, DMA_BASE, PAL_BASE, offset, nbytes); fbi->reg_lccr4 = lcd_readl(fbi, LCCR4) & ~LCCR4_PAL_FOR_MASK; fbi->reg_lccr4 |= (fbi->lccr4 & LCCR4_PAL_FOR_MASK); @@ -1362,7 +1365,7 @@ static struct pxafb_info * __devinit pxafb_init_fbinfo(struct device *dev) fbi->fb.fix.type = FB_TYPE_PACKED_PIXELS; fbi->fb.fix.type_aux = 0; fbi->fb.fix.xpanstep = 0; - fbi->fb.fix.ypanstep = 0; + fbi->fb.fix.ypanstep = 1; fbi->fb.fix.ywrapstep = 0; fbi->fb.fix.accel = FB_ACCEL_NONE; @@ -1370,7 +1373,7 @@ static struct pxafb_info * __devinit pxafb_init_fbinfo(struct device *dev) fbi->fb.var.activate = FB_ACTIVATE_NOW; fbi->fb.var.height = -1; fbi->fb.var.width = -1; - fbi->fb.var.accel_flags = 0; + fbi->fb.var.accel_flags = FB_ACCELF_TEXT; fbi->fb.var.vmode = FB_VMODE_NONINTERLACED; fbi->fb.fbops = &pxafb_ops; -- cgit v1.2.3 From 6e354846e807e037751fdc8faaee8ad492177113 Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Wed, 17 Dec 2008 16:50:43 +0800 Subject: [ARM] pxafb: add support for FBIOPAN_DISPLAY by dma braching dma branching is enabled by extending the current setup_frame_dma() function to allow a 2nd set of frame/palette dma descriptors to be used. As a result, pxafb_dma_buff.dma_desc[], pxafb_dma_buff.pal_desc[] and pxafb_info.fdadr[] are doubled. This allows maximum re-use of the current dma setup code, although the pxafb_info.fdadr[xx] for FBRx register values looks a bit odd. Signed-off-by: Eric Miao Signed-off-by: Eric Miao --- drivers/video/pxafb.c | 61 +++++++++++++++++++++++++++++++++++++-------------- drivers/video/pxafb.h | 9 +++++--- 2 files changed, 51 insertions(+), 19 deletions(-) (limited to 'drivers/video') diff --git a/drivers/video/pxafb.c b/drivers/video/pxafb.c index ab816cadb470..b43907d36d66 100644 --- a/drivers/video/pxafb.c +++ b/drivers/video/pxafb.c @@ -71,6 +71,7 @@ static int pxafb_activate_var(struct fb_var_screeninfo *var, struct pxafb_info *); static void set_ctrlr_state(struct pxafb_info *fbi, u_int state); +static void setup_base_frame(struct pxafb_info *fbi, int branch); static unsigned long video_mem_size = 0; @@ -467,6 +468,24 @@ static int pxafb_set_par(struct fb_info *info) return 0; } +static int pxafb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct pxafb_info *fbi = (struct pxafb_info *)info; + int dma = DMA_MAX + DMA_BASE; + + if (fbi->state != C_ENABLE) + return 0; + + setup_base_frame(fbi, 1); + + if (fbi->lccr0 & LCCR0_SDS) + lcd_writel(fbi, FBR1, fbi->fdadr[dma + 1] | 0x1); + + lcd_writel(fbi, FBR0, fbi->fdadr[dma] | 0x1); + return 0; +} + /* * pxafb_blank(): * Blank the display by setting all palette values to zero. Note, the @@ -506,6 +525,7 @@ static struct fb_ops pxafb_ops = { .owner = THIS_MODULE, .fb_check_var = pxafb_check_var, .fb_set_par = pxafb_set_par, + .fb_pan_display = pxafb_pan_display, .fb_setcolreg = pxafb_setcolreg, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, @@ -597,7 +617,7 @@ static int setup_frame_dma(struct pxafb_info *fbi, int dma, int pal, struct pxafb_dma_descriptor *dma_desc, *pal_desc; unsigned int dma_desc_off, pal_desc_off; - if (dma < 0 || dma >= DMA_MAX) + if (dma < 0 || dma >= DMA_MAX * 2) return -EINVAL; dma_desc = &fbi->dma_buff->dma_desc[dma]; @@ -607,7 +627,7 @@ static int setup_frame_dma(struct pxafb_info *fbi, int dma, int pal, dma_desc->fidr = 0; dma_desc->ldcmd = size; - if (pal < 0 || pal >= PAL_MAX) { + if (pal < 0 || pal >= PAL_MAX * 2) { dma_desc->fdadr = fbi->dma_buff_phys + dma_desc_off; fbi->fdadr[dma] = fbi->dma_buff_phys + dma_desc_off; } else { @@ -633,6 +653,27 @@ static int setup_frame_dma(struct pxafb_info *fbi, int dma, int pal, return 0; } +static void setup_base_frame(struct pxafb_info *fbi, int branch) +{ + struct fb_var_screeninfo *var = &fbi->fb.var; + struct fb_fix_screeninfo *fix = &fbi->fb.fix; + unsigned int nbytes, offset; + int dma, pal, bpp = var->bits_per_pixel; + + dma = DMA_BASE + (branch ? DMA_MAX : 0); + pal = (bpp >= 16) ? PAL_NONE : PAL_BASE + (branch ? PAL_MAX : 0); + + nbytes = fix->line_length * var->yres; + offset = fix->line_length * var->yoffset; + + if (fbi->lccr0 & LCCR0_SDS) { + nbytes = nbytes / 2; + setup_frame_dma(fbi, dma + 1, PAL_NONE, offset + nbytes, nbytes); + } + + setup_frame_dma(fbi, dma, pal, offset, nbytes); +} + #ifdef CONFIG_FB_PXA_SMARTPANEL static int setup_smart_dma(struct pxafb_info *fbi) { @@ -880,7 +921,6 @@ static int pxafb_activate_var(struct fb_var_screeninfo *var, struct pxafb_info *fbi) { u_long flags; - size_t nbytes, offset; #if DEBUG_VAR if (!(fbi->lccr0 & LCCR0_LCDT)) { @@ -935,25 +975,14 @@ static int pxafb_activate_var(struct fb_var_screeninfo *var, #endif setup_parallel_timing(fbi, var); + setup_base_frame(fbi, 0); + fbi->reg_lccr0 = fbi->lccr0 | (LCCR0_LDM | LCCR0_SFM | LCCR0_IUM | LCCR0_EFM | LCCR0_QDM | LCCR0_BM | LCCR0_OUM); fbi->reg_lccr3 |= pxafb_bpp_to_lccr3(var); - nbytes = fbi->fb.fix.line_length * var->yres; - offset = fbi->fb.fix.line_length * var->yoffset; - - if ((fbi->lccr0 & LCCR0_SDS) == LCCR0_Dual) { - nbytes = nbytes / 2; - setup_frame_dma(fbi, DMA_LOWER, PAL_NONE, offset + nbytes, nbytes); - } - - if ((var->bits_per_pixel >= 16) || (fbi->lccr0 & LCCR0_LCDT)) - setup_frame_dma(fbi, DMA_BASE, PAL_NONE, offset, nbytes); - else - setup_frame_dma(fbi, DMA_BASE, PAL_BASE, offset, nbytes); - fbi->reg_lccr4 = lcd_readl(fbi, LCCR4) & ~LCCR4_PAL_FOR_MASK; fbi->reg_lccr4 |= (fbi->lccr4 & LCCR4_PAL_FOR_MASK); local_irq_restore(flags); diff --git a/drivers/video/pxafb.h b/drivers/video/pxafb.h index 0981938682ef..e0f90f4c467d 100644 --- a/drivers/video/pxafb.h +++ b/drivers/video/pxafb.h @@ -54,11 +54,14 @@ enum { #define PALETTE_SIZE (256 * 4) #define CMD_BUFF_SIZE (1024 * 50) +/* NOTE: the palette and frame dma descriptors are doubled to allow + * the 2nd set for branch settings (FBRx) + */ struct pxafb_dma_buff { unsigned char palette[PAL_MAX * PALETTE_SIZE]; uint16_t cmd_buff[CMD_BUFF_SIZE]; - struct pxafb_dma_descriptor pal_desc[PAL_MAX]; - struct pxafb_dma_descriptor dma_desc[DMA_MAX]; + struct pxafb_dma_descriptor pal_desc[PAL_MAX * 2]; + struct pxafb_dma_descriptor dma_desc[DMA_MAX * 2]; }; struct pxafb_info { @@ -71,7 +74,7 @@ struct pxafb_info { struct pxafb_dma_buff *dma_buff; size_t dma_buff_size; dma_addr_t dma_buff_phys; - dma_addr_t fdadr[DMA_MAX]; + dma_addr_t fdadr[DMA_MAX * 2]; void __iomem *video_mem; /* virtual address of frame buffer */ unsigned long video_mem_phys; /* physical address of frame buffer */ -- cgit v1.2.3 From a0427509a76c61984fbba4e206b617c689f419ef Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Thu, 18 Dec 2008 22:10:00 +0800 Subject: [ARM] pxafb: add palette format support for LCCR4_PAL_FOR_3 Add the palette format support for LCCR4_PAL_FOR_3, and fix the issue of LCCR4 being never assigned. Also remove the useless pxafb_set_truecolor(). Signed-off-by: Eric Miao Signed-off-by: Eric Miao --- drivers/video/pxafb.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'drivers/video') diff --git a/drivers/video/pxafb.c b/drivers/video/pxafb.c index b43907d36d66..c57f909cb7a8 100644 --- a/drivers/video/pxafb.c +++ b/drivers/video/pxafb.c @@ -155,6 +155,12 @@ pxafb_setpalettereg(u_int regno, u_int red, u_int green, u_int blue, val |= ((blue >> 8) & 0x000000fc); ((u32 *)(fbi->palette_cpu))[regno] = val; break; + case LCCR4_PAL_FOR_3: + val = ((red << 8) & 0x00ff0000); + val |= ((green >> 0) & 0x0000ff00); + val |= ((blue >> 8) & 0x000000ff); + ((u32 *)(fbi->palette_cpu))[regno] = val; + break; } return 0; @@ -416,11 +422,6 @@ static int pxafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) return 0; } -static inline void pxafb_set_truecolor(u_int is_true_color) -{ - /* do your machine-specific setup if needed */ -} - /* * pxafb_set_par(): * Set the user defined part of the display for the specified console @@ -453,11 +454,6 @@ static int pxafb_set_par(struct fb_info *info) fbi->palette_cpu = (u16 *)&fbi->dma_buff->palette[0]; - /* - * Set (any) board control register to handle new color depth - */ - pxafb_set_truecolor(fbi->fb.fix.visual == FB_VISUAL_TRUECOLOR); - if (fbi->fb.var.bits_per_pixel >= 16) fb_dealloc_cmap(&fbi->fb.cmap); else @@ -727,6 +723,7 @@ int pxafb_smart_flush(struct fb_info *info) lcd_writel(fbi, LCCR1, fbi->reg_lccr1); lcd_writel(fbi, LCCR2, fbi->reg_lccr2); lcd_writel(fbi, LCCR3, fbi->reg_lccr3); + lcd_writel(fbi, LCCR4, fbi->reg_lccr4); lcd_writel(fbi, FDADR0, fbi->fdadr[0]); lcd_writel(fbi, FDADR6, fbi->fdadr[6]); @@ -995,6 +992,7 @@ static int pxafb_activate_var(struct fb_var_screeninfo *var, (lcd_readl(fbi, LCCR1) != fbi->reg_lccr1) || (lcd_readl(fbi, LCCR2) != fbi->reg_lccr2) || (lcd_readl(fbi, LCCR3) != fbi->reg_lccr3) || + (lcd_readl(fbi, LCCR4) != fbi->reg_lccr4) || (lcd_readl(fbi, FDADR0) != fbi->fdadr[0]) || (lcd_readl(fbi, FDADR1) != fbi->fdadr[1])) pxafb_schedule_work(fbi, C_REENABLE); @@ -1041,6 +1039,7 @@ static void pxafb_enable_controller(struct pxafb_info *fbi) return; /* Sequence from 11.7.10 */ + lcd_writel(fbi, LCCR4, fbi->reg_lccr4); lcd_writel(fbi, LCCR3, fbi->reg_lccr3); lcd_writel(fbi, LCCR2, fbi->reg_lccr2); lcd_writel(fbi, LCCR1, fbi->reg_lccr1); @@ -1313,6 +1312,7 @@ static void pxafb_decode_mach_info(struct pxafb_info *fbi, fbi->cmap_inverse = inf->cmap_inverse; fbi->cmap_static = inf->cmap_static; + fbi->lccr4 = inf->lccr4; switch (lcd_conn & LCD_TYPE_MASK) { case LCD_TYPE_MONO_STN: @@ -1337,7 +1337,6 @@ static void pxafb_decode_mach_info(struct pxafb_info *fbi, /* fall back to backward compatibility way */ fbi->lccr0 = inf->lccr0; fbi->lccr3 = inf->lccr3; - fbi->lccr4 = inf->lccr4; goto decode_mode; } -- cgit v1.2.3 From 878f5783199a95cfa91db45a6e34d2f72756fa18 Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Thu, 18 Dec 2008 22:36:26 +0800 Subject: [ARM] pxafb: cleanup of the color format manipulation code 1. introduce var_to_depth() to calculate the color depth including the transparency bit 2. the conversion from 'fb_var_screeninfo' to LCCR3 BPP bits can be re- used by overlays (in OVLxC1), thus an individual pxafb_var_to_bpp() has been separated out. 3. pxafb_setmode() should really set the color bitfields correctly at begining, introduce a pxafb_set_pixfmt() for this 4. allow user apps to specify color formats within fb_var_screeninfo, and checking of this in pxafb_check_var() has been simplified as below: a) pxafb_var_to_bpp() should pass - which means a basically correct bits_per_pixel and color depth setting b) the RGBT bitfields are then forced into supported values by pxafb_set_pixfmt() Signed-off-by: Eric Miao Signed-off-by: Eric Miao --- drivers/video/pxafb.c | 183 +++++++++++++++++++++++++++++--------------------- 1 file changed, 106 insertions(+), 77 deletions(-) (limited to 'drivers/video') diff --git a/drivers/video/pxafb.c b/drivers/video/pxafb.c index c57f909cb7a8..1faf52642f5f 100644 --- a/drivers/video/pxafb.c +++ b/drivers/video/pxafb.c @@ -66,7 +66,7 @@ LCCR0_SFM | LCCR0_LDM | LCCR0_ENB) #define LCCR3_INVALID_CONFIG_MASK (LCCR3_HSP | LCCR3_VSP |\ - LCCR3_PCD | LCCR3_BPP) + LCCR3_PCD | LCCR3_BPP(0xf)) static int pxafb_activate_var(struct fb_var_screeninfo *var, struct pxafb_info *); @@ -221,37 +221,110 @@ pxafb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, return ret; } -/* - * pxafb_bpp_to_lccr3(): - * Convert a bits per pixel value to the correct bit pattern for LCCR3 - */ -static int pxafb_bpp_to_lccr3(struct fb_var_screeninfo *var) +/* calculate pixel depth, transparency bit included, >=16bpp formats _only_ */ +static inline int var_to_depth(struct fb_var_screeninfo *var) { - int ret = 0; + return var->red.length + var->green.length + + var->blue.length + var->transp.length; +} + +/* calculate 4-bit BPP value for LCCR3 and OVLxC1 */ +static int pxafb_var_to_bpp(struct fb_var_screeninfo *var) +{ + int bpp = -EINVAL; + switch (var->bits_per_pixel) { - case 1: ret = LCCR3_1BPP; break; - case 2: ret = LCCR3_2BPP; break; - case 4: ret = LCCR3_4BPP; break; - case 8: ret = LCCR3_8BPP; break; - case 16: ret = LCCR3_16BPP; break; + case 1: bpp = 0; break; + case 2: bpp = 1; break; + case 4: bpp = 2; break; + case 8: bpp = 3; break; + case 16: bpp = 4; break; case 24: - switch (var->red.length + var->green.length + - var->blue.length + var->transp.length) { - case 18: ret = LCCR3_18BPP_P | LCCR3_PDFOR_3; break; - case 19: ret = LCCR3_19BPP_P; break; + switch (var_to_depth(var)) { + case 18: bpp = 6; break; /* 18-bits/pixel packed */ + case 19: bpp = 8; break; /* 19-bits/pixel packed */ + case 24: bpp = 9; break; } break; case 32: - switch (var->red.length + var->green.length + - var->blue.length + var->transp.length) { - case 18: ret = LCCR3_18BPP | LCCR3_PDFOR_3; break; - case 19: ret = LCCR3_19BPP; break; - case 24: ret = LCCR3_24BPP | LCCR3_PDFOR_3; break; - case 25: ret = LCCR3_25BPP; break; + switch (var_to_depth(var)) { + case 18: bpp = 5; break; /* 18-bits/pixel unpacked */ + case 19: bpp = 7; break; /* 19-bits/pixel unpacked */ + case 25: bpp = 10; break; } break; } - return ret; + return bpp; +} + +/* + * pxafb_var_to_lccr3(): + * Convert a bits per pixel value to the correct bit pattern for LCCR3 + * + * NOTE: for PXA27x with overlays support, the LCCR3_PDFOR_x bits have an + * implication of the acutal use of transparency bit, which we handle it + * here separatedly. See PXA27x Developer's Manual, Section <<7.4.6 Pixel + * Formats>> for the valid combination of PDFOR, PAL_FOR for various BPP. + * + * Transparency for palette pixel formats is not supported at the moment. + */ +static uint32_t pxafb_var_to_lccr3(struct fb_var_screeninfo *var) +{ + int bpp = pxafb_var_to_bpp(var); + uint32_t lccr3; + + if (bpp < 0) + return 0; + + lccr3 = LCCR3_BPP(bpp); + + switch (var_to_depth(var)) { + case 16: lccr3 |= var->transp.length ? LCCR3_PDFOR_3 : 0; break; + case 18: lccr3 |= LCCR3_PDFOR_3; break; + case 24: lccr3 |= var->transp.length ? LCCR3_PDFOR_2 : LCCR3_PDFOR_3; + break; + case 19: + case 25: lccr3 |= LCCR3_PDFOR_0; break; + } + return lccr3; +} + +#define SET_PIXFMT(v, r, g, b, t) \ +({ \ + (v)->transp.offset = (t) ? (r) + (g) + (b) : 0; \ + (v)->transp.length = (t) ? (t) : 0; \ + (v)->blue.length = (b); (v)->blue.offset = 0; \ + (v)->green.length = (g); (v)->green.offset = (b); \ + (v)->red.length = (r); (v)->red.offset = (b) + (g); \ +}) + +/* set the RGBT bitfields of fb_var_screeninf according to + * var->bits_per_pixel and given depth + */ +static void pxafb_set_pixfmt(struct fb_var_screeninfo *var, int depth) +{ + if (depth == 0) + depth = var->bits_per_pixel; + + if (var->bits_per_pixel < 16) { + /* indexed pixel formats */ + var->red.offset = 0; var->red.length = 8; + var->green.offset = 0; var->green.length = 8; + var->blue.offset = 0; var->blue.length = 8; + var->transp.offset = 0; var->transp.length = 8; + } + + switch (depth) { + case 16: var->transp.length ? + SET_PIXFMT(var, 5, 5, 5, 1) : /* RGBT555 */ + SET_PIXFMT(var, 5, 6, 5, 0); break; /* RGB565 */ + case 18: SET_PIXFMT(var, 6, 6, 6, 0); break; /* RGB666 */ + case 19: SET_PIXFMT(var, 6, 6, 6, 1); break; /* RGBT666 */ + case 24: var->transp.length ? + SET_PIXFMT(var, 8, 8, 7, 1) : /* RGBT887 */ + SET_PIXFMT(var, 8, 8, 8, 0); break; /* RGB888 */ + case 25: SET_PIXFMT(var, 8, 8, 8, 1); break; /* RGBT888 */ + } } #ifdef CONFIG_CPU_FREQ @@ -313,6 +386,9 @@ static void pxafb_setmode(struct fb_var_screeninfo *var, var->lower_margin = mode->lower_margin; var->sync = mode->sync; var->grayscale = mode->cmap_greyscale; + + /* set the initial RGBA bitfields */ + pxafb_set_pixfmt(var, mode->depth); } /* @@ -328,6 +404,7 @@ static int pxafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { struct pxafb_info *fbi = (struct pxafb_info *)info; struct pxafb_mach_info *inf = fbi->dev->platform_data; + int err; if (var->xres < MIN_XRES) var->xres = MIN_XRES; @@ -359,60 +436,12 @@ static int pxafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) else var->yres_virtual = max(var->yres_virtual, var->yres); - /* - * Setup the RGB parameters for this display. - * - * The pixel packing format is described on page 7-11 of the - * PXA2XX Developer's Manual. - */ - if (var->bits_per_pixel == 16) { - var->red.offset = 11; var->red.length = 5; - var->green.offset = 5; var->green.length = 6; - var->blue.offset = 0; var->blue.length = 5; - var->transp.offset = var->transp.length = 0; - } else if (var->bits_per_pixel > 16) { - struct pxafb_mode_info *mode; + /* do a test conversion to BPP fields to check the color formats */ + err = pxafb_var_to_bpp(var); + if (err < 0) + return err; - mode = pxafb_getmode(inf, var); - if (!mode) - return -EINVAL; - - switch (mode->depth) { - case 18: /* RGB666 */ - var->transp.offset = var->transp.length = 0; - var->red.offset = 12; var->red.length = 6; - var->green.offset = 6; var->green.length = 6; - var->blue.offset = 0; var->blue.length = 6; - break; - case 19: /* RGBT666 */ - var->transp.offset = 18; var->transp.length = 1; - var->red.offset = 12; var->red.length = 6; - var->green.offset = 6; var->green.length = 6; - var->blue.offset = 0; var->blue.length = 6; - break; - case 24: /* RGB888 */ - var->transp.offset = var->transp.length = 0; - var->red.offset = 16; var->red.length = 8; - var->green.offset = 8; var->green.length = 8; - var->blue.offset = 0; var->blue.length = 8; - break; - case 25: /* RGBT888 */ - var->transp.offset = 24; var->transp.length = 1; - var->red.offset = 16; var->red.length = 8; - var->green.offset = 8; var->green.length = 8; - var->blue.offset = 0; var->blue.length = 8; - break; - default: - return -EINVAL; - } - } else { - var->red.offset = var->green.offset = 0; - var->blue.offset = var->transp.offset = 0; - var->red.length = 8; - var->green.length = 8; - var->blue.length = 8; - var->transp.length = 0; - } + pxafb_set_pixfmt(var, var_to_depth(var)); #ifdef CONFIG_CPU_FREQ pr_debug("pxafb: dma period = %d ps\n", @@ -978,7 +1007,7 @@ static int pxafb_activate_var(struct fb_var_screeninfo *var, (LCCR0_LDM | LCCR0_SFM | LCCR0_IUM | LCCR0_EFM | LCCR0_QDM | LCCR0_BM | LCCR0_OUM); - fbi->reg_lccr3 |= pxafb_bpp_to_lccr3(var); + fbi->reg_lccr3 |= pxafb_var_to_lccr3(var); fbi->reg_lccr4 = lcd_readl(fbi, LCCR4) & ~LCCR4_PAL_FOR_MASK; fbi->reg_lccr4 |= (fbi->lccr4 & LCCR4_PAL_FOR_MASK); -- cgit v1.2.3 From 3f16ff608a75c8bf28c8cafed12e076d67a3602a Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Thu, 18 Dec 2008 22:51:54 +0800 Subject: [ARM] pxafb: cleanup of the timing checking code Signed-off-by: Eric Miao Signed-off-by: Eric Miao --- drivers/video/pxafb.c | 108 ++++++++++++++++++++------------------------------ drivers/video/pxafb.h | 6 +++ 2 files changed, 50 insertions(+), 64 deletions(-) (limited to 'drivers/video') diff --git a/drivers/video/pxafb.c b/drivers/video/pxafb.c index 1faf52642f5f..7935706a7565 100644 --- a/drivers/video/pxafb.c +++ b/drivers/video/pxafb.c @@ -391,6 +391,46 @@ static void pxafb_setmode(struct fb_var_screeninfo *var, pxafb_set_pixfmt(var, mode->depth); } +static int pxafb_adjust_timing(struct pxafb_info *fbi, + struct fb_var_screeninfo *var) +{ + int line_length; + + var->xres = max_t(int, var->xres, MIN_XRES); + var->yres = max_t(int, var->yres, MIN_YRES); + + if (!(fbi->lccr0 & LCCR0_LCDT)) { + clamp_val(var->hsync_len, 1, 64); + clamp_val(var->vsync_len, 1, 64); + clamp_val(var->left_margin, 1, 255); + clamp_val(var->right_margin, 1, 255); + clamp_val(var->upper_margin, 1, 255); + clamp_val(var->lower_margin, 1, 255); + } + + /* make sure each line is aligned on word boundary */ + line_length = var->xres * var->bits_per_pixel / 8; + line_length = ALIGN(line_length, 4); + var->xres = line_length * 8 / var->bits_per_pixel; + + /* we don't support xpan, force xres_virtual to be equal to xres */ + var->xres_virtual = var->xres; + + if (var->accel_flags & FB_ACCELF_TEXT) + var->yres_virtual = fbi->fb.fix.smem_len / line_length; + else + var->yres_virtual = max(var->yres_virtual, var->yres); + + /* check for limits */ + if (var->xres > MAX_XRES || var->yres > MAX_YRES) + return -EINVAL; + + if (var->yres > var->yres_virtual) + return -EINVAL; + + return 0; +} + /* * pxafb_check_var(): * Get the video params out of 'var'. If a value doesn't fit, round it up, @@ -406,11 +446,6 @@ static int pxafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) struct pxafb_mach_info *inf = fbi->dev->platform_data; int err; - if (var->xres < MIN_XRES) - var->xres = MIN_XRES; - if (var->yres < MIN_YRES) - var->yres = MIN_YRES; - if (inf->fixed_modes) { struct pxafb_mode_info *mode; @@ -418,24 +453,8 @@ static int pxafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) if (!mode) return -EINVAL; pxafb_setmode(var, mode); - } else { - if (var->xres > inf->modes->xres) - return -EINVAL; - if (var->yres > inf->modes->yres) - return -EINVAL; - if (var->bits_per_pixel > inf->modes->bpp) - return -EINVAL; } - /* we don't support xpan, force xres_virtual to be equal to xres */ - var->xres_virtual = var->xres; - - if (var->accel_flags & FB_ACCELF_TEXT) - var->yres_virtual = fbi->fb.fix.smem_len / - (var->xres_virtual * var->bits_per_pixel / 8); - else - var->yres_virtual = max(var->yres_virtual, var->yres); - /* do a test conversion to BPP fields to check the color formats */ err = pxafb_var_to_bpp(var); if (err < 0) @@ -443,6 +462,10 @@ static int pxafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) pxafb_set_pixfmt(var, var_to_depth(var)); + err = pxafb_adjust_timing(fbi, var); + if (err) + return err; + #ifdef CONFIG_CPU_FREQ pr_debug("pxafb: dma period = %d ps\n", pxafb_display_dma_period(var)); @@ -948,49 +971,6 @@ static int pxafb_activate_var(struct fb_var_screeninfo *var, { u_long flags; -#if DEBUG_VAR - if (!(fbi->lccr0 & LCCR0_LCDT)) { - if (var->xres < 16 || var->xres > 1024) - printk(KERN_ERR "%s: invalid xres %d\n", - fbi->fb.fix.id, var->xres); - switch (var->bits_per_pixel) { - case 1: - case 2: - case 4: - case 8: - case 16: - case 24: - case 32: - break; - default: - printk(KERN_ERR "%s: invalid bit depth %d\n", - fbi->fb.fix.id, var->bits_per_pixel); - break; - } - - if (var->hsync_len < 1 || var->hsync_len > 64) - printk(KERN_ERR "%s: invalid hsync_len %d\n", - fbi->fb.fix.id, var->hsync_len); - if (var->left_margin < 1 || var->left_margin > 255) - printk(KERN_ERR "%s: invalid left_margin %d\n", - fbi->fb.fix.id, var->left_margin); - if (var->right_margin < 1 || var->right_margin > 255) - printk(KERN_ERR "%s: invalid right_margin %d\n", - fbi->fb.fix.id, var->right_margin); - if (var->yres < 1 || var->yres > 1024) - printk(KERN_ERR "%s: invalid yres %d\n", - fbi->fb.fix.id, var->yres); - if (var->vsync_len < 1 || var->vsync_len > 64) - printk(KERN_ERR "%s: invalid vsync_len %d\n", - fbi->fb.fix.id, var->vsync_len); - if (var->upper_margin < 0 || var->upper_margin > 255) - printk(KERN_ERR "%s: invalid upper_margin %d\n", - fbi->fb.fix.id, var->upper_margin); - if (var->lower_margin < 0 || var->lower_margin > 255) - printk(KERN_ERR "%s: invalid lower_margin %d\n", - fbi->fb.fix.id, var->lower_margin); - } -#endif /* Update shadow copy atomically */ local_irq_save(flags); diff --git a/drivers/video/pxafb.h b/drivers/video/pxafb.h index e0f90f4c467d..ae3cbc1ca64f 100644 --- a/drivers/video/pxafb.h +++ b/drivers/video/pxafb.h @@ -145,4 +145,10 @@ struct pxafb_info { #define MIN_XRES 64 #define MIN_YRES 64 +/* maximum X and Y resolutions - note these are limits from the register + * bits length instead of the real ones + */ +#define MAX_XRES 1024 +#define MAX_YRES 1024 + #endif /* __PXAFB_H__ */ -- cgit v1.2.3 From 198fc108ee4c2cd3f08954eae6a819c81c03214b Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Tue, 23 Dec 2008 17:49:43 +0800 Subject: [ARM] pxafb: add support for overlay1 and overlay2 as framebuffer devices PXA27x and later processors support overlay1 and overlay2 on-top of the base framebuffer (although under-neath the base is also possible). They support palette and no-palette RGB formats, as well as YUV formats (only available on overlay2). These overlays have dedicated DMA channels and behave in a similar way as a framebuffer. This heavily simplified and re-structured work is based on the original pxafb_overlay.c (which is pending for mainline merge for a long time). The major problems with this pxafb_overlay.c are (if you are interested in the history): 1. heavily redundant (the control logics for overlay1 and overlay2 are actually identical except for some small operations, which are now abstracted into a 'pxafb_layer_ops' structure) 2. a lot of useless and un-tested code (two workarounds which are now fixed on mature silicons) 3. cursorfb is actually useless, hardware cursor should not be used this way, and the code was actually un-tested for a long time. The code in this patch should be self-explanatory, I tried to add minimum comments. As said, this is basically simplified, there are several things still on the pending list: 1. palette mode is un-supported and un-tested (although re-using the palette code of the base framebuffer is actually very easy now with previous clean-up patches) 2. fb_pan_display for overlay(s) is un-supported 3. the base framebuffer can actually be abstracted by 'pxafb_layer' as well, which will help further re-use of the code and keep a better and consistent structure. (This is the reason I named it 'pxafb_layer' instead of 'pxafb_overlay' or something alike) See Documentation/fb/pxafb.txt for additional usage information. Signed-off-by: Eric Miao Cc: Rodolfo Giometti Signed-off-by: Eric Miao --- drivers/video/Kconfig | 5 + drivers/video/pxafb.c | 364 +++++++++++++++++++++++++++++++++++++++++++++++++- drivers/video/pxafb.h | 45 +++++++ 3 files changed, 407 insertions(+), 7 deletions(-) (limited to 'drivers/video') diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 3f3ce13fef43..486d81ca02a3 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -1817,6 +1817,11 @@ config FB_PXA If unsure, say N. +config FB_PXA_OVERLAY + bool "Support PXA27x/PXA3xx Overlay(s) as framebuffer" + default n + depends on FB_PXA && (PXA27x || PXA3xx) + config FB_PXA_SMARTPANEL bool "PXA Smartpanel LCD support" default n diff --git a/drivers/video/pxafb.c b/drivers/video/pxafb.c index 7935706a7565..3a41ea10e8ec 100644 --- a/drivers/video/pxafb.c +++ b/drivers/video/pxafb.c @@ -20,6 +20,16 @@ * * linux-arm-kernel@lists.arm.linux.org.uk * + * Add support for overlay1 and overlay2 based on pxafb_overlay.c: + * + * Copyright (C) 2004, Intel Corporation + * + * 2003/08/27: + * 2004/03/10: + * 2004/10/28: + * + * Copyright (C) 2006-2008 Marvell International Ltd. + * All Rights Reserved */ #include @@ -72,6 +82,8 @@ static int pxafb_activate_var(struct fb_var_screeninfo *var, struct pxafb_info *); static void set_ctrlr_state(struct pxafb_info *fbi, u_int state); static void setup_base_frame(struct pxafb_info *fbi, int branch); +static int setup_frame_dma(struct pxafb_info *fbi, int dma, int pal, + unsigned long offset, size_t size); static unsigned long video_mem_size = 0; @@ -581,6 +593,330 @@ static struct fb_ops pxafb_ops = { .fb_blank = pxafb_blank, }; +#ifdef CONFIG_FB_PXA_OVERLAY +static void overlay1fb_setup(struct pxafb_layer *ofb) +{ + int size = ofb->fb.fix.line_length * ofb->fb.var.yres_virtual; + unsigned long start = ofb->video_mem_phys; + setup_frame_dma(ofb->fbi, DMA_OV1, PAL_NONE, start, size); +} + +/* Depending on the enable status of overlay1/2, the DMA should be + * updated from FDADRx (when disabled) or FBRx (when enabled). + */ +static void overlay1fb_enable(struct pxafb_layer *ofb) +{ + int enabled = lcd_readl(ofb->fbi, OVL1C1) & OVLxC1_OEN; + uint32_t fdadr1 = ofb->fbi->fdadr[DMA_OV1] | (enabled ? 0x1 : 0); + + lcd_writel(ofb->fbi, enabled ? FBR1 : FDADR1, fdadr1); + lcd_writel(ofb->fbi, OVL1C2, ofb->control[1]); + lcd_writel(ofb->fbi, OVL1C1, ofb->control[0] | OVLxC1_OEN); +} + +static void overlay1fb_disable(struct pxafb_layer *ofb) +{ + uint32_t lccr5 = lcd_readl(ofb->fbi, LCCR5); + + lcd_writel(ofb->fbi, OVL1C1, ofb->control[0] & ~OVLxC1_OEN); + + lcd_writel(ofb->fbi, LCSR1, LCSR1_BS(1)); + lcd_writel(ofb->fbi, LCCR5, lccr5 & ~LCSR1_BS(1)); + lcd_writel(ofb->fbi, FBR1, ofb->fbi->fdadr[DMA_OV1] | 0x3); + + if (wait_for_completion_timeout(&ofb->branch_done, 1 * HZ) == 0) + pr_warning("%s: timeout disabling overlay1\n", __func__); + + lcd_writel(ofb->fbi, LCCR5, lccr5); +} + +static void overlay2fb_setup(struct pxafb_layer *ofb) +{ + int size, div = 1, pfor = NONSTD_TO_PFOR(ofb->fb.var.nonstd); + unsigned long start[3] = { ofb->video_mem_phys, 0, 0 }; + + if (pfor == OVERLAY_FORMAT_RGB || pfor == OVERLAY_FORMAT_YUV444_PACKED) { + size = ofb->fb.fix.line_length * ofb->fb.var.yres_virtual; + setup_frame_dma(ofb->fbi, DMA_OV2_Y, -1, start[0], size); + } else { + size = ofb->fb.var.xres_virtual * ofb->fb.var.yres_virtual; + switch (pfor) { + case OVERLAY_FORMAT_YUV444_PLANAR: div = 1; break; + case OVERLAY_FORMAT_YUV422_PLANAR: div = 2; break; + case OVERLAY_FORMAT_YUV420_PLANAR: div = 4; break; + } + start[1] = start[0] + size; + start[2] = start[1] + size / div; + setup_frame_dma(ofb->fbi, DMA_OV2_Y, -1, start[0], size); + setup_frame_dma(ofb->fbi, DMA_OV2_Cb, -1, start[1], size / div); + setup_frame_dma(ofb->fbi, DMA_OV2_Cr, -1, start[2], size / div); + } +} + +static void overlay2fb_enable(struct pxafb_layer *ofb) +{ + int pfor = NONSTD_TO_PFOR(ofb->fb.var.nonstd); + int enabled = lcd_readl(ofb->fbi, OVL2C1) & OVLxC1_OEN; + uint32_t fdadr2 = ofb->fbi->fdadr[DMA_OV2_Y] | (enabled ? 0x1 : 0); + uint32_t fdadr3 = ofb->fbi->fdadr[DMA_OV2_Cb] | (enabled ? 0x1 : 0); + uint32_t fdadr4 = ofb->fbi->fdadr[DMA_OV2_Cr] | (enabled ? 0x1 : 0); + + if (pfor == OVERLAY_FORMAT_RGB || pfor == OVERLAY_FORMAT_YUV444_PACKED) + lcd_writel(ofb->fbi, enabled ? FBR2 : FDADR2, fdadr2); + else { + lcd_writel(ofb->fbi, enabled ? FBR2 : FDADR2, fdadr2); + lcd_writel(ofb->fbi, enabled ? FBR3 : FDADR3, fdadr3); + lcd_writel(ofb->fbi, enabled ? FBR4 : FDADR4, fdadr4); + } + lcd_writel(ofb->fbi, OVL2C2, ofb->control[1]); + lcd_writel(ofb->fbi, OVL2C1, ofb->control[0] | OVLxC1_OEN); +} + +static void overlay2fb_disable(struct pxafb_layer *ofb) +{ + uint32_t lccr5 = lcd_readl(ofb->fbi, LCCR5); + + lcd_writel(ofb->fbi, OVL2C1, ofb->control[0] & ~OVLxC1_OEN); + + lcd_writel(ofb->fbi, LCSR1, LCSR1_BS(2)); + lcd_writel(ofb->fbi, LCCR5, lccr5 & ~LCSR1_BS(2)); + lcd_writel(ofb->fbi, FBR2, ofb->fbi->fdadr[DMA_OV2_Y] | 0x3); + lcd_writel(ofb->fbi, FBR3, ofb->fbi->fdadr[DMA_OV2_Cb] | 0x3); + lcd_writel(ofb->fbi, FBR4, ofb->fbi->fdadr[DMA_OV2_Cr] | 0x3); + + if (wait_for_completion_timeout(&ofb->branch_done, 1 * HZ) == 0) + pr_warning("%s: timeout disabling overlay2\n", __func__); +} + +static struct pxafb_layer_ops ofb_ops[] = { + [0] = { + .enable = overlay1fb_enable, + .disable = overlay1fb_disable, + .setup = overlay1fb_setup, + }, + [1] = { + .enable = overlay2fb_enable, + .disable = overlay2fb_disable, + .setup = overlay2fb_setup, + }, +}; + +static int overlayfb_open(struct fb_info *info, int user) +{ + struct pxafb_layer *ofb = (struct pxafb_layer *)info; + + /* no support for framebuffer console on overlay */ + if (user == 0) + return -ENODEV; + + /* allow only one user at a time */ + if (atomic_inc_and_test(&ofb->usage)) + return -EBUSY; + + /* unblank the base framebuffer */ + fb_blank(&ofb->fbi->fb, FB_BLANK_UNBLANK); + return 0; +} + +static int overlayfb_release(struct fb_info *info, int user) +{ + struct pxafb_layer *ofb = (struct pxafb_layer*) info; + + atomic_dec(&ofb->usage); + ofb->ops->disable(ofb); + + free_pages_exact(ofb->video_mem, ofb->video_mem_size); + ofb->video_mem = NULL; + ofb->video_mem_size = 0; + return 0; +} + +static int overlayfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct pxafb_layer *ofb = (struct pxafb_layer *)info; + struct fb_var_screeninfo *base_var = &ofb->fbi->fb.var; + int xpos, ypos, pfor, bpp; + + xpos = NONSTD_TO_XPOS(var->nonstd); + ypos = NONSTD_TO_XPOS(var->nonstd); + pfor = NONSTD_TO_PFOR(var->nonstd); + + bpp = pxafb_var_to_bpp(var); + if (bpp < 0) + return -EINVAL; + + /* no support for YUV format on overlay1 */ + if (ofb->id == OVERLAY1 && pfor != 0) + return -EINVAL; + + /* for YUV packed formats, bpp = 'minimum bpp of YUV components' */ + switch (pfor) { + case OVERLAY_FORMAT_RGB: + bpp = pxafb_var_to_bpp(var); + if (bpp < 0) + return -EINVAL; + + pxafb_set_pixfmt(var, var_to_depth(var)); + break; + case OVERLAY_FORMAT_YUV444_PACKED: bpp = 24; break; + case OVERLAY_FORMAT_YUV444_PLANAR: bpp = 8; break; + case OVERLAY_FORMAT_YUV422_PLANAR: bpp = 4; break; + case OVERLAY_FORMAT_YUV420_PLANAR: bpp = 2; break; + default: + return -EINVAL; + } + + /* each line must start at a 32-bit word boundary */ + if ((xpos * bpp) % 32) + return -EINVAL; + + /* xres must align on 32-bit word boundary */ + var->xres = roundup(var->xres * bpp, 32) / bpp; + + if ((xpos + var->xres > base_var->xres) || + (ypos + var->yres > base_var->yres)) + return -EINVAL; + + var->xres_virtual = var->xres; + var->yres_virtual = max(var->yres, var->yres_virtual); + return 0; +} + +static int overlayfb_map_video_memory(struct pxafb_layer *ofb) +{ + struct fb_var_screeninfo *var = &ofb->fb.var; + int pfor = NONSTD_TO_PFOR(var->nonstd); + int size, bpp = 0; + + switch (pfor) { + case OVERLAY_FORMAT_RGB: bpp = var->bits_per_pixel; break; + case OVERLAY_FORMAT_YUV444_PACKED: bpp = 24; break; + case OVERLAY_FORMAT_YUV444_PLANAR: bpp = 24; break; + case OVERLAY_FORMAT_YUV422_PLANAR: bpp = 16; break; + case OVERLAY_FORMAT_YUV420_PLANAR: bpp = 12; break; + } + + ofb->fb.fix.line_length = var->xres_virtual * bpp / 8; + + size = PAGE_ALIGN(ofb->fb.fix.line_length * var->yres_virtual); + + /* don't re-allocate if the original video memory is enough */ + if (ofb->video_mem) { + if (ofb->video_mem_size >= size) + return 0; + + free_pages_exact(ofb->video_mem, ofb->video_mem_size); + } + + ofb->video_mem = alloc_pages_exact(size, GFP_KERNEL | __GFP_ZERO); + if (ofb->video_mem == NULL) + return -ENOMEM; + + ofb->video_mem_phys = virt_to_phys(ofb->video_mem); + ofb->video_mem_size = size; + + ofb->fb.fix.smem_start = ofb->video_mem_phys; + ofb->fb.fix.smem_len = ofb->fb.fix.line_length * var->yres_virtual; + ofb->fb.screen_base = ofb->video_mem; + return 0; +} + +static int overlayfb_set_par(struct fb_info *info) +{ + struct pxafb_layer *ofb = (struct pxafb_layer *)info; + struct fb_var_screeninfo *var = &info->var; + int xpos, ypos, pfor, bpp, ret; + + ret = overlayfb_map_video_memory(ofb); + if (ret) + return ret; + + bpp = pxafb_var_to_bpp(var); + xpos = NONSTD_TO_XPOS(var->nonstd); + ypos = NONSTD_TO_XPOS(var->nonstd); + pfor = NONSTD_TO_PFOR(var->nonstd); + + ofb->control[0] = OVLxC1_PPL(var->xres) | OVLxC1_LPO(var->yres) | + OVLxC1_BPP(bpp); + ofb->control[1] = OVLxC2_XPOS(xpos) | OVLxC2_YPOS(ypos); + + if (ofb->id == OVERLAY2) + ofb->control[1] |= OVL2C2_PFOR(pfor); + + ofb->ops->setup(ofb); + ofb->ops->enable(ofb); + return 0; +} + +static struct fb_ops overlay_fb_ops = { + .owner = THIS_MODULE, + .fb_open = overlayfb_open, + .fb_release = overlayfb_release, + .fb_check_var = overlayfb_check_var, + .fb_set_par = overlayfb_set_par, +}; + +static void __devinit init_pxafb_overlay(struct pxafb_info *fbi, + struct pxafb_layer *ofb, int id) +{ + sprintf(ofb->fb.fix.id, "overlay%d", id + 1); + + ofb->fb.fix.type = FB_TYPE_PACKED_PIXELS; + ofb->fb.fix.xpanstep = 0; + ofb->fb.fix.ypanstep = 1; + + ofb->fb.var.activate = FB_ACTIVATE_NOW; + ofb->fb.var.height = -1; + ofb->fb.var.width = -1; + ofb->fb.var.vmode = FB_VMODE_NONINTERLACED; + + ofb->fb.fbops = &overlay_fb_ops; + ofb->fb.flags = FBINFO_FLAG_DEFAULT; + ofb->fb.node = -1; + ofb->fb.pseudo_palette = NULL; + + ofb->id = id; + ofb->ops = &ofb_ops[id]; + atomic_set(&ofb->usage, 0); + ofb->fbi = fbi; + init_completion(&ofb->branch_done); +} + +static int __devinit pxafb_overlay_init(struct pxafb_info *fbi) +{ + int i, ret; + + for (i = 0; i < 2; i++) { + init_pxafb_overlay(fbi, &fbi->overlay[i], i); + ret = register_framebuffer(&fbi->overlay[i].fb); + if (ret) { + dev_err(fbi->dev, "failed to register overlay %d\n", i); + return ret; + } + } + + /* mask all IU/BS/EOF/SOF interrupts */ + lcd_writel(fbi, LCCR5, ~0); + + /* place overlay(s) on top of base */ + fbi->lccr0 |= LCCR0_OUC; + pr_info("PXA Overlay driver loaded successfully!\n"); + return 0; +} + +static void __devexit pxafb_overlay_exit(struct pxafb_info *fbi) +{ + int i; + + for (i = 0; i < 2; i++) + unregister_framebuffer(&fbi->overlay[i].fb); +} +#else +static inline void pxafb_overlay_init(struct pxafb_info *fbi) {} +static inline void pxafb_overlay_exit(struct pxafb_info *fbi) {} +#endif /* CONFIG_FB_PXA_OVERLAY */ + /* * Calculate the PCD value from the clock rate (in picoseconds). * We take account of the PPCR clock setting. @@ -660,7 +996,7 @@ unsigned long pxafb_get_hsync_time(struct device *dev) EXPORT_SYMBOL(pxafb_get_hsync_time); static int setup_frame_dma(struct pxafb_info *fbi, int dma, int pal, - unsigned int offset, size_t size) + unsigned long start, size_t size) { struct pxafb_dma_descriptor *dma_desc, *pal_desc; unsigned int dma_desc_off, pal_desc_off; @@ -671,7 +1007,7 @@ static int setup_frame_dma(struct pxafb_info *fbi, int dma, int pal, dma_desc = &fbi->dma_buff->dma_desc[dma]; dma_desc_off = offsetof(struct pxafb_dma_buff, dma_desc[dma]); - dma_desc->fsadr = fbi->video_mem_phys + offset; + dma_desc->fsadr = start; dma_desc->fidr = 0; dma_desc->ldcmd = size; @@ -705,14 +1041,14 @@ static void setup_base_frame(struct pxafb_info *fbi, int branch) { struct fb_var_screeninfo *var = &fbi->fb.var; struct fb_fix_screeninfo *fix = &fbi->fb.fix; - unsigned int nbytes, offset; - int dma, pal, bpp = var->bits_per_pixel; + int nbytes, dma, pal, bpp = var->bits_per_pixel; + unsigned long offset; dma = DMA_BASE + (branch ? DMA_MAX : 0); pal = (bpp >= 16) ? PAL_NONE : PAL_BASE + (branch ? PAL_MAX : 0); nbytes = fix->line_length * var->yres; - offset = fix->line_length * var->yoffset; + offset = fix->line_length * var->yoffset + fbi->video_mem_phys; if (fbi->lccr0 & LCCR0_SDS) { nbytes = nbytes / 2; @@ -1090,8 +1426,9 @@ static void pxafb_disable_controller(struct pxafb_info *fbi) static irqreturn_t pxafb_handle_irq(int irq, void *dev_id) { struct pxafb_info *fbi = dev_id; - unsigned int lccr0, lcsr = lcd_readl(fbi, LCSR); + unsigned int lccr0, lcsr, lcsr1; + lcsr = lcd_readl(fbi, LCSR); if (lcsr & LCSR_LDD) { lccr0 = lcd_readl(fbi, LCCR0); lcd_writel(fbi, LCCR0, lccr0 | LCCR0_LDM); @@ -1102,8 +1439,18 @@ static irqreturn_t pxafb_handle_irq(int irq, void *dev_id) if (lcsr & LCSR_CMD_INT) complete(&fbi->command_done); #endif - lcd_writel(fbi, LCSR, lcsr); + +#ifdef CONFIG_FB_PXA_OVERLAY + lcsr1 = lcd_readl(fbi, LCSR1); + if (lcsr1 & LCSR1_BS(1)) + complete(&fbi->overlay[0].branch_done); + + if (lcsr1 & LCSR1_BS(2)) + complete(&fbi->overlay[1].branch_done); + + lcd_writel(fbi, LCSR1, lcsr1); +#endif return IRQ_HANDLED; } @@ -1802,6 +2149,8 @@ static int __devinit pxafb_probe(struct platform_device *dev) goto failed_free_cmap; } + pxafb_overlay_init(fbi); + #ifdef CONFIG_CPU_FREQ fbi->freq_transition.notifier_call = pxafb_freq_transition; fbi->freq_policy.notifier_call = pxafb_freq_policy; @@ -1852,6 +2201,7 @@ static int __devexit pxafb_remove(struct platform_device *dev) info = &fbi->fb; + pxafb_overlay_exit(fbi); unregister_framebuffer(info); pxafb_disable_controller(fbi); diff --git a/drivers/video/pxafb.h b/drivers/video/pxafb.h index ae3cbc1ca64f..2353521c5c8c 100644 --- a/drivers/video/pxafb.h +++ b/drivers/video/pxafb.h @@ -64,6 +64,47 @@ struct pxafb_dma_buff { struct pxafb_dma_descriptor dma_desc[DMA_MAX * 2]; }; +enum { + OVERLAY1, + OVERLAY2, +}; + +enum { + OVERLAY_FORMAT_RGB = 0, + OVERLAY_FORMAT_YUV444_PACKED, + OVERLAY_FORMAT_YUV444_PLANAR, + OVERLAY_FORMAT_YUV422_PLANAR, + OVERLAY_FORMAT_YUV420_PLANAR, +}; + +#define NONSTD_TO_XPOS(x) (((x) >> 0) & 0x3ff) +#define NONSTD_TO_YPOS(x) (((x) >> 10) & 0x3ff) +#define NONSTD_TO_PFOR(x) (((x) >> 20) & 0x7) + +struct pxafb_layer; + +struct pxafb_layer_ops { + void (*enable)(struct pxafb_layer *); + void (*disable)(struct pxafb_layer *); + void (*setup)(struct pxafb_layer *); +}; + +struct pxafb_layer { + struct fb_info fb; + int id; + atomic_t usage; + uint32_t control[2]; + + struct pxafb_layer_ops *ops; + + void __iomem *video_mem; + unsigned long video_mem_phys; + size_t video_mem_size; + struct completion branch_done; + + struct pxafb_info *fbi; +}; + struct pxafb_info { struct fb_info fb; struct device *dev; @@ -114,6 +155,10 @@ struct pxafb_info { struct task_struct *smart_thread; #endif +#ifdef CONFIG_FB_PXA_OVERLAY + struct pxafb_layer overlay[2]; +#endif + #ifdef CONFIG_CPU_FREQ struct notifier_block freq_transition; struct notifier_block freq_policy; -- cgit v1.2.3