summaryrefslogtreecommitdiff
path: root/drivers/video/mxc/mx2fb.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/mxc/mx2fb.c')
-rw-r--r--drivers/video/mxc/mx2fb.c1349
1 files changed, 1349 insertions, 0 deletions
diff --git a/drivers/video/mxc/mx2fb.c b/drivers/video/mxc/mx2fb.c
new file mode 100644
index 000000000000..d1daf9d1ea16
--- /dev/null
+++ b/drivers/video/mxc/mx2fb.c
@@ -0,0 +1,1349 @@
+/*
+ * Copyright (C) 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * 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_MX27 Framebuffer Driver for MX27.
+ */
+
+/*!
+ * @file mx2fb.c
+ *
+ * @brief Frame buffer driver for MX27 ADS.
+ *
+ * @ingroup Framebuffer_MX27
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/mxcfb.h>
+#include <linux/uaccess.h>
+#include <mach/hardware.h>
+
+#include "mx2fb.h"
+
+#define MX2FB_TYPE_BG 0
+#define MX2FB_TYPE_GW 1
+
+extern void gpio_lcdc_active(void);
+extern void gpio_lcdc_inactive(void);
+extern void board_power_lcd(int on);
+
+static char *fb_mode;
+static int fb_enabled;
+static unsigned long default_bpp = 16;
+static ATOMIC_NOTIFIER_HEAD(mx2fb_notifier_list);
+static struct clk *lcdc_clk;
+/*!
+ * @brief Structure containing the MX2 specific framebuffer information.
+ */
+struct mx2fb_info {
+ int type;
+ char *id;
+ int registered;
+ int blank;
+ unsigned long pseudo_palette[16];
+};
+
+/* Framebuffer APIs */
+static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info);
+static int mx2fb_set_par(struct fb_info *info);
+static int mx2fb_setcolreg(unsigned regno, unsigned red, unsigned green,
+ unsigned blue, unsigned transp,
+ struct fb_info *info);
+static int mx2fb_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *info);
+static int mx2fb_blank(int blank_mode, struct fb_info *info);
+static int mx2fb_ioctl(struct fb_info *info, unsigned int cmd,
+ unsigned long arg);
+
+/* Driver entries */
+int __init mx2fb_init(void);
+void __exit mx2fb_exit(void);
+#ifndef MODULE
+static int __init mx2fb_setup(char *);
+#endif
+
+/* Internal functions */
+static int __init _init_fbinfo(struct fb_info *info,
+ struct platform_device *pdev);
+static int __init _install_fb(struct fb_info *info,
+ struct platform_device *pdev);
+static void __exit _uninstall_fb(struct fb_info *info);
+static int _map_video_memory(struct fb_info *info);
+static void _unmap_video_memory(struct fb_info *info);
+static void _set_fix(struct fb_info *info);
+static void _enable_lcdc(struct fb_info *info);
+static void _disable_lcdc(struct fb_info *info);
+static void _enable_graphic_window(struct fb_info *info);
+static void _disable_graphic_window(struct fb_info *info);
+static void _update_lcdc(struct fb_info *info);
+static void _request_irq(void);
+static void _free_irq(void);
+
+#ifdef CONFIG_PM
+static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state);
+static int mx2fb_resume(struct platform_device *pdev);
+#else
+#define mx2fb_suspend 0
+#define mx2fb_resume 0
+#endif
+
+static int mx2fb_probe(struct platform_device *pdev);
+
+#ifdef CONFIG_FB_MXC_TVOUT
+#include <linux/video_encoder.h>
+/*
+ * FIXME: VGA mode is not defined by video_encoder.h
+ * while FS453 supports VGA output.
+ */
+#ifndef VIDEO_ENCODER_VGA
+#define VIDEO_ENCODER_VGA 32
+#endif
+
+#define MODE_PAL "TV-PAL"
+#define MODE_NTSC "TV-NTSC"
+#define MODE_VGA "TV-VGA"
+
+extern int fs453_ioctl(unsigned int cmd, void *arg);
+#endif
+
+struct mx2fb_info mx2fbi_bg = {
+ .type = MX2FB_TYPE_BG,
+ .id = "DISP0 BG",
+ .registered = 0,
+};
+
+static struct mx2fb_info mx2fbi_gw = {
+ .type = MX2FB_TYPE_GW,
+ .id = "DISP0 FG",
+ .registered = 0,
+};
+
+/*! Current graphic window information */
+static struct fb_gwinfo g_gwinfo = {
+ .enabled = 0,
+ .alpha_value = 255,
+ .ck_enabled = 0,
+ .ck_red = 0,
+ .ck_green = 0,
+ .ck_blue = 0,
+ .xpos = 0,
+ .ypos = 0,
+};
+
+/*!
+ * @brief Framebuffer information structures.
+ * There are up to 3 framebuffers: background, TVout, and graphic window.
+ * If graphic window is configured, it must be the last framebuffer.
+ */
+static struct fb_info mx2fb_info[] = {
+ {.par = &mx2fbi_bg},
+ {.par = &mx2fbi_gw},
+};
+
+/*!
+ * @brief This structure contains pointers to the power management
+ * callback functions.
+ */
+static struct platform_driver mx2fb_driver = {
+ .driver = {
+ .name = "mxc_sdc_fb",
+ .owner = THIS_MODULE,
+ .bus = &platform_bus_type,
+ },
+ .probe = mx2fb_probe,
+ .suspend = mx2fb_suspend,
+ .resume = mx2fb_resume,
+};
+
+/*!
+ * @brief Framebuffer file operations
+ */
+static struct fb_ops mx2fb_ops = {
+ .owner = THIS_MODULE,
+ .fb_check_var = mx2fb_check_var,
+ .fb_set_par = mx2fb_set_par,
+ .fb_setcolreg = mx2fb_setcolreg,
+ .fb_blank = mx2fb_blank,
+ .fb_pan_display = mx2fb_pan_display,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+ .fb_ioctl = mx2fb_ioctl,
+};
+
+/*!
+ * @brief Validates a var passed in.
+ *
+ * @param var Frame buffer variable screen structure
+ * @param info Frame buffer structure that represents a single frame buffer
+ *
+ * @return Negative errno on error, or zero on success.
+ *
+ * Checks to see if the hardware supports the state requested by var passed
+ * in. This function does not alter the hardware state! If the var passed in
+ * is slightly off by what the hardware can support then we alter the var
+ * PASSED in to what we can do. If the hardware doesn't support mode change
+ * a -EINVAL will be returned by the upper layers.
+ *
+ */
+static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+ unsigned long htotal, vtotal;
+
+ if (var->xres_virtual < var->xres)
+ var->xres_virtual = var->xres;
+ if (var->yres_virtual < var->yres)
+ var->yres_virtual = var->yres;
+
+ if (var->xoffset < 0)
+ var->xoffset = 0;
+
+ if (var->yoffset < 0)
+ var->yoffset = 0;
+
+ if (var->xoffset + info->var.xres > info->var.xres_virtual)
+ var->xoffset = info->var.xres_virtual - info->var.xres;
+
+ if (var->yoffset + info->var.yres > info->var.yres_virtual)
+ var->yoffset = info->var.yres_virtual - info->var.yres;
+
+ if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) &&
+ (var->bits_per_pixel != 16)) {
+ var->bits_per_pixel = default_bpp;
+ }
+
+ switch (var->bits_per_pixel) {
+ 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;
+
+ /* Copy nonstd field to/from sync for fbset usage */
+ var->sync |= var->nonstd;
+ var->nonstd |= var->sync;
+
+ return 0;
+}
+
+/*!
+ * @brief Alters the hardware state.
+ *
+ * @param info Frame buffer structure that represents a single frame buffer
+ *
+ * @return Zero on success others on failure
+ *
+ * Using the fb_var_screeninfo in fb_info we set the resolution of this
+ * particular framebuffer. This function alters the fb_fix_screeninfo stored
+ * in fb_info. It doesn't not alter var in fb_info since we are using that
+ * data. This means we depend on the data in var inside fb_info to be
+ * supported by the hardware. mx2fb_check_var is always called before
+ * mx2fb_set_par to ensure this.
+ */
+static int mx2fb_set_par(struct fb_info *info)
+{
+ unsigned long len;
+ struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par;
+
+ _set_fix(info);
+
+ len = info->var.yres_virtual * info->fix.line_length;
+ if (len > info->fix.smem_len) {
+ if (info->fix.smem_start)
+ _unmap_video_memory(info);
+
+ /* Memory allocation for framebuffer */
+ if (_map_video_memory(info)) {
+ dev_err(info->device, "Unable to allocate fb memory\n");
+ return -ENOMEM;
+ }
+ }
+
+ _update_lcdc(info);
+ if (info->fbops->fb_blank)
+ info->fbops->fb_blank(mx2fbi->blank, info);
+
+ return 0;
+}
+
+/*!
+ * @brief Sets a color register.
+ *
+ * @param regno Which register in the CLUT we are programming
+ * @param red The red value which can be up to 16 bits wide
+ * @param green The green value which can be up to 16 bits wide
+ * @param blue The blue value which can be up to 16 bits wide.
+ * @param transp If supported the alpha value which can be up to
+ * 16 bits wide.
+ * @param info Frame buffer info structure
+ *
+ * @return Negative errno on error, or zero on success.
+ *
+ * Set a single color register. The values supplied have a 16 bit magnitude
+ * which needs to be scaled in this function for the hardware. Things to take
+ * into consideration are how many color registers, if any, are supported with
+ * the current color visual. With truecolor mode no color palettes are
+ * supported. Here a psuedo palette is created which we store the value in
+ * pseudo_palette in struct fb_info. For pseudocolor mode we have a limited
+ * color palette.
+ */
+static int mx2fb_setcolreg(unsigned regno, unsigned red, unsigned green,
+ unsigned blue, unsigned transp, struct fb_info *info)
+{
+ int ret = 1;
+
+ /*
+ * If greyscale is true, then we convert the RGB value
+ * to greyscale no matter what visual we are using.
+ */
+ if (info->var.grayscale)
+ red = green = blue = (19595 * red + 38470 * green +
+ 7471 * blue) >> 16;
+ switch (info->fix.visual) {
+ case FB_VISUAL_TRUECOLOR:
+ /*
+ * 16-bit True Colour. We encode the RGB value
+ * according to the RGB bitfield information.
+ */
+ if (regno < 16) {
+ u32 *pal = info->pseudo_palette;
+ u32 v;
+
+#define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16)
+ red = CNVT_TOHW(red, info->var.red.length);
+ green = CNVT_TOHW(green, info->var.green.length);
+ blue = CNVT_TOHW(blue, info->var.blue.length);
+ transp = CNVT_TOHW(transp, info->var.transp.length);
+#undef CNVT_TOHW
+
+ v = (red << info->var.red.offset) |
+ (green << info->var.green.offset) |
+ (blue << info->var.blue.offset) |
+ (transp << info->var.transp.offset);
+
+ pal[regno] = v;
+ ret = 0;
+ }
+ break;
+ case FB_VISUAL_STATIC_PSEUDOCOLOR:
+ case FB_VISUAL_PSEUDOCOLOR:
+ break;
+ }
+
+ return ret;
+}
+
+/*!
+ * @brief Pans the display.
+ *
+ * @param var Frame buffer variable screen structure
+ * @param info Frame buffer structure that represents a single frame buffer
+ *
+ * @return Negative errno on error, or zero on success.
+ *
+ * Pan (or wrap, depending on the `vmode' field) the display using the
+ * 'xoffset' and 'yoffset' fields of the 'var' structure. If the values
+ * don't fit, return -EINVAL.
+ */
+static int mx2fb_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ if ((info->var.xoffset == var->xoffset) &&
+ (info->var.yoffset == var->yoffset)) {
+ return 0; /* No change, do nothing */
+ }
+
+ if (var->xoffset < 0 || var->yoffset < 0
+ || var->xoffset + info->var.xres > info->var.xres_virtual
+ || var->yoffset + info->var.yres > info->var.yres_virtual)
+ return -EINVAL;
+
+ info->var.xoffset = var->xoffset;
+ info->var.yoffset = var->yoffset;
+
+ _update_lcdc(info);
+
+ if (var->vmode & FB_VMODE_YWRAP)
+ info->var.vmode |= FB_VMODE_YWRAP;
+ else
+ info->var.vmode &= ~FB_VMODE_YWRAP;
+
+ return 0;
+}
+
+/*!
+ * @brief Blanks the display.
+ *
+ * @param blank_mode The blank mode we want.
+ * @param info Frame buffer structure that represents a single frame buffer
+ *
+ * @return Negative errno on error, or zero on success.
+ *
+ * Blank the screen if blank_mode != 0, else unblank. Return 0 if blanking
+ * succeeded, != 0 if un-/blanking failed.
+ * blank_mode == 2: suspend vsync
+ * blank_mode == 3: suspend hsync
+ * blank_mode == 4: powerdown
+ */
+static int mx2fb_blank(int blank_mode, struct fb_info *info)
+{
+ struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par;
+
+ dev_dbg(info->device, "blank mode = %d\n", blank_mode);
+
+ mx2fbi->blank = blank_mode;
+
+ switch (blank_mode) {
+ case FB_BLANK_POWERDOWN:
+ case FB_BLANK_VSYNC_SUSPEND:
+ case FB_BLANK_HSYNC_SUSPEND:
+ case FB_BLANK_NORMAL:
+ _disable_lcdc(info);
+ break;
+ case FB_BLANK_UNBLANK:
+ _enable_lcdc(info);
+ break;
+ }
+
+ return 0;
+}
+
+/*!
+ * @brief Ioctl function to support customized ioctl operations.
+ *
+ * @param info Framebuffer structure that represents a single frame buffer
+ * @param cmd The command number
+ * @param arg Argument which depends on cmd
+ *
+ * @return Negative errno on error, or zero on success.
+ */
+static int mx2fb_ioctl(struct fb_info *info, unsigned int cmd,
+ unsigned long arg)
+{
+ struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par;
+ struct mx2fb_gbl_alpha ga;
+ struct mx2fb_color_key ck;
+
+ switch (cmd) {
+ case MX2FB_SET_GBL_ALPHA:
+ if (mx2fbi->type != MX2FB_TYPE_GW)
+ return -ENODEV;
+
+ if (!arg)
+ return -EINVAL;
+
+ /* set graphic window information */
+ if (copy_from_user((void *)&ga, (void *)arg, sizeof(ga)))
+ return -EFAULT;
+
+ g_gwinfo.alpha_value = ga.alpha;
+
+ if (g_gwinfo.enabled)
+ _enable_graphic_window(info);
+ else
+ _disable_graphic_window(info);
+ break;
+ case MX2FB_SET_CLR_KEY:
+ if (mx2fbi->type != MX2FB_TYPE_GW)
+ return -ENODEV;
+
+ if (!arg)
+ return -EINVAL;
+
+ /* set graphic window information */
+ if (copy_from_user((void *)&ck, (void *)arg, sizeof(ck)))
+ return -EFAULT;
+
+ g_gwinfo.ck_enabled = ck.enable;
+ g_gwinfo.ck_red = (ck.color_key & 0x003F0000) >> 16;
+ g_gwinfo.ck_green = (ck.color_key & 0x00003F00) >> 8;
+ g_gwinfo.ck_blue = ck.color_key & 0x0000003F;
+
+ if (g_gwinfo.enabled)
+ _enable_graphic_window(info);
+ else
+ _disable_graphic_window(info);
+ break;
+ case FBIOGET_GWINFO:
+ if (mx2fbi->type != MX2FB_TYPE_GW)
+ return -ENODEV;
+
+ if (!arg)
+ return -EINVAL;
+
+ /* get graphic window information */
+ if (copy_to_user((void *)arg, (void *)&g_gwinfo,
+ sizeof(g_gwinfo)))
+ return -EFAULT;
+ break;
+ case FBIOPUT_GWINFO:
+ if (mx2fbi->type != MX2FB_TYPE_GW)
+ return -ENODEV;
+
+ if (!arg)
+ return -EINVAL;
+
+ /* set graphic window information */
+ if (copy_from_user((void *)&g_gwinfo, (void *)arg,
+ sizeof(g_gwinfo)))
+ return -EFAULT;
+
+ if (g_gwinfo.enabled)
+ _enable_graphic_window(info);
+ else
+ _disable_graphic_window(info);
+ break;
+#ifdef CONFIG_FB_MXC_TVOUT
+ case ENCODER_GET_CAPABILITIES:{
+ int ret;
+ struct video_encoder_capability cap;
+
+ if (mx2fbi->type != MX2FB_TYPE_BG)
+ return -ENODEV;
+
+ ret = fs453_ioctl(cmd, &cap);
+ if (ret)
+ return ret;
+
+ if (copy_to_user((void *)arg, &cap, sizeof(cap)))
+ return -EFAULT;
+ break;
+ }
+ case ENCODER_SET_NORM:{
+ int ret;
+ unsigned long mode;
+ char *smode;
+ struct fb_var_screeninfo var;
+
+ if (mx2fbi->type != MX2FB_TYPE_BG)
+ return -ENODEV;
+
+ if (copy_from_user(&mode, (void *)arg, sizeof(mode)))
+ return -EFAULT;
+ ret = fs453_ioctl(cmd, &mode);
+ if (ret)
+ return ret;
+
+ if (mode == VIDEO_ENCODER_PAL)
+ smode = MODE_PAL;
+ else if (mode == VIDEO_ENCODER_NTSC)
+ smode = MODE_NTSC;
+ else
+ smode = MODE_VGA;
+
+ var = info->var;
+ var.nonstd = 0;
+ ret = fb_find_mode(&var, info, smode, mxcfb_modedb,
+ mxcfb_modedb_sz, NULL, default_bpp);
+ /* check for specified mode not found */
+ if ((ret != 1) && (ret != 2))
+ return -ENODEV;
+
+ info->var = var;
+ fb_mode = smode;
+ return mx2fb_set_par(info);
+ }
+ case ENCODER_SET_INPUT:
+ case ENCODER_SET_OUTPUT:
+ case ENCODER_ENABLE_OUTPUT:{
+ unsigned long varg;
+
+ if (mx2fbi->type != MX2FB_TYPE_BG)
+ return -ENODEV;
+
+ if (copy_from_user(&varg, (void *)arg, sizeof(varg)))
+ return -EFAULT;
+ return fs453_ioctl(cmd, &varg);
+ }
+#endif
+ default:
+ dev_dbg(info->device, "Unknown ioctl command (0x%08X)\n", cmd);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*!
+ * @brief Set fixed framebuffer parameters based on variable settings.
+ *
+ * @param info framebuffer information pointer
+ * @return Negative errno on error, or zero on success.
+ */
+static void _set_fix(struct fb_info *info)
+{
+ struct fb_fix_screeninfo *fix = &info->fix;
+ struct fb_var_screeninfo *var = &info->var;
+ struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par;
+
+ strncpy(fix->id, mx2fbi->id, strlen(mx2fbi->id));
+ 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 = 1;
+ fix->ypanstep = 1;
+}
+
+/*!
+ * @brief Initialize framebuffer information structure.
+ *
+ * @param info framebuffer information pointer
+ * @param pdev pointer to struct device
+ * @return Negative errno on error, or zero on success.
+ */
+static int __init _init_fbinfo(struct fb_info *info,
+ struct platform_device *pdev)
+{
+ struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par;
+
+ info->device = &pdev->dev;
+ info->var.activate = FB_ACTIVATE_NOW;
+ info->fbops = &mx2fb_ops;
+ info->flags = FBINFO_FLAG_DEFAULT;
+ info->pseudo_palette = &mx2fbi->pseudo_palette;
+
+ /* Allocate colormap */
+ fb_alloc_cmap(&info->cmap, 16, 0);
+
+ return 0;
+}
+
+/*!
+ * @brief Install framebuffer into the system.
+ *
+ * @param info framebuffer information pointer
+ * @param pdev pointer to struct device
+ * @return Negative errno on error, or zero on success.
+ */
+static int __init _install_fb(struct fb_info *info,
+ struct platform_device *pdev)
+{
+ struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par;
+
+ if (_init_fbinfo(info, pdev))
+ return -EINVAL;
+
+ if (fb_mode == 0)
+ fb_mode = pdev->dev.platform_data;
+
+ if (!fb_find_mode(&info->var, info, fb_mode, mxcfb_modedb,
+ mxcfb_modedb_sz, NULL, default_bpp)) {
+ fb_dealloc_cmap(&info->cmap);
+ return -EBUSY;
+ }
+
+ /* Default Y virtual size is 2x panel size */
+ /* info->var.yres_virtual = info->var.yres << 1; */
+
+ if (mx2fbi->type == MX2FB_TYPE_GW)
+ mx2fbi->blank = FB_BLANK_NORMAL;
+ else
+ mx2fbi->blank = FB_BLANK_UNBLANK;
+
+ if (mx2fb_set_par(info)) {
+ fb_dealloc_cmap(&info->cmap);
+ return -EINVAL;
+ }
+
+ if (register_framebuffer(info) < 0) {
+ _unmap_video_memory(info);
+ fb_dealloc_cmap(&info->cmap);
+ return -EINVAL;
+ }
+
+ mx2fbi->registered = 1;
+ dev_info(info->device, "fb%d: %s fb device registered successfully.\n",
+ info->node, info->fix.id);
+
+ return 0;
+}
+
+/*!
+ * @brief Uninstall framebuffer from the system.
+ *
+ * @param info framebuffer information pointer
+ */
+static void __exit _uninstall_fb(struct fb_info *info)
+{
+ struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par;
+
+ if (!mx2fbi->registered)
+ return;
+
+ unregister_framebuffer(info);
+ _unmap_video_memory(info);
+ if (&info->cmap)
+ fb_dealloc_cmap(&info->cmap);
+
+ mx2fbi->registered = 0;
+}
+
+/*!
+ * @brief Allocate memory for framebuffer.
+ *
+ * @param info framebuffer information pointer
+ * @return Negative errno on error, or zero on success.
+ */
+static int _map_video_memory(struct fb_info *info)
+{
+ info->fix.smem_len = info->fix.line_length * info->var.yres_virtual;
+ info->screen_base = dma_alloc_coherent(0,
+ info->fix.smem_len,
+ (dma_addr_t *) &info->fix.smem_start,
+ GFP_DMA | GFP_KERNEL);
+
+ if (info->screen_base == 0) {
+ dev_err(info->device, "Unable to allocate fb memory\n");
+ return -EBUSY;
+ }
+ dev_dbg(info->device, "Allocated fb @ paddr=0x%08lX, size=%d.\n",
+ info->fix.smem_start, info->fix.smem_len);
+
+ info->screen_size = info->fix.smem_len;
+
+ /* Clear the screen */
+ memset((char *)info->screen_base, 0, info->fix.smem_len);
+
+ return 0;
+}
+
+/*!
+ * @brief Release memory for framebuffer.
+ * @param info framebuffer information pointer
+ */
+static void _unmap_video_memory(struct fb_info *info)
+{
+ dma_free_coherent(0, info->fix.smem_len, info->screen_base,
+ (dma_addr_t) info->fix.smem_start);
+
+ info->screen_base = 0;
+ info->fix.smem_start = 0;
+ info->fix.smem_len = 0;
+}
+
+/*!
+ * @brief Enable LCD controller.
+ * @param info framebuffer information pointer
+ */
+static void _enable_lcdc(struct fb_info *info)
+{
+ static int first_enable = 1;
+ struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par;
+
+ /*
+ * Graphic window can only be enabled while the HCLK to the LCDC
+ * is disabled. Once enabled it can subsequently be disabled and
+ * enabled without turning off the HCLK.
+ * The graphic window is enabled and then disabled here. So next
+ * time to enable graphic window the HCLK to LCDC does not need
+ * to be disabled, and the flicker (due to disabling of HCLK to
+ * LCDC) is avoided.
+ */
+ if (first_enable) {
+ _enable_graphic_window(info);
+ _disable_graphic_window(info);
+ first_enable = 0;
+ }
+
+ if (mx2fbi->type == MX2FB_TYPE_GW)
+ _enable_graphic_window(info);
+ else if (!fb_enabled) {
+ clk_enable(lcdc_clk);
+ gpio_lcdc_active();
+ board_power_lcd(1);
+ fb_enabled++;
+#ifdef CONFIG_FB_MXC_TVOUT
+ if (fb_mode) {
+ unsigned long mode = 0;
+
+ if (strcmp(fb_mode, MODE_VGA) == 0)
+ mode = VIDEO_ENCODER_VGA;
+ else if (strcmp(fb_mode, MODE_NTSC) == 0)
+ mode = VIDEO_ENCODER_NTSC;
+ else if (strcmp(fb_mode, MODE_PAL) == 0)
+ mode = VIDEO_ENCODER_PAL;
+ if (mode)
+ fs453_ioctl(ENCODER_SET_NORM, &mode);
+ }
+#endif
+ }
+}
+
+/*!
+ * @brief Disable LCD controller.
+ * @param info framebuffer information pointer
+ */
+static void _disable_lcdc(struct fb_info *info)
+{
+ struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par;
+
+ if (mx2fbi->type == MX2FB_TYPE_GW)
+ _disable_graphic_window(info);
+ else {
+ if (fb_enabled) {
+ gpio_lcdc_inactive();
+ board_power_lcd(0);
+ clk_disable(lcdc_clk);
+ fb_enabled = 0;
+ }
+#ifdef CONFIG_FB_MXC_TVOUT
+ if (fb_mode) {
+ int enable = 0;
+
+ if ((strcmp(fb_mode, MODE_VGA) == 0)
+ || (strcmp(fb_mode, MODE_NTSC) == 0)
+ || (strcmp(fb_mode, MODE_PAL) == 0))
+ fs453_ioctl(ENCODER_ENABLE_OUTPUT, &enable);
+ }
+#endif
+ }
+}
+
+/*!
+ * @brief Enable graphic window.
+ * @param info framebuffer information pointer
+ */
+static void _enable_graphic_window(struct fb_info *info)
+{
+ struct fb_var_screeninfo *var = &info->var;
+
+ g_gwinfo.enabled = 1;
+
+ g_gwinfo.base = (var->yoffset * var->xres_virtual + var->xoffset);
+ g_gwinfo.base *= (var->bits_per_pixel) / 8;
+ g_gwinfo.base += info->fix.smem_start;
+
+ g_gwinfo.xres = var->xres;
+ g_gwinfo.yres = var->yres;
+ g_gwinfo.xres_virtual = var->xres_virtual;
+
+ mx2_gw_set(&g_gwinfo);
+}
+
+/*!
+ * @brief Disable graphic window.
+ * @param info framebuffer information pointer
+ */
+static void _disable_graphic_window(struct fb_info *info)
+{
+ unsigned long i = 0;
+
+ g_gwinfo.enabled = 0;
+
+ /*
+ * Set alpha value to zero and reduce gw size, otherwise the graphic
+ * window will not be able to be enabled again.
+ */
+ __raw_writel(__raw_readl(LCDC_REG(LCDC_LGWCR)) & 0x00FFFFFF,
+ LCDC_REG(LCDC_LGWCR));
+ __raw_writel(((16 >> 4) << 20) + 16, LCDC_REG(LCDC_LGWSR));
+ while (i < 1000)
+ i++;
+
+ /* Now disable graphic window */
+ __raw_writel(__raw_readl(LCDC_REG(LCDC_LGWCR)) & ~0x00400000,
+ LCDC_REG(LCDC_LGWCR));
+
+ dev_dbg(info->device, "Graphic window disabled.\n");
+}
+
+/*!
+ * @brief Setup graphic window properties.
+ * @param gwinfo graphic window information pointer
+ */
+void mx2_gw_set(struct fb_gwinfo *gwinfo)
+{
+ int width, height, xpos, ypos;
+ int width_bg, height_bg;
+ /* Graphic window control register */
+ unsigned long lgwcr = 0x00400000;
+
+ if (!gwinfo->enabled) {
+ _disable_graphic_window(0);
+ return;
+ }
+
+ /* Graphic window start address register */
+ __raw_writel(gwinfo->base, LCDC_REG(LCDC_LGWSAR));
+
+ /*
+ * The graphic window width, height, x position and y position
+ * must be synced up width the background window, otherwise there
+ * may be flickering.
+ */
+ width_bg = (__raw_readl(LCDC_REG(LCDC_LSR)) & 0x03F00000) >> 16;
+ height_bg = __raw_readl(LCDC_REG(LCDC_LSR)) & 0x000003FF;
+
+ width = (gwinfo->xres > width_bg) ? width_bg : gwinfo->xres;
+ height = (gwinfo->yres > height_bg) ? height_bg : gwinfo->yres;
+
+ xpos = gwinfo->xpos;
+ ypos = gwinfo->ypos;
+
+ if (xpos + width > width_bg)
+ xpos = width_bg - width;
+ if (ypos + height > height_bg)
+ ypos = height_bg - height;
+
+ /* Graphic window size register */
+ __raw_writel(((width >> 4) << 20) + height, LCDC_REG(LCDC_LGWSR));
+
+ /* Graphic window virtual page width register */
+ __raw_writel(gwinfo->xres_virtual >> 1, LCDC_REG(LCDC_LGWVPWR));
+
+ /* Graphic window position register */
+ __raw_writel(((xpos & 0x000003FF) << 16) | (ypos & 0x000003FF),
+ LCDC_REG(LCDC_LGWPR));
+
+ /* Graphic window panning offset register */
+ __raw_writel(0, LCDC_REG(LCDC_LGWPOR));
+
+ /* Graphic window DMA control register */
+ if (cpu_is_mx27_rev(CHIP_REV_2_0) > 0)
+ __raw_writel(0x00040060, LCDC_REG(LCDC_LGWDCR));
+ else
+ __raw_writel(0x00020010, LCDC_REG(LCDC_LGWDCR));
+
+ /* Graphic window control register */
+ lgwcr |= (gwinfo->alpha_value & 0x000000FF) << 24;
+ lgwcr |= gwinfo->ck_enabled ? 0x00800000 : 0;
+ lgwcr |= gwinfo->vs_reversed ? 0x00200000 : 0;
+
+ /*
+ * Color keying value
+ * Todo: assume always use RGB565
+ */
+ lgwcr |= (gwinfo->ck_red & 0x0000003F) << 12;
+ lgwcr |= (gwinfo->ck_green & 0x0000003F) << 6;
+ lgwcr |= gwinfo->ck_blue & 0x0000003F;
+
+ __raw_writel(lgwcr, LCDC_REG(LCDC_LGWCR));
+
+ pr_debug("Graphic window enabled.\n");
+}
+EXPORT_SYMBOL(mx2_gw_set);
+
+/*!
+ * @brief Update LCDC registers
+ * @param info framebuffer information pointer
+ */
+static void _update_lcdc(struct fb_info *info)
+{
+ unsigned long base;
+ unsigned long perclk, pcd, pcr;
+ struct fb_var_screeninfo *var = &info->var;
+ struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par;
+
+ if (mx2fbi->type == MX2FB_TYPE_GW) {
+ _enable_graphic_window(info);
+ return;
+ }
+
+ base = (var->yoffset * var->xres_virtual + var->xoffset);
+ base *= (var->bits_per_pixel) / 8;
+ base += info->fix.smem_start;
+
+ /* Screen start address register */
+ __raw_writel(base, LCDC_REG(LCDC_LSSAR));
+
+ /* Size register */
+ dev_dbg(info->device, "xres = %d, yres = %d\n",
+ info->var.xres, info->var.yres);
+ __raw_writel(((info->var.xres >> 4) << 20) + info->var.yres,
+ LCDC_REG(LCDC_LSR));
+
+ /* Virtual page width register */
+ __raw_writel(info->var.xres_virtual >> 1, LCDC_REG(LCDC_LVPWR));
+
+ /* To setup LCDC pixel clock */
+ perclk = clk_round_rate(lcdc_clk, 134000000);
+ if (clk_set_rate(lcdc_clk, perclk)) {
+ printk(KERN_INFO "mx2fb: Unable to set clock to %lu\n", perclk);
+ perclk = clk_get_rate(lcdc_clk);
+ }
+
+ /* Calculate pixel clock divider, and round to the nearest integer */
+ pcd = (perclk * 8 / (PICOS2KHZ(var->pixclock) * 1000UL) + 4) / 8;
+ if (--pcd > 0x3F)
+ pcd = 0x3F;
+
+ /* Panel configuration register */
+ pcr = 0xFA008B80 | pcd;
+ pcr |= (var->sync & FB_SYNC_CLK_LAT_FALL) ? 0x00200000 : 0;
+ pcr |= (var->sync & FB_SYNC_DATA_INVERT) ? 0x01000000 : 0;
+ pcr |= (var->sync & FB_SYNC_SHARP_MODE) ? 0x00000040 : 0;
+ pcr |= (var->sync & FB_SYNC_OE_LOW_ACT) ? 0x00100000 : 0;
+ __raw_writel(pcr, LCDC_REG(LCDC_LPCR));
+
+ /* Horizontal and vertical configuration register */
+ __raw_writel(((var->hsync_len - 1) << 26)
+ + ((var->right_margin - 1) << 8)
+ + (var->left_margin - 3), LCDC_REG(LCDC_LHCR));
+ __raw_writel((var->vsync_len << 26)
+ + (var->lower_margin << 8)
+ + var->upper_margin, LCDC_REG(LCDC_LVCR));
+
+ /* Sharp configuration register */
+ __raw_writel(0x00120300, LCDC_REG(LCDC_LSCR));
+
+ /* Refresh mode control reigster */
+ __raw_writel(0x00000000, LCDC_REG(LCDC_LRMCR));
+
+ /* DMA control register */
+ if (cpu_is_mx27_rev(CHIP_REV_2_0) > 0)
+ __raw_writel(0x00040060, LCDC_REG(LCDC_LDCR));
+ else
+ __raw_writel(0x00020010, LCDC_REG(LCDC_LDCR));
+}
+
+/*!
+ * @brief Set LCD brightness
+ * @param level brightness level
+ */
+void mx2fb_set_brightness(uint8_t level)
+{
+ /* Set LCDC PWM contract control register */
+ __raw_writel(0x00A90300 | level, LCDC_REG(LCDC_LPCCR));
+}
+EXPORT_SYMBOL(mx2fb_set_brightness);
+
+/*
+ * @brief LCDC interrupt handler
+ */
+static irqreturn_t mx2fb_isr(int irq, void *dev_id)
+{
+ struct fb_event event;
+ unsigned long status = __raw_readl(LCDC_REG(LCDC_LISR));
+
+ if (status & MX2FB_INT_EOF) {
+ event.info = &mx2fb_info[0];
+ atomic_notifier_call_chain(&mx2fb_notifier_list,
+ FB_EVENT_MXC_EOF, &event);
+ }
+
+ if (status & MX2FB_INT_GW_EOF) {
+ event.info = &mx2fb_info[1];
+ atomic_notifier_call_chain(&mx2fb_notifier_list,
+ FB_EVENT_MXC_EOF, &event);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*!
+ * @brief Config and request LCDC interrupt
+ */
+static void _request_irq(void)
+{
+ unsigned long status;
+ unsigned long flags;
+
+ /* Read to clear the status */
+ status = __raw_readl(LCDC_REG(LCDC_LISR));
+
+ if (request_irq(MXC_INT_LCDC, mx2fb_isr, 0, "LCDC", 0))
+ pr_info("Request LCDC IRQ failed.\n");
+ else {
+ spin_lock_irqsave(&mx2fb_notifier_list.lock, flags);
+
+ /* Enable interrupt in case client has registered */
+ if (mx2fb_notifier_list.head != NULL) {
+ unsigned long status;
+ unsigned long ints = MX2FB_INT_EOF;
+
+ ints |= MX2FB_INT_GW_EOF;
+
+ /* Read to clear the status */
+ status = __raw_readl(LCDC_REG(LCDC_LISR));
+
+ /* Configure interrupt condition for EOF */
+ __raw_writel(0x0, LCDC_REG(LCDC_LICR));
+
+ /* Enable EOF and graphic window EOF interrupt */
+ __raw_writel(ints, LCDC_REG(LCDC_LIER));
+ }
+
+ spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags);
+ }
+}
+
+/*!
+ * @brief Free LCDC interrupt handler
+ */
+static void _free_irq(void)
+{
+ /* Disable all LCDC interrupt */
+ __raw_writel(0x0, LCDC_REG(LCDC_LIER));
+
+ free_irq(MXC_INT_LCDC, 0);
+}
+
+/*!
+ * @brief Register a client notifier
+ * @param nb notifier block to callback on events
+ */
+int mx2fb_register_client(struct notifier_block *nb)
+{
+ unsigned long flags;
+ int ret;
+
+ ret = atomic_notifier_chain_register(&mx2fb_notifier_list, nb);
+
+ spin_lock_irqsave(&mx2fb_notifier_list.lock, flags);
+
+ /* Enable interrupt in case client has registered */
+ if (mx2fb_notifier_list.head != NULL) {
+ unsigned long status;
+ unsigned long ints = MX2FB_INT_EOF;
+
+ ints |= MX2FB_INT_GW_EOF;
+
+ /* Read to clear the status */
+ status = __raw_readl(LCDC_REG(LCDC_LISR));
+
+ /* Configure interrupt condition for EOF */
+ __raw_writel(0x0, LCDC_REG(LCDC_LICR));
+
+ /* Enable EOF and graphic window EOF interrupt */
+ __raw_writel(ints, LCDC_REG(LCDC_LIER));
+ }
+
+ spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags);
+
+ return ret;
+}
+EXPORT_SYMBOL(mx2fb_register_client);
+
+/*!
+ * @brief Unregister a client notifier
+ * @param nb notifier block to callback on events
+ */
+int mx2fb_unregister_client(struct notifier_block *nb)
+{
+ unsigned long flags;
+ int ret;
+
+ ret = atomic_notifier_chain_unregister(&mx2fb_notifier_list, nb);
+
+ spin_lock_irqsave(&mx2fb_notifier_list.lock, flags);
+
+ /* Mask interrupt in case no client registered */
+ if (mx2fb_notifier_list.head == NULL)
+ __raw_writel(0x0, LCDC_REG(LCDC_LIER));
+
+ spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags);
+
+ return ret;
+}
+EXPORT_SYMBOL(mx2fb_unregister_client);
+
+#ifdef CONFIG_PM
+/*
+ * Power management hooks. Note that we won't be called from IRQ context,
+ * unlike the blank functions above, so we may sleep.
+ */
+
+/*!
+ * @brief Suspends the framebuffer and blanks the screen.
+ * Power management support
+ */
+static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ _disable_lcdc(&mx2fb_info[0]);
+
+ return 0;
+}
+
+/*!
+ * @brief Resumes the framebuffer and unblanks the screen.
+ * Power management support
+ */
+static int mx2fb_resume(struct platform_device *pdev)
+{
+ _enable_lcdc(&mx2fb_info[0]);
+
+ return 0;
+}
+
+#endif /* CONFIG_PM */
+
+/*!
+ * @brief Probe routine for the framebuffer driver. It is called during the
+ * driver binding process.
+ *
+ * @return Appropriate error code to the kernel common code
+ */
+static int mx2fb_probe(struct platform_device *pdev)
+{
+ int ret, i;
+
+ lcdc_clk = clk_get(&pdev->dev, "lcdc_clk");
+
+ for (i = 0; i < sizeof(mx2fb_info) / sizeof(struct fb_info); i++) {
+ ret = _install_fb(&mx2fb_info[i], pdev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to register framebuffer %d\n", i);
+ return ret;
+ }
+ }
+ _request_irq();
+
+ return 0;
+}
+
+/*!
+ * @brief Initialization
+ */
+int __init mx2fb_init(void)
+{
+ /*
+ * For kernel boot options (in 'video=xxxfb:<options>' format)
+ */
+#ifndef MODULE
+ {
+ char *option;
+
+ if (fb_get_options("mxcfb", &option))
+ return -ENODEV;
+ mx2fb_setup(option);
+ }
+#endif
+ return platform_driver_register(&mx2fb_driver);
+}
+
+/*!
+ * @brief Cleanup
+ */
+void __exit mx2fb_exit(void)
+{
+ int i;
+
+ _free_irq();
+ for (i = sizeof(mx2fb_info) / sizeof(struct fb_info); i > 0; i--)
+ _uninstall_fb(&mx2fb_info[i - 1]);
+
+ platform_driver_unregister(&mx2fb_driver);
+}
+
+#ifndef MODULE
+/*!
+ * @brief Setup
+ * Parse user specified options
+ * Example: video=mxcfb:240x320,bpp=16,Sharp-QVGA
+ */
+static int __init mx2fb_setup(char *options)
+{
+ char *opt;
+
+ if (!options || !*options)
+ return 0;
+
+ fb_mode = 0;
+ fb_enabled = 0;
+
+ while ((opt = strsep(&options, ",")) != NULL) {
+ if (!*opt)
+ continue;
+
+ if (!strncmp(opt, "bpp=", 4))
+ default_bpp = simple_strtoul(opt + 4, NULL, 0);
+ else
+ fb_mode = opt;
+ }
+
+ return 0;
+}
+#endif
+
+/* Modularization */
+module_init(mx2fb_init);
+module_exit(mx2fb_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MX2 framebuffer driver");
+MODULE_LICENSE("GPL");