/* * linux/drivers/video/mmp/fb/mmpfb.c * Framebuffer driver for Marvell Display controller. * * Copyright (C) 2012 Marvell Technology Group Ltd. * Authors: Zhou Zhu <zzhu3@marvell.com> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. * */ #include <linux/module.h> #include <linux/dma-mapping.h> #include <linux/platform_device.h> #include "mmpfb.h" static int var_to_pixfmt(struct fb_var_screeninfo *var) { /* * Pseudocolor mode? */ if (var->bits_per_pixel == 8) return PIXFMT_PSEUDOCOLOR; /* * Check for YUV422PLANAR. */ if (var->bits_per_pixel == 16 && var->red.length == 8 && var->green.length == 4 && var->blue.length == 4) { if (var->green.offset >= var->blue.offset) return PIXFMT_YUV422P; else return PIXFMT_YVU422P; } /* * Check for YUV420PLANAR. */ if (var->bits_per_pixel == 12 && var->red.length == 8 && var->green.length == 2 && var->blue.length == 2) { if (var->green.offset >= var->blue.offset) return PIXFMT_YUV420P; else return PIXFMT_YVU420P; } /* * Check for YUV422PACK. */ if (var->bits_per_pixel == 16 && var->red.length == 16 && var->green.length == 16 && var->blue.length == 16) { if (var->red.offset == 0) return PIXFMT_YUYV; else if (var->green.offset >= var->blue.offset) return PIXFMT_UYVY; else return PIXFMT_VYUY; } /* * Check for 565/1555. */ if (var->bits_per_pixel == 16 && var->red.length <= 5 && var->green.length <= 6 && var->blue.length <= 5) { if (var->transp.length == 0) { if (var->red.offset >= var->blue.offset) return PIXFMT_RGB565; else return PIXFMT_BGR565; } } /* * Check for 888/A888. */ if (var->bits_per_pixel <= 32 && var->red.length <= 8 && var->green.length <= 8 && var->blue.length <= 8) { if (var->bits_per_pixel == 24 && var->transp.length == 0) { if (var->red.offset >= var->blue.offset) return PIXFMT_RGB888PACK; else return PIXFMT_BGR888PACK; } if (var->bits_per_pixel == 32 && var->transp.offset == 24) { if (var->red.offset >= var->blue.offset) return PIXFMT_RGBA888; else return PIXFMT_BGRA888; } else { if (var->red.offset >= var->blue.offset) return PIXFMT_RGB888UNPACK; else return PIXFMT_BGR888UNPACK; } /* fall through */ } return -EINVAL; } static void pixfmt_to_var(struct fb_var_screeninfo *var, int pix_fmt) { switch (pix_fmt) { case PIXFMT_RGB565: 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 = 0; var->transp.length = 0; break; case PIXFMT_BGR565: var->bits_per_pixel = 16; var->red.offset = 0; var->red.length = 5; var->green.offset = 5; var->green.length = 6; var->blue.offset = 11; var->blue.length = 5; var->transp.offset = 0; var->transp.length = 0; break; case PIXFMT_RGB888UNPACK: var->bits_per_pixel = 32; var->red.offset = 16; var->red.length = 8; var->green.offset = 8; var->green.length = 8; var->blue.offset = 0; var->blue.length = 8; var->transp.offset = 0; var->transp.length = 0; break; case PIXFMT_BGR888UNPACK: var->bits_per_pixel = 32; var->red.offset = 0; var->red.length = 8; var->green.offset = 8; var->green.length = 8; var->blue.offset = 16; var->blue.length = 8; var->transp.offset = 0; var->transp.length = 0; break; case PIXFMT_RGBA888: var->bits_per_pixel = 32; var->red.offset = 16; var->red.length = 8; var->green.offset = 8; var->green.length = 8; var->blue.offset = 0; var->blue.length = 8; var->transp.offset = 24; var->transp.length = 8; break; case PIXFMT_BGRA888: var->bits_per_pixel = 32; var->red.offset = 0; var->red.length = 8; var->green.offset = 8; var->green.length = 8; var->blue.offset = 16; var->blue.length = 8; var->transp.offset = 24; var->transp.length = 8; break; case PIXFMT_RGB888PACK: var->bits_per_pixel = 24; var->red.offset = 16; var->red.length = 8; var->green.offset = 8; var->green.length = 8; var->blue.offset = 0; var->blue.length = 8; var->transp.offset = 0; var->transp.length = 0; break; case PIXFMT_BGR888PACK: var->bits_per_pixel = 24; var->red.offset = 0; var->red.length = 8; var->green.offset = 8; var->green.length = 8; var->blue.offset = 16; var->blue.length = 8; var->transp.offset = 0; var->transp.length = 0; break; case PIXFMT_YUV420P: var->bits_per_pixel = 12; var->red.offset = 4; var->red.length = 8; var->green.offset = 2; var->green.length = 2; var->blue.offset = 0; var->blue.length = 2; var->transp.offset = 0; var->transp.length = 0; break; case PIXFMT_YVU420P: var->bits_per_pixel = 12; var->red.offset = 4; var->red.length = 8; var->green.offset = 0; var->green.length = 2; var->blue.offset = 2; var->blue.length = 2; var->transp.offset = 0; var->transp.length = 0; break; case PIXFMT_YUV422P: var->bits_per_pixel = 16; var->red.offset = 8; var->red.length = 8; var->green.offset = 4; var->green.length = 4; var->blue.offset = 0; var->blue.length = 4; var->transp.offset = 0; var->transp.length = 0; break; case PIXFMT_YVU422P: var->bits_per_pixel = 16; var->red.offset = 8; var->red.length = 8; var->green.offset = 0; var->green.length = 4; var->blue.offset = 4; var->blue.length = 4; var->transp.offset = 0; var->transp.length = 0; break; case PIXFMT_UYVY: var->bits_per_pixel = 16; var->red.offset = 8; var->red.length = 16; var->green.offset = 4; var->green.length = 16; var->blue.offset = 0; var->blue.length = 16; var->transp.offset = 0; var->transp.length = 0; break; case PIXFMT_VYUY: var->bits_per_pixel = 16; var->red.offset = 8; var->red.length = 16; var->green.offset = 0; var->green.length = 16; var->blue.offset = 4; var->blue.length = 16; var->transp.offset = 0; var->transp.length = 0; break; case PIXFMT_YUYV: var->bits_per_pixel = 16; var->red.offset = 0; var->red.length = 16; var->green.offset = 4; var->green.length = 16; var->blue.offset = 8; var->blue.length = 16; var->transp.offset = 0; var->transp.length = 0; break; case PIXFMT_PSEUDOCOLOR: var->bits_per_pixel = 8; 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 = 0; break; } } /* * fb framework has its limitation: * 1. input color/output color is not seprated * 2. fb_videomode not include output color * so for fb usage, we keep a output format which is not changed * then it's added for mmpmode */ static void fbmode_to_mmpmode(struct mmp_mode *mode, struct fb_videomode *videomode, int output_fmt) { u64 div_result = 1000000000000ll; mode->name = videomode->name; mode->refresh = videomode->refresh; mode->xres = videomode->xres; mode->yres = videomode->yres; do_div(div_result, videomode->pixclock); mode->pixclock_freq = (u32)div_result; mode->left_margin = videomode->left_margin; mode->right_margin = videomode->right_margin; mode->upper_margin = videomode->upper_margin; mode->lower_margin = videomode->lower_margin; mode->hsync_len = videomode->hsync_len; mode->vsync_len = videomode->vsync_len; mode->hsync_invert = !!(videomode->sync & FB_SYNC_HOR_HIGH_ACT); mode->vsync_invert = !!(videomode->sync & FB_SYNC_VERT_HIGH_ACT); /* no defined flag in fb, use vmode>>3*/ mode->invert_pixclock = !!(videomode->vmode & 8); mode->pix_fmt_out = output_fmt; } static void mmpmode_to_fbmode(struct fb_videomode *videomode, struct mmp_mode *mode) { u64 div_result = 1000000000000ll; videomode->name = mode->name; videomode->refresh = mode->refresh; videomode->xres = mode->xres; videomode->yres = mode->yres; do_div(div_result, mode->pixclock_freq); videomode->pixclock = (u32)div_result; videomode->left_margin = mode->left_margin; videomode->right_margin = mode->right_margin; videomode->upper_margin = mode->upper_margin; videomode->lower_margin = mode->lower_margin; videomode->hsync_len = mode->hsync_len; videomode->vsync_len = mode->vsync_len; videomode->sync = (mode->hsync_invert ? FB_SYNC_HOR_HIGH_ACT : 0) | (mode->vsync_invert ? FB_SYNC_VERT_HIGH_ACT : 0); videomode->vmode = mode->invert_pixclock ? 8 : 0; } static int mmpfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { struct mmpfb_info *fbi = info->par; if (var->bits_per_pixel == 8) return -EINVAL; /* * Basic geometry sanity checks. */ if (var->xoffset + var->xres > var->xres_virtual) return -EINVAL; if (var->yoffset + var->yres > var->yres_virtual) return -EINVAL; /* * Check size of framebuffer. */ if (var->xres_virtual * var->yres_virtual * (var->bits_per_pixel >> 3) > fbi->fb_size) return -EINVAL; return 0; } static unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) { return ((chan & 0xffff) >> (16 - bf->length)) << bf->offset; } static u32 to_rgb(u16 red, u16 green, u16 blue) { red >>= 8; green >>= 8; blue >>= 8; return (red << 16) | (green << 8) | blue; } static int mmpfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, unsigned int trans, struct fb_info *info) { struct mmpfb_info *fbi = info->par; u32 val; if (info->fix.visual == FB_VISUAL_TRUECOLOR && regno < 16) { val = chan_to_field(red, &info->var.red); val |= chan_to_field(green, &info->var.green); val |= chan_to_field(blue , &info->var.blue); fbi->pseudo_palette[regno] = val; } if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR && regno < 256) { val = to_rgb(red, green, blue); /* TODO */ } return 0; } static int mmpfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { struct mmpfb_info *fbi = info->par; struct mmp_addr addr; memset(&addr, 0, sizeof(addr)); addr.phys[0] = (var->yoffset * var->xres_virtual + var->xoffset) * var->bits_per_pixel / 8 + fbi->fb_start_dma; mmp_overlay_set_addr(fbi->overlay, &addr); return 0; } static int var_update(struct fb_info *info) { struct mmpfb_info *fbi = info->par; struct fb_var_screeninfo *var = &info->var; struct fb_videomode *m; int pix_fmt; /* set pix_fmt */ pix_fmt = var_to_pixfmt(var); if (pix_fmt < 0) return -EINVAL; pixfmt_to_var(var, pix_fmt); fbi->pix_fmt = pix_fmt; /* set var according to best video mode*/ m = (struct fb_videomode *)fb_match_mode(var, &info->modelist); if (!m) { dev_err(fbi->dev, "set par: no match mode, use best mode\n"); m = (struct fb_videomode *)fb_find_best_mode(var, &info->modelist); fb_videomode_to_var(var, m); } memcpy(&fbi->mode, m, sizeof(struct fb_videomode)); /* fix to 2* yres */ var->yres_virtual = var->yres * 2; info->fix.visual = (pix_fmt == PIXFMT_PSEUDOCOLOR) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; info->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8; info->fix.ypanstep = var->yres; return 0; } static int mmpfb_set_par(struct fb_info *info) { struct mmpfb_info *fbi = info->par; struct fb_var_screeninfo *var = &info->var; struct mmp_addr addr; struct mmp_win win; struct mmp_mode mode; int ret; ret = var_update(info); if (ret != 0) return ret; /* set window/path according to new videomode */ fbmode_to_mmpmode(&mode, &fbi->mode, fbi->output_fmt); mmp_path_set_mode(fbi->path, &mode); memset(&win, 0, sizeof(win)); win.xsrc = win.xdst = fbi->mode.xres; win.ysrc = win.ydst = fbi->mode.yres; win.pix_fmt = fbi->pix_fmt; mmp_overlay_set_win(fbi->overlay, &win); /* set address always */ memset(&addr, 0, sizeof(addr)); addr.phys[0] = (var->yoffset * var->xres_virtual + var->xoffset) * var->bits_per_pixel / 8 + fbi->fb_start_dma; mmp_overlay_set_addr(fbi->overlay, &addr); return 0; } static void mmpfb_power(struct mmpfb_info *fbi, int power) { struct mmp_addr addr; struct mmp_win win; struct fb_var_screeninfo *var = &fbi->fb_info->var; /* for power on, always set address/window again */ if (power) { memset(&win, 0, sizeof(win)); win.xsrc = win.xdst = fbi->mode.xres; win.ysrc = win.ydst = fbi->mode.yres; win.pix_fmt = fbi->pix_fmt; mmp_overlay_set_win(fbi->overlay, &win); /* set address always */ memset(&addr, 0, sizeof(addr)); addr.phys[0] = fbi->fb_start_dma + (var->yoffset * var->xres_virtual + var->xoffset) * var->bits_per_pixel / 8; mmp_overlay_set_addr(fbi->overlay, &addr); } mmp_overlay_set_onoff(fbi->overlay, power); } static int mmpfb_blank(int blank, struct fb_info *info) { struct mmpfb_info *fbi = info->par; mmpfb_power(fbi, (blank == FB_BLANK_UNBLANK)); return 0; } static struct fb_ops mmpfb_ops = { .owner = THIS_MODULE, .fb_blank = mmpfb_blank, .fb_check_var = mmpfb_check_var, .fb_set_par = mmpfb_set_par, .fb_setcolreg = mmpfb_setcolreg, .fb_pan_display = mmpfb_pan_display, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, }; static int modes_setup(struct mmpfb_info *fbi) { struct fb_videomode *videomodes; struct mmp_mode *mmp_modes; struct fb_info *info = fbi->fb_info; int videomode_num, i; /* get videomodes from path */ videomode_num = mmp_path_get_modelist(fbi->path, &mmp_modes); if (!videomode_num) { dev_warn(fbi->dev, "can't get videomode num\n"); return 0; } /* put videomode list to info structure */ videomodes = kzalloc(sizeof(struct fb_videomode) * videomode_num, GFP_KERNEL); if (!videomodes) { dev_err(fbi->dev, "can't malloc video modes\n"); return -ENOMEM; } for (i = 0; i < videomode_num; i++) mmpmode_to_fbmode(&videomodes[i], &mmp_modes[i]); fb_videomode_to_modelist(videomodes, videomode_num, &info->modelist); /* set videomode[0] as default mode */ memcpy(&fbi->mode, &videomodes[0], sizeof(struct fb_videomode)); fbi->output_fmt = mmp_modes[0].pix_fmt_out; fb_videomode_to_var(&info->var, &fbi->mode); mmp_path_set_mode(fbi->path, &mmp_modes[0]); kfree(videomodes); return videomode_num; } static int fb_info_setup(struct fb_info *info, struct mmpfb_info *fbi) { int ret = 0; /* Initialise static fb parameters.*/ info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK | FBINFO_HWACCEL_XPAN | FBINFO_HWACCEL_YPAN; info->node = -1; strcpy(info->fix.id, fbi->name); info->fix.type = FB_TYPE_PACKED_PIXELS; info->fix.type_aux = 0; info->fix.xpanstep = 0; info->fix.ypanstep = info->var.yres; info->fix.ywrapstep = 0; info->fix.accel = FB_ACCEL_NONE; info->fix.smem_start = fbi->fb_start_dma; info->fix.smem_len = fbi->fb_size; info->fix.visual = (fbi->pix_fmt == PIXFMT_PSEUDOCOLOR) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; info->fix.line_length = info->var.xres_virtual * info->var.bits_per_pixel / 8; info->fbops = &mmpfb_ops; info->pseudo_palette = fbi->pseudo_palette; info->screen_base = fbi->fb_start; info->screen_size = fbi->fb_size; /* For FB framework: Allocate color map and Register framebuffer*/ if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) ret = -ENOMEM; return ret; } static void fb_info_clear(struct fb_info *info) { fb_dealloc_cmap(&info->cmap); } static int mmpfb_probe(struct platform_device *pdev) { struct mmp_buffer_driver_mach_info *mi; struct fb_info *info = 0; struct mmpfb_info *fbi = 0; int ret, modes_num; mi = pdev->dev.platform_data; if (mi == NULL) { dev_err(&pdev->dev, "no platform data defined\n"); return -EINVAL; } /* initialize fb */ info = framebuffer_alloc(sizeof(struct mmpfb_info), &pdev->dev); if (info == NULL) return -ENOMEM; fbi = info->par; if (!fbi) { ret = -EINVAL; goto failed; } /* init fb */ fbi->fb_info = info; platform_set_drvdata(pdev, fbi); fbi->dev = &pdev->dev; fbi->name = mi->name; fbi->pix_fmt = mi->default_pixfmt; pixfmt_to_var(&info->var, fbi->pix_fmt); mutex_init(&fbi->access_ok); /* get display path by name */ fbi->path = mmp_get_path(mi->path_name); if (!fbi->path) { dev_err(&pdev->dev, "can't get the path %s\n", mi->path_name); ret = -EINVAL; goto failed_destroy_mutex; } dev_info(fbi->dev, "path %s get\n", fbi->path->name); /* get overlay */ fbi->overlay = mmp_path_get_overlay(fbi->path, mi->overlay_id); if (!fbi->overlay) { ret = -EINVAL; goto failed_destroy_mutex; } /* set fetch used */ mmp_overlay_set_fetch(fbi->overlay, mi->dmafetch_id); modes_num = modes_setup(fbi); if (modes_num < 0) { ret = modes_num; goto failed_destroy_mutex; } /* * if get modes success, means not hotplug panels, use caculated buffer * or use default size */ if (modes_num > 0) { /* fix to 2* yres */ info->var.yres_virtual = info->var.yres * 2; /* Allocate framebuffer memory: size = modes xy *4 */ fbi->fb_size = info->var.xres_virtual * info->var.yres_virtual * info->var.bits_per_pixel / 8; } else { fbi->fb_size = MMPFB_DEFAULT_SIZE; } fbi->fb_start = dma_alloc_coherent(&pdev->dev, PAGE_ALIGN(fbi->fb_size), &fbi->fb_start_dma, GFP_KERNEL); if (fbi->fb_start == NULL) { dev_err(&pdev->dev, "can't alloc framebuffer\n"); ret = -ENOMEM; goto failed_destroy_mutex; } memset(fbi->fb_start, 0, fbi->fb_size); dev_info(fbi->dev, "fb %dk allocated\n", fbi->fb_size/1024); /* fb power on */ if (modes_num > 0) mmpfb_power(fbi, 1); ret = fb_info_setup(info, fbi); if (ret < 0) goto failed_free_buff; ret = register_framebuffer(info); if (ret < 0) { dev_err(&pdev->dev, "Failed to register fb: %d\n", ret); ret = -ENXIO; goto failed_clear_info; } dev_info(fbi->dev, "loaded to /dev/fb%d <%s>.\n", info->node, info->fix.id); #ifdef CONFIG_LOGO if (fbi->fb_start) { fb_prepare_logo(info, 0); fb_show_logo(info, 0); } #endif return 0; failed_clear_info: fb_info_clear(info); failed_free_buff: dma_free_coherent(&pdev->dev, PAGE_ALIGN(fbi->fb_size), fbi->fb_start, fbi->fb_start_dma); failed_destroy_mutex: mutex_destroy(&fbi->access_ok); failed: dev_err(fbi->dev, "mmp-fb: frame buffer device init failed\n"); platform_set_drvdata(pdev, NULL); framebuffer_release(info); return ret; } static struct platform_driver mmpfb_driver = { .driver = { .name = "mmp-fb", .owner = THIS_MODULE, }, .probe = mmpfb_probe, }; static int mmpfb_init(void) { return platform_driver_register(&mmpfb_driver); } module_init(mmpfb_init); MODULE_AUTHOR("Zhou Zhu <zhou.zhu@marvell.com>"); MODULE_DESCRIPTION("Framebuffer driver for Marvell displays"); MODULE_LICENSE("GPL");