diff options
author | Fancy Fang <chen.fang@nxp.com> | 2016-12-27 09:31:33 +0800 |
---|---|---|
committer | Leonard Crestez <leonard.crestez@nxp.com> | 2018-08-24 12:41:33 +0300 |
commit | 4f074d20d1a838fbbe2e330d8aa936b1ff26410b (patch) | |
tree | fd314cf4d11fbfdbc848296f0ca416ac11f83906 /drivers/video/fbdev/mxsfb.c | |
parent | d12535c786d1f74b558bbddef2c103284f57bbf4 (diff) |
MLK-13684-3 video: mxsfb: add overlay framebuffer support based on lcdif
The enhanced LCDIF controller has added an AS surface which
can be used to blend with the original framebuffer fb0.
Signed-off-by: Fancy Fang <chen.fang@nxp.com>
Diffstat (limited to 'drivers/video/fbdev/mxsfb.c')
-rw-r--r-- | drivers/video/fbdev/mxsfb.c | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/drivers/video/fbdev/mxsfb.c b/drivers/video/fbdev/mxsfb.c index ff8c716a1d35..5dc467b0ff8a 100644 --- a/drivers/video/fbdev/mxsfb.c +++ b/drivers/video/fbdev/mxsfb.c @@ -56,6 +56,7 @@ #include <linux/fb.h> #include <linux/mxcfb.h> #include <linux/regulator/consumer.h> +#include <linux/videodev2.h> #include <video/of_display_timing.h> #include <video/videomode.h> #include <linux/uaccess.h> @@ -89,6 +90,9 @@ #define LCDC_V3_DATA 0x1b0 #define LCDC_V4_DEBUG0 0x1d0 #define LCDC_V3_DEBUG0 0x1f0 +#define LCDC_AS_CTRL 0x210 +#define LCDC_AS_BUF 0x220 +#define LCDC_AS_NEXT_BUF 0x230 #define CTRL_SFTRST (1 << 31) #define CTRL_CLKGATE (1 << 30) @@ -201,6 +205,31 @@ struct mxsfb_devdata { u32 flags; }; +struct mxsfb_layer; + +struct mxsfb_layer_ops { + void (*enable)(struct mxsfb_layer *ofb); + void (*disable)(struct mxsfb_layer *ofb); + void (*setup)(struct mxsfb_layer *ofb); +}; + +struct mxsfb_layer { + struct fb_info *ol_fb; + int id; + int registered; + uint32_t usage; + int blank_state; + + struct mxsfb_layer_ops *ops; + + struct device *dev; + void __iomem *video_mem; + unsigned long video_mem_phys; + size_t video_mem_size; + + struct mxsfb_info *fbi; +}; + struct mxsfb_info { struct fb_info *fb_info; struct platform_device *pdev; @@ -228,6 +257,10 @@ struct mxsfb_info { int id; struct fb_var_screeninfo var; struct pm_qos_request pm_qos_req; + +#ifdef CONFIG_FB_MXC_OVERLAY + struct mxsfb_layer overlay; +#endif }; #define mxsfb_is_v3(host) (host->devdata->ipversion == 3) @@ -360,6 +393,84 @@ static const struct fb_bitfield def_rgb565[] = { } }; +#ifdef CONFIG_FB_MXC_OVERLAY +static const struct fb_bitfield def_argb555[] = { + [RED] = { + .offset = 10, + .length = 5, + }, + [GREEN] = { + .offset = 5, + .length = 5, + }, + [BLUE] = { + .offset = 0, + .length = 5, + }, + [TRANSP] = { + .offset = 15, + .length = 0, + } +}; + +static const struct fb_bitfield def_rgb555[] = { + [RED] = { + .offset = 10, + .length = 5, + }, + [GREEN] = { + .offset = 5, + .length = 5, + }, + [BLUE] = { + .offset = 0, + .length = 5, + }, + [TRANSP] = { + .offset = 0, + .length = 0, + } +}; + +static const struct fb_bitfield def_argb444[] = { + [RED] = { + .offset = 8, + .length = 4, + }, + [GREEN] = { + .offset = 4, + .length = 4, + }, + [BLUE] = { + .offset = 0, + .length = 4, + }, + [TRANSP] = { + .offset = 12, + .length = 4, + } +}; + +static const struct fb_bitfield def_rgb444[] = { + [RED] = { + .offset = 8, + .length = 4, + }, + [GREEN] = { + .offset = 4, + .length = 4, + }, + [BLUE] = { + .offset = 0, + .length = 4, + }, + [TRANSP] = { + .offset = 0, + .length = 0, + } +}; +#endif + static const struct fb_bitfield def_rgb666[] = { [RED] = { .offset = 16, @@ -396,6 +507,25 @@ static const struct fb_bitfield def_rgb888[] = { } }; +static const struct fb_bitfield def_argb32[] = { + [RED] = { + .offset = 16, + .length = 8, + }, + [GREEN] = { + .offset = 8, + .length = 8, + }, + [BLUE] = { + .offset = 0, + .length = 8, + }, + [TRANSP] = { + .offset = 24, + .length = 8, + } +}; + #define bitfield_is_equal(f1, f2) (!memcmp(&(f1), &(f2), sizeof(f1))) static inline bool pixfmt_is_equal(struct fb_var_screeninfo *var, @@ -1398,6 +1528,407 @@ static const struct of_device_id mxsfb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, mxsfb_dt_ids); +#ifdef CONFIG_FB_MXC_OVERLAY +static int overlay_fmt_support(uint32_t fmt) +{ + switch (fmt) { + case V4L2_PIX_FMT_ARGB32: + case V4L2_PIX_FMT_XRGB32: + return 32; + case V4L2_PIX_FMT_ARGB555: + case V4L2_PIX_FMT_ARGB444: + case V4L2_PIX_FMT_XRGB555: + case V4L2_PIX_FMT_RGB444: + case V4L2_PIX_FMT_RGB565: + return 16; + default: + return -EINVAL; + } +} + +/* alpha mode */ +#define ALPHA_CTRL_EMBEDDED 0x0 +#define ALPHA_CTRL_OVERRIDE 0x1 +#define ALPHA_CTRL_MULTIPLY 0x2 +#define ALPHA_CTRL_ROPS 0x3 + +static void overlayfb_enable(struct mxsfb_layer *ofb) +{ + uint32_t as_ctrl = 0x0; + struct mxsfb_info *fbi = ofb->fbi; + + /* enable AS */ + as_ctrl = readl(fbi->base + LCDC_AS_CTRL); + as_ctrl |= 0x1; + + writel(as_ctrl, fbi->base + LCDC_AS_CTRL); +} + +static void overlayfb_disable(struct mxsfb_layer *ofb) +{ + uint32_t as_ctrl = 0x0; + struct mxsfb_info *fbi = ofb->fbi; + + as_ctrl = readl(fbi->base + LCDC_AS_CTRL); + as_ctrl &= 0xfffffffe; + + writel(as_ctrl, fbi->base + LCDC_AS_CTRL); +} + +static void overlayfb_setup(struct mxsfb_layer *ofb) +{ + uint32_t as_next_buf, as_ctrl = 0; + uint8_t format, alpha_ctrl; + struct mxsfb_info *fbi = ofb->fbi; + struct fb_var_screeninfo *var = &ofb->ol_fb->var; + + /* set fb1 framebuffer address */ + as_next_buf = ofb->video_mem_phys; + writel(as_next_buf, fbi->base + LCDC_AS_NEXT_BUF); + + switch (var->grayscale) { + case 0: /* color */ + switch (var->bits_per_pixel) { + case 16: /* RGB565 */ + format = 0xE; + break; + case 32: /* ARGB8888 */ + format = 0x0; + break; + default: + return; + } + case 1: /* grayscale */ + return; + default: + switch (var->grayscale) { + case V4L2_PIX_FMT_ARGB32: + format = 0x0; + break; + case V4L2_PIX_FMT_XRGB32: + format = 0x4; + break; + case V4L2_PIX_FMT_ARGB555: + format = 0x8; + break; + case V4L2_PIX_FMT_ARGB444: + format = 0x9; + break; + case V4L2_PIX_FMT_RGB555: + format = 0xC; + break; + case V4L2_PIX_FMT_RGB444: + format = 0xD; + break; + case V4L2_PIX_FMT_RGB565: + format = 0xE; + break; + default: + return; + } + break; + } + as_ctrl |= ((format & 0xf) << 4); + + /*XXX Only support embbeded alpha mode first */ + alpha_ctrl = ALPHA_CTRL_EMBEDDED; + as_ctrl |= ((alpha_ctrl & 0x3) << 1); + + writel(as_ctrl, fbi->base + LCDC_AS_CTRL); +} + +static struct mxsfb_layer_ops ofb_ops = { + .enable = overlayfb_enable, + .disable = overlayfb_disable, + .setup = overlayfb_setup, +}; + +static int overlayfb_open(struct fb_info *info, int user) +{ + struct mxsfb_layer *ofb = (struct mxsfb_layer*)info->par; + + if (ofb->usage++ == 0) + info->var.activate &= ~FB_ACTIVATE_NXTOPEN; + + return 0; +} + +static int overlayfb_release(struct fb_info *info, int user) +{ + struct mxsfb_layer *ofb = (struct mxsfb_layer*)info->par; + + BUG_ON(!ofb->usage); + + if (--ofb->usage == 0) { + if (ofb->blank_state == FB_BLANK_UNBLANK) + ofb->ops->disable(ofb); + + ofb->blank_state = -1; + info->var.activate = FB_ACTIVATE_NXTOPEN; + } + + return 0; +} + +static void fill_fmt_bitfields(struct fb_var_screeninfo *var, + const struct fb_bitfield *color) +{ + var->red = color[RED]; + var->green = color[GREEN]; + var->blue = color[BLUE]; + var->transp = color[TRANSP]; +} + +static int overlayfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + int bpp; + struct mxsfb_layer *ofb = (struct mxsfb_layer*)info->par; + struct mxsfb_info *fbi = ofb->fbi; + const struct fb_bitfield *rgb = NULL; + + /* overlay width & should be equal to fb0 */ + if ((var->xres != fbi->fb_info->var.xres) || + (var->yres != fbi->fb_info->var.yres)) + return -EINVAL; + + if ((var->xres > 2048) || (var->yres > 2048)) + return -EINVAL; + + if (var->xres_virtual > var->xres) + return -EINVAL; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + switch (var->grayscale) { + case 0: /* color */ + switch (var->bits_per_pixel) { + case 16:/* RGB565 */ + rgb = def_rgb565; + break; + case 32:/* ARGB8888 */ + rgb = def_argb32; + break; + default: + return -EINVAL; + } + break; + case 1: /* grayscale */ + return -EINVAL; + default: /* fourcc */ + if ((bpp = overlay_fmt_support(var->grayscale)) < 0) { + dev_err(info->dev, "unsupport pixel format for overlay\n"); + return -EINVAL; + } + + var->bits_per_pixel = bpp; + if (var->bits_per_pixel < 16) + return -EINVAL; + + switch (var->grayscale) { + case V4L2_PIX_FMT_ARGB32: + rgb = def_argb32; + break; + case V4L2_PIX_FMT_XRGB32: + rgb = def_rgb888; + break; + case V4L2_PIX_FMT_ARGB555: + rgb = def_argb555; + break; + case V4L2_PIX_FMT_ARGB444: + rgb = def_argb444; + break; + case V4L2_PIX_FMT_RGB555: + rgb = def_rgb555; + break; + case V4L2_PIX_FMT_RGB444: + rgb = def_rgb444; + break; + case V4L2_PIX_FMT_RGB565: + rgb = def_rgb565; + break; + } + break; + } + fill_fmt_bitfields(var, rgb); + + return 0; +} + +static int overlayfb_set_par(struct fb_info *info) +{ + int size, bpp; + struct mxsfb_layer *ofb = (struct mxsfb_layer*)info->par; + struct fb_var_screeninfo *var = &ofb->ol_fb->var; + + bpp = var->bits_per_pixel; + ofb->ol_fb->fix.line_length = var->xres_virtual * bpp / 8; + + size = PAGE_ALIGN(ofb->ol_fb->fix.line_length * var->yres_virtual); + if (ofb->video_mem_size < size) + return -EINVAL; + + if (ofb->blank_state == FB_BLANK_UNBLANK) + ofb->ops->disable(ofb); + + ofb->ops->setup(ofb); + + if (ofb->blank_state == FB_BLANK_UNBLANK) + ofb->ops->enable(ofb); + + if ((var->activate & FB_ACTIVATE_FORCE) && + (var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) + var->activate = FB_ACTIVATE_NOW; + + return 0; +} + +static int overlayfb_blank(int blank, struct fb_info *info) +{ + struct mxsfb_layer *ofb = (struct mxsfb_layer*)info->par; + + if (ofb->blank_state == blank) + return 0; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ofb->ops->disable(ofb); + break; + case FB_BLANK_UNBLANK: + ofb->ops->enable(ofb); + break; + } + + ofb->blank_state = blank; + + 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, + .fb_blank = overlayfb_blank, + .fb_mmap = mxsfb_mmap, +}; + +static void init_mxsfb_overlay(struct mxsfb_info *fbi, + struct mxsfb_layer *ofb) +{ + dev_dbg(&fbi->pdev->dev, "AS overlay init\n"); + + ofb->ol_fb->fix.type = FB_TYPE_PACKED_PIXELS; + ofb->ol_fb->fix.xpanstep = 0; + ofb->ol_fb->fix.ypanstep = 1; + ofb->ol_fb->fix.ywrapstep = 1; + ofb->ol_fb->fix.visual = FB_VISUAL_TRUECOLOR; + ofb->ol_fb->fix.accel = FB_ACCEL_NONE; + + ofb->ol_fb->var.activate = FB_ACTIVATE_NXTOPEN; + ofb->ol_fb->var.xres = fbi->fb_info->var.xres; + ofb->ol_fb->var.yres = fbi->fb_info->var.yres; + ofb->ol_fb->var.xres_virtual = fbi->fb_info->var.xres_virtual; + ofb->ol_fb->var.yres_virtual = fbi->fb_info->var.yres; + ofb->ol_fb->var.bits_per_pixel = fbi->fb_info->var.bits_per_pixel; + ofb->ol_fb->var.vmode = FB_VMODE_NONINTERLACED; + ofb->ol_fb->var.nonstd = 0; + + ofb->ol_fb->fbops = &overlay_fb_ops; + ofb->ol_fb->node = -1; + ofb->ol_fb->par = ofb; + + ofb->id = 0; + ofb->ops = &ofb_ops; + ofb->usage = 0; + ofb->blank_state = -1; + ofb->fbi = fbi; +} + +static int mxsfb_overlay_map_video_memory(struct mxsfb_info *fbi, + struct mxsfb_layer *ofb) +{ + struct fb_info *fb = fbi->fb_info; + BUG_ON(!fb->fix.smem_len); + + ofb->video_mem_size = fb->fix.smem_len; + ofb->video_mem = dma_alloc_writecombine(ofb->dev, + ofb->video_mem_size, + (dma_addr_t *)&ofb->video_mem_phys, + GFP_DMA | GFP_KERNEL); + + if (ofb->video_mem == NULL) { + dev_err(ofb->dev, "Unable to allocate overlay fb memory\n"); + return -ENOMEM; + } + + ofb->ol_fb->fix.smem_start = ofb->video_mem_phys; + ofb->ol_fb->fix.smem_len = ofb->video_mem_size; + ofb->ol_fb->screen_base = ofb->video_mem; + + return 0; +} + +static void mxsfb_overlay_init(struct mxsfb_info *fbi) +{ + int ret; + struct mxsfb_layer *ofb = &fbi->overlay; + + ofb->dev = &fbi->pdev->dev; + ofb->ol_fb = framebuffer_alloc(0, ofb->dev); + if (!ofb->ol_fb) { + dev_err(ofb->dev, "Faild to allocate overlay fbinfo\n"); + return; + } + + init_mxsfb_overlay(fbi, ofb); + + ret = register_framebuffer(ofb->ol_fb); + if (ret) { + dev_err(ofb->dev, "failed to register overlay\n"); + goto fb_release; + } + + ret = mxsfb_overlay_map_video_memory(fbi, ofb); + if (ret) { + dev_err(ofb->dev, "failed to map video mem for overlay\n"); + goto fb_unregister; + } + ofb->registered = 1; + + return; + +fb_unregister: + unregister_framebuffer(ofb->ol_fb); +fb_release: + framebuffer_release(ofb->ol_fb); +} + +static void mxsfb_overlay_exit(struct mxsfb_info *fbi) +{ + struct mxsfb_layer *ofb = &fbi->overlay; + + if (ofb->registered) { + if (ofb->video_mem) + dma_free_writecombine(ofb->dev, ofb->video_mem_size, + ofb->video_mem, ofb->video_mem_phys); + + framebuffer_release(ofb->ol_fb); + unregister_framebuffer(ofb->ol_fb); + } +} +#else +static void mxsfb_overlay_init(struct mxsfb_info *fbi) {} +static void mxsfb_overlay_exit(struct mxsfb_info *fbi) {} +#endif + static int mxsfb_probe(struct platform_device *pdev) { const struct of_device_id *of_id = @@ -1535,6 +2066,8 @@ static int mxsfb_probe(struct platform_device *pdev) goto fb_destroy; } + mxsfb_overlay_init(host); + console_lock(); ret = fb_blank(fb_info, FB_BLANK_UNBLANK); console_unlock(); @@ -1577,6 +2110,7 @@ static int mxsfb_remove(struct platform_device *pdev) pm_qos_remove_request(&host->pm_qos_req); pm_runtime_disable(&host->pdev->dev); + mxsfb_overlay_exit(host); unregister_framebuffer(fb_info); mxsfb_free_videomem(host); |