/* * Copyright 2004-2007 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 #include #include #include #include #include #include #include #include #include #include #include #include #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 = 0; static int fb_enabled = 0; static unsigned long default_bpp = 16; static unsigned char brightness = 255; 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 _set_brightness(unsigned char level); 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 /* * 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_cursor = soft_cursor, .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; unsigned char level; 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 MX2FB_SET_BRIGHTNESS: if (copy_from_user((void *)&level, (void *)arg, sizeof(level))) return -EFAULT; brightness = level; _set_brightness(level); 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; if ((ret = fs453_ioctl(cmd, &mode))) 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); if ((ret != 1) && (ret != 2)) /* specified mode not found */ 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_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); _set_brightness(brightness); 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); _set_brightness(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; unsigned long lgwcr = 0x00400000; /* Graphic window control register */ 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"); } /*! * @brief Update LCDC registers * @param info framebuffer information pointer */ static void _update_lcdc(struct fb_info *info) { unsigned long base; unsigned long perclk3, 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 */ perclk3 = clk_round_rate(lcdc_clk, 134000000); if (clk_set_rate(lcdc_clk, perclk3)) { printk(KERN_INFO "mx2fb: Unable to set clock to %lu\n", perclk3); perclk3 = clk_get_rate(lcdc_clk); } /* Calculate pixel clock divider, and round to the nearest integer */ pcd = (perclk3 * 8 / (PICOS2KHZ(var->pixclock) * 1000UL) + 4) / 8; if (--pcd > 0x3F) pcd = 0x3F; /* Panel configuration register */ pcr = 0xFA008B80 | pcd; pcr |= (var->sync & FB_SYNC_CLK_INVERT) ? 0x01000000 : 0; pcr |= (var->sync & FB_SYNC_SHARP_MODE) ? 0x00000040 : 0; pcr |= (var->sync & FB_SYNC_OE_ACT_HIGH) ? 0 : 0x00100000; pcr |= (var->sync & FB_SYNC_FLM_ACT_LOW) ? 0x00800000 : 0; pcr |= (var->sync & FB_SYNC_LP_ACT_LOW) ? 0x00400000 : 0; pcr |= (var->sync & FB_SYNC_CLK_POS_EDGE) ? 0x00200000 : 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)); /* PWM contrast control register */ _set_brightness(brightness); } /*! * @brief Set LCD brightness * @param level brightness level */ static void _set_brightness(unsigned char level) { /* Set LCDC PWM contract control register */ __raw_writel(0x00A90300 | level, LCDC_REG(LCDC_LPCCR)); } /* * @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(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(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; } /*! * @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; } #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++) { if ((ret = _install_fb(&mx2fb_info[i], pdev))) { 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:' 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; 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); EXPORT_SYMBOL(mx2_gw_set); EXPORT_SYMBOL(mx2fb_register_client); EXPORT_SYMBOL(mx2fb_unregister_client); MODULE_AUTHOR("Freescale Semiconductor, Inc."); MODULE_DESCRIPTION("MX2 framebuffer driver"); MODULE_LICENSE("GPL");