Logo Search packages:      
Sourcecode: linux-fsl-imx51 version File versions  Download package

mx2fb.c

Go to the documentation of this file.
/*
 * Copyright 2004-2009 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 = 0;
static int fb_enabled = 0;
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.
 */
00058 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 */
00142 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.
 */
00158 static struct fb_info mx2fb_info[] = {
      {.par = &mx2fbi_bg},
      {.par = &mx2fbi_gw},
};

/*!
 * @brief This structure contains pointers to the power management
 * callback functions.
 */
00167 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
 */
00181 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.
 *
 */
00210 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.
 */
00327 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.
 */
00374 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.
 */
00432 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
 */
00473 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.
 */
00505 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;
                  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.
 */
00656 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.
 */
00678 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.
 */
00702 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
 */
00750 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.
 */
00771 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
 */
00799 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
 */
00813 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
 */
00861 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
 */
00891 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
 */
00912 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
 */
00939 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
 */
01013 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
 */
01086 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
 */
01120 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
 */
01157 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
 */
01169 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
 */
01204 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
 */
01258 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
 */
01279 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
 */
01299 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
 */
01316 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");

Generated by  Doxygen 1.6.0   Back to index