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

mxcfb.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 Framebuffer Driver for SDC and ADC.
 */

/*!
 * @file mxcfb.c
 *
 * @brief MXC Frame buffer driver for SDC
 *
 * @ingroup Framebuffer
 */

/*!
 * Include files
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/fb.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>
#include <linux/console.h>
#include <linux/io.h>
#include <linux/ipu.h>
#include <linux/mxcfb.h>
#include <asm/mach-types.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>

/*
 * Driver name
 */
00054 #define MXCFB_NAME      "mxc_sdc_fb"
/*!
 * Structure containing the MXC specific framebuffer information.
 */
struct mxcfb_info {
      int blank;
      ipu_channel_t ipu_ch;
      uint32_t ipu_ch_irq;
      uint32_t cur_ipu_buf;

      u32 pseudo_palette[16];

      struct semaphore flip_sem;
      spinlock_t fb_lock;
};

struct mxcfb_data {
      struct fb_info *fbi;
      struct fb_info *fbi_ovl;
      volatile int32_t vsync_flag;
      wait_queue_head_t vsync_wq;
      wait_queue_head_t suspend_wq;
      bool suspended;
      int backlight_level;
};

struct mxcfb_alloc_list {
      struct list_head list;
      dma_addr_t phy_addr;
      void *cpu_addr;
      u32 size;
};

static struct mxcfb_data mxcfb_drv_data;

static char *fb_mode = NULL;
static unsigned long default_bpp = 16;
#ifdef CONFIG_FB_MXC_INTERNAL_MEM
static struct clk *iram_clk;
#endif
LIST_HEAD(fb_alloc_list);

static uint32_t bpp_to_pixfmt(int bpp)
{
      uint32_t pixfmt = 0;
      switch (bpp) {
      case 24:
            pixfmt = IPU_PIX_FMT_BGR24;
            break;
      case 32:
            pixfmt = IPU_PIX_FMT_BGR32;
            break;
      case 16:
            pixfmt = IPU_PIX_FMT_RGB565;
            break;
      }
      return pixfmt;
}

extern void gpio_lcd_active(void);
extern void gpio_lcd_inactive(void);
static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id);
static int mxcfb_blank(int blank, struct fb_info *info);
static int mxcfb_map_video_memory(struct fb_info *fbi, bool use_internal_ram);
static int mxcfb_unmap_video_memory(struct fb_info *fbi);

/*
 * Set fixed framebuffer parameters based on variable settings.
 *
 * @param       info     framebuffer information pointer
 */
static int mxcfb_set_fix(struct fb_info *info)
{
      struct fb_fix_screeninfo *fix = &info->fix;
      struct fb_var_screeninfo *var = &info->var;
      struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par;

      if (mxc_fbi->ipu_ch == MEM_SDC_FG)
            strncpy(fix->id, "DISP3 FG", 8);
      else
            strncpy(fix->id, "DISP3 BG", 8);

      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;

      return 0;
}

/*
 * Set framebuffer parameters and change the operating mode.
 *
 * @param       info     framebuffer information pointer
 */
static int mxcfb_set_par(struct fb_info *fbi)
{
      int retval;
      bool use_iram = false;
      u32 mem_len;
      ipu_di_signal_cfg_t sig_cfg;
      ipu_panel_t mode = IPU_PANEL_TFT;
      struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;

      if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq,
                                     (mxcfb_drv_data.suspended ==
                                    false))) < 0) {
            return retval;
      }

      ipu_disable_irq(mxc_fbi->ipu_ch_irq);
      ipu_disable_channel(mxc_fbi->ipu_ch, true);
      ipu_uninit_channel(mxc_fbi->ipu_ch);
      ipu_clear_irq(mxc_fbi->ipu_ch_irq);
      mxcfb_set_fix(fbi);

      mem_len = fbi->var.yres_virtual * fbi->fix.line_length;
      if (mem_len > fbi->fix.smem_len) {
            if (fbi->fix.smem_start)
                  mxcfb_unmap_video_memory(fbi);

#ifdef CONFIG_FB_MXC_INTERNAL_MEM
            if (mxc_fbi->ipu_ch == MEM_SDC_BG) {
                  use_iram = true;
            }
#endif
            if (mxcfb_map_video_memory(fbi, use_iram) < 0)
                  return -ENOMEM;
      }

      ipu_init_channel(mxc_fbi->ipu_ch, NULL);

      /* Clear the screen */
      memset((char *)fbi->screen_base, 0, fbi->fix.smem_len);

      if (mxc_fbi->ipu_ch == MEM_SDC_BG) {
            memset(&sig_cfg, 0, sizeof(sig_cfg));
            if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT)
                  sig_cfg.Hsync_pol = true;
            if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT)
                  sig_cfg.Vsync_pol = true;
            if (!(fbi->var.sync & FB_SYNC_CLK_LAT_FALL))
                  sig_cfg.clk_pol = true;
            if (fbi->var.sync & FB_SYNC_DATA_INVERT)
                  sig_cfg.data_pol = true;
            if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT))
                  sig_cfg.enable_pol = true;
            if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN)
                  sig_cfg.clkidle_en = true;
            if (fbi->var.sync & FB_SYNC_SHARP_MODE)
                  mode = IPU_PANEL_SHARP_TFT;

            dev_dbg(fbi->device, "pixclock = %ul Hz\n",
                  (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL));

            if (ipu_sdc_init_panel(mode,
                               (PICOS2KHZ(fbi->var.pixclock)) * 1000UL,
                               fbi->var.xres, fbi->var.yres,
                               (fbi->var.sync & FB_SYNC_SWAP_RGB) ?
                               IPU_PIX_FMT_BGR666 : IPU_PIX_FMT_RGB666,
                               fbi->var.left_margin,
                               fbi->var.hsync_len,
                               fbi->var.right_margin,
                               fbi->var.upper_margin,
                               fbi->var.vsync_len,
                               fbi->var.lower_margin, sig_cfg) != 0) {
                  dev_err(fbi->device,
                        "mxcfb: Error initializing panel.\n");
                  return -EINVAL;
            }

            fbi->mode =
                (struct fb_videomode *)fb_match_mode(&fbi->var,
                                           &fbi->modelist);
      }

      ipu_disp_set_window_pos(mxc_fbi->ipu_ch, 0, 0);

      mxc_fbi->cur_ipu_buf = 1;
      sema_init(&mxc_fbi->flip_sem, 1);
      fbi->var.xoffset = fbi->var.yoffset = 0;

      retval = ipu_init_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER,
                               bpp_to_pixfmt(fbi->var.bits_per_pixel),
                               fbi->var.xres, fbi->var.yres,
                               fbi->var.xres_virtual,
                               IPU_ROTATE_NONE,
                               fbi->fix.smem_start +
                               (fbi->fix.line_length * fbi->var.yres),
                               fbi->fix.smem_start,
                               0, 0);
      if (retval) {
            dev_err(fbi->device,
                  "ipu_init_channel_buffer error %d\n", retval);
            return retval;
      }

      if (mxc_fbi->blank == FB_BLANK_UNBLANK) {
            ipu_enable_channel(mxc_fbi->ipu_ch);
      }

      return 0;
}

/*
 * Check framebuffer variable parameters and adjust to valid values.
 *
 * @param       var      framebuffer variable parameters
 *
 * @param       info     framebuffer information pointer
 */
static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
      u32 vtotal;
      u32 htotal;

      if (var->xres_virtual < var->xres)
            var->xres_virtual = var->xres;
      if (var->yres_virtual < var->yres)
            var->yres_virtual = var->yres;

#ifdef CONFIG_FB_MXC_INTERNAL_MEM
      if ((info->fix.smem_start == FB_RAM_BASE_ADDR) &&
          ((var->yres_virtual * var->xres_virtual * var->bits_per_pixel / 8) >
           FB_RAM_SIZE)) {
            return -EINVAL;
      }
#endif

      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;

      /* nonstd used for YUV formats, but only RGB supported */
      var->nonstd = 0;

      return 0;
}

static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf)
{
      chan &= 0xffff;
      chan >>= 16 - bf->length;
      return chan << bf->offset;
}
static int
mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
            u_int trans, struct fb_info *fbi)
{
      unsigned int val;
      int ret = 1;

      /*
       * If greyscale is true, then we convert the RGB value
       * to greyscale no matter what visual we are using.
       */
      if (fbi->var.grayscale)
            red = green = blue = (19595 * red + 38470 * green +
                              7471 * blue) >> 16;
      switch (fbi->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 = fbi->pseudo_palette;

                  val = _chan_to_field(red, &fbi->var.red);
                  val |= _chan_to_field(green, &fbi->var.green);
                  val |= _chan_to_field(blue, &fbi->var.blue);

                  pal[regno] = val;
                  ret = 0;
            }
            break;

      case FB_VISUAL_STATIC_PSEUDOCOLOR:
      case FB_VISUAL_PSEUDOCOLOR:
            break;
      }

      return ret;
}

/*
 * Function to handle custom ioctls for MXC framebuffer.
 *
 * @param       inode   inode struct
 *
 * @param       file    file struct
 *
 * @param       cmd     Ioctl command to handle
 *
 * @param       arg     User pointer to command arguments
 *
 * @param       fbi     framebuffer information pointer
 */
static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg)
{
      int retval = 0;
      int __user *argp = (void __user *)arg;

      if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq,
                                     (mxcfb_drv_data.suspended ==
                                    false))) < 0) {
            return retval;
      }

      switch (cmd) {
      case MXCFB_SET_GBL_ALPHA:
            {
                  struct mxcfb_gbl_alpha ga;
                  if (copy_from_user(&ga, (void *)arg, sizeof(ga))) {
                        retval = -EFAULT;
                        break;
                  }
                  retval =
                      ipu_sdc_set_global_alpha((bool) ga.enable,
                                         ga.alpha);
                  dev_dbg(fbi->device, "Set global alpha to %d\n",
                        ga.alpha);
                  break;
            }
      case MXCFB_SET_CLR_KEY:
            {
                  struct mxcfb_color_key key;
                  if (copy_from_user(&key, (void *)arg, sizeof(key))) {
                        retval = -EFAULT;
                        break;
                  }
                  retval = ipu_sdc_set_color_key(MEM_SDC_BG, key.enable,
                                           key.color_key);
                  dev_dbg(fbi->device, "Set color key to 0x%08X\n",
                        key.color_key);
                  break;
            }
      case MXCFB_WAIT_FOR_VSYNC:
            {
#ifndef CONFIG_ARCH_MX3
                  mxcfb_drv_data.vsync_flag = 0;
                  ipu_enable_irq(IPU_IRQ_SDC_DISP3_VSYNC);
                  if (!wait_event_interruptible_timeout
                      (mxcfb_drv_data.vsync_wq,
                       mxcfb_drv_data.vsync_flag != 0, 1 * HZ)) {
                        dev_err(fbi->device,
                              "MXCFB_WAIT_FOR_VSYNC: timeout\n");
                        retval = -ETIME;
                        break;
                  } else if (signal_pending(current)) {
                        dev_err(fbi->device,
                              "MXCFB_WAIT_FOR_VSYNC: interrupt received\n");
                        retval = -ERESTARTSYS;
                        break;
                  }
#endif
                  break;
            }
      case MXCFB_GET_FB_IPU_CHAN:
            {
                  struct mxcfb_info *mxc_fbi =
                        (struct mxcfb_info *)fbi->par;

                  if (put_user(mxc_fbi->ipu_ch, argp))
                        return -EFAULT;

                  break;
            }
      default:
            retval = -EINVAL;
      }
      return retval;
}

/*
 * Function to handle custom ioctls for MXC framebuffer.
 *
 * @param       inode   inode struct
 *
 * @param       file    file struct
 *
 * @param       cmd     Ioctl command to handle
 *
 * @param       arg     User pointer to command arguments
 *
 * @param       fbi     framebuffer information pointer
 */
static int mxcfb_ioctl_ovl(struct fb_info *fbi, unsigned int cmd,
                     unsigned long arg)
{
      int retval = 0;
      int __user *argp = (void __user *)arg;
      struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;

      if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq,
                                     (mxcfb_drv_data.suspended ==
                                    false))) < 0) {
            return retval;
      }

      switch (cmd) {
      case FBIO_ALLOC:
            {
                  int size;
                  struct mxcfb_alloc_list *mem;

                  mem = kzalloc(sizeof(*mem), GFP_KERNEL);
                  if (mem == NULL)
                        return -ENOMEM;

                  if (get_user(size, argp))
                        return -EFAULT;

                  mem->size = PAGE_ALIGN(size);

                  mem->cpu_addr = dma_alloc_coherent(fbi->device, size,
                                             &mem->phy_addr,
                                             GFP_DMA);
                  if (mem->cpu_addr == NULL) {
                        kfree(mem);
                        return -ENOMEM;
                  }

                  list_add(&mem->list, &fb_alloc_list);

                  dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n",
                        mem->size, mem->phy_addr);

                  if (put_user(mem->phy_addr, argp))
                        return -EFAULT;

                  break;
            }
      case FBIO_FREE:
            {
                  unsigned long offset;
                  struct mxcfb_alloc_list *mem;

                  if (get_user(offset, argp))
                        return -EFAULT;

                  retval = -EINVAL;
                  list_for_each_entry(mem, &fb_alloc_list, list) {
                        if (mem->phy_addr == offset) {
                              list_del(&mem->list);
                              dma_free_coherent(fbi->device,
                                            mem->size,
                                            mem->cpu_addr,
                                            mem->phy_addr);
                              kfree(mem);
                              retval = 0;
                              break;
                        }
                  }

                  break;
            }
      case MXCFB_SET_OVERLAY_POS:
            {
                  struct mxcfb_pos pos;
                  if (copy_from_user(&pos, (void *)arg, sizeof(pos))) {
                        retval = -EFAULT;
                        break;
                  }
                  retval = ipu_disp_set_window_pos(mxc_fbi->ipu_ch,
                                          pos.x, pos.y);
                  break;
            }
      case MXCFB_GET_FB_IPU_CHAN:
            {
                  struct mxcfb_info *mxc_fbi =
                        (struct mxcfb_info *)fbi->par;

                  if (put_user(mxc_fbi->ipu_ch, argp))
                        return -EFAULT;

                  break;
            }
      default:
            retval = -EINVAL;
      }
      return retval;
}

/*
 * mxcfb_blank():
 *      Blank the display.
 */
static int mxcfb_blank(int blank, struct fb_info *info)
{
      int retval;
      struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par;

      dev_dbg(info->device, "blank = %d\n", blank);

      if (mxc_fbi->blank == blank)
            return 0;

      if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq,
                                     (mxcfb_drv_data.suspended ==
                                    false))) < 0) {
            return retval;
      }

      mxc_fbi->blank = blank;

      switch (blank) {
      case FB_BLANK_POWERDOWN:
      case FB_BLANK_VSYNC_SUSPEND:
      case FB_BLANK_HSYNC_SUSPEND:
      case FB_BLANK_NORMAL:
            ipu_disable_channel(MEM_SDC_BG, true);
            gpio_lcd_inactive();
            break;
      case FB_BLANK_UNBLANK:
            gpio_lcd_active();
            ipu_enable_channel(MEM_SDC_BG);
            break;
      }
      return 0;
}

/*
 * mxcfb_blank_ovl():
 *      Blank the display.
 */
static int mxcfb_blank_ovl(int blank, struct fb_info *info)
{
      int retval;
      struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par;

      dev_dbg(info->device, "ovl blank = %d\n", blank);

      if (mxc_fbi->blank == blank)
            return 0;

      if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq,
                                     (mxcfb_drv_data.suspended ==
                                    false))) < 0) {
            return retval;
      }

      mxc_fbi->blank = blank;

      switch (blank) {
      case FB_BLANK_POWERDOWN:
      case FB_BLANK_VSYNC_SUSPEND:
      case FB_BLANK_HSYNC_SUSPEND:
      case FB_BLANK_NORMAL:
            ipu_disable_channel(MEM_SDC_FG, true);
            break;
      case FB_BLANK_UNBLANK:
            ipu_enable_channel(MEM_SDC_FG);
            break;
      }
      return 0;
}

/*
 * Pan or Wrap the Display
 *
 * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
 *
 * @param               var     Variable screen buffer information
 * @param               info    Framebuffer information pointer
 */
static int
mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
{
      struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par;
      unsigned long lock_flags = 0;
      int retval;
      u_int y_bottom;
      unsigned long base;

      if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq,
                                     (mxcfb_drv_data.suspended ==
                                    false))) < 0) {
            return retval;
      }

      if (var->xoffset > 0) {
            dev_dbg(info->device, "x panning not supported\n");
            return -EINVAL;
      }

      if ((info->var.xoffset == var->xoffset) &&
          (info->var.yoffset == var->yoffset)) {
            return 0;   // No change, do nothing
      }

      y_bottom = var->yoffset;

      if (!(var->vmode & FB_VMODE_YWRAP)) {
            y_bottom += var->yres;
      }

      if (y_bottom > info->var.yres_virtual) {
            return -EINVAL;
      }

      base = (var->yoffset * var->xres_virtual + var->xoffset);
      base *= (var->bits_per_pixel) / 8;
      base += info->fix.smem_start;

      down(&mxc_fbi->flip_sem);

      spin_lock_irqsave(&mxc_fbi->fb_lock, lock_flags);

      dev_dbg(info->device, "Updating SDC BG buf %d address=0x%08lX\n",
            mxc_fbi->cur_ipu_buf, base);

      mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf;
      if (ipu_update_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER,
                              mxc_fbi->cur_ipu_buf, base) == 0) {
            ipu_select_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER,
                          mxc_fbi->cur_ipu_buf);
            ipu_clear_irq(mxc_fbi->ipu_ch_irq);
            ipu_enable_irq(mxc_fbi->ipu_ch_irq);
      } else {
            dev_err(info->device,
                  "Error updating SDC buf %d to address=0x%08lX\n",
                  mxc_fbi->cur_ipu_buf, base);
      }

      spin_unlock_irqrestore(&mxc_fbi->fb_lock, lock_flags);

      dev_dbg(info->device, "Update complete\n");

      info->var.xoffset = var->xoffset;
      info->var.yoffset = var->yoffset;

      if (var->vmode & FB_VMODE_YWRAP) {
            info->var.vmode |= FB_VMODE_YWRAP;
      } else {
            info->var.vmode &= ~FB_VMODE_YWRAP;
      }

      return 0;
}

/*
 * Function to handle custom mmap for MXC framebuffer.
 *
 * @param       fbi     framebuffer information pointer
 *
 * @param       vma     Pointer to vm_area_struct
 */
static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma)
{
      bool found = false;
      u32 len;
      unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
      struct mxcfb_alloc_list *mem;

      if (offset < fbi->fix.smem_len) {
            /* mapping framebuffer memory */
            len = fbi->fix.smem_len - offset;
            vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT;
      } else {
            list_for_each_entry(mem, &fb_alloc_list, list) {
                  if (offset == mem->phy_addr) {
                        found = true;
                        len = mem->size;
                        break;
                  }
            }
            if (!found) {
                  return -EINVAL;
            }
      }

      len = PAGE_ALIGN(len);
      if (vma->vm_end - vma->vm_start > len) {
            return -EINVAL;
      }

      /* make buffers write-thru cacheable */
      vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) &
                             ~L_PTE_BUFFERABLE);

      vma->vm_flags |= VM_IO | VM_RESERVED;

      if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
                      vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
            dev_dbg(fbi->device, "mmap remap_pfn_range failed\n");
            return -ENOBUFS;

      }

      return 0;
}

/*!
 * This structure contains the pointers to the control functions that are
 * invoked by the core framebuffer driver to perform operations like
 * blitting, rectangle filling, copy regions and cursor definition.
 */
00826 static struct fb_ops mxcfb_ops = {
      .owner = THIS_MODULE,
      .fb_set_par = mxcfb_set_par,
      .fb_check_var = mxcfb_check_var,
      .fb_setcolreg = mxcfb_setcolreg,
      .fb_pan_display = mxcfb_pan_display,
      .fb_ioctl = mxcfb_ioctl,
      .fb_fillrect = cfb_fillrect,
      .fb_copyarea = cfb_copyarea,
      .fb_imageblit = cfb_imageblit,
      .fb_blank = mxcfb_blank,
};

static struct fb_ops mxcfb_ovl_ops = {
      .owner = THIS_MODULE,
      .fb_set_par = mxcfb_set_par,
      .fb_check_var = mxcfb_check_var,
      .fb_setcolreg = mxcfb_setcolreg,
      .fb_pan_display = mxcfb_pan_display,
      .fb_ioctl = mxcfb_ioctl_ovl,
      .fb_mmap = mxcfb_mmap,
      .fb_fillrect = cfb_fillrect,
      .fb_copyarea = cfb_copyarea,
      .fb_imageblit = cfb_imageblit,
      .fb_blank = mxcfb_blank_ovl,
};

static irqreturn_t mxcfb_vsync_irq_handler(int irq, void *dev_id)
{
      struct mxcfb_data *fb_data = dev_id;

      ipu_disable_irq(irq);

      fb_data->vsync_flag = 1;
      wake_up_interruptible(&fb_data->vsync_wq);
      return IRQ_HANDLED;
}

static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id)
{
      struct fb_info *fbi = dev_id;
      struct mxcfb_info *mxc_fbi = fbi->par;

      up(&mxc_fbi->flip_sem);
      ipu_disable_irq(irq);
      return IRQ_HANDLED;
}

#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.
 */

/*
 * Suspends the framebuffer and blanks the screen. Power management support
 */
static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state)
{
      struct mxcfb_data *drv_data = platform_get_drvdata(pdev);
      struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)drv_data->fbi->par;
      struct mxcfb_info *mxc_fbi_ovl =
          (struct mxcfb_info *)drv_data->fbi_ovl->par;
#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY
      void *fbmem;
#endif

      drv_data->suspended = true;

      acquire_console_sem();
      fb_set_suspend(drv_data->fbi, 1);
      fb_set_suspend(drv_data->fbi_ovl, 1);
      release_console_sem();

      if (mxc_fbi_ovl->blank == FB_BLANK_UNBLANK) {
            ipu_disable_channel(MEM_SDC_FG, true);
      }

      if (mxc_fbi->blank == FB_BLANK_UNBLANK) {
#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY
            if (drv_data->fbi->fix.smem_start != FB_RAM_BASE_ADDR) {
                  fbmem = ioremap(FB_RAM_BASE_ADDR, FB_RAM_SIZE);
                  memcpy(fbmem, drv_data->fbi->screen_base, FB_RAM_SIZE);
                  iounmap(fbmem);
                  mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf;
                  ipu_update_channel_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER,
                                      mxc_fbi->cur_ipu_buf,
                                      FB_RAM_BASE_ADDR);
                  ipu_select_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER,
                                mxc_fbi->cur_ipu_buf);
            }
            ipu_lowpwr_display_enable();
#else
            ipu_disable_channel(MEM_SDC_BG, true);
            gpio_lcd_inactive();
#endif
      }
      return 0;
}

/*
 * Resumes the framebuffer and unblanks the screen. Power management support
 */
static int mxcfb_resume(struct platform_device *pdev)
{
      struct mxcfb_data *drv_data = platform_get_drvdata(pdev);
      struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)drv_data->fbi->par;
      struct mxcfb_info *mxc_fbi_ovl =
          (struct mxcfb_info *)drv_data->fbi_ovl->par;

      drv_data->suspended = false;

      if (mxc_fbi->blank == FB_BLANK_UNBLANK) {
#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY
            ipu_lowpwr_display_disable();
            if (drv_data->fbi->fix.smem_start != FB_RAM_BASE_ADDR) {
                  mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf;
                  ipu_update_channel_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER,
                                      mxc_fbi->cur_ipu_buf,
                                      drv_data->fbi->fix.
                                      smem_start);
                  ipu_select_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER,
                                mxc_fbi->cur_ipu_buf);
            }
#else
            gpio_lcd_active();
            ipu_enable_channel(MEM_SDC_BG);
#endif
      }

      if (mxc_fbi_ovl->blank == FB_BLANK_UNBLANK) {
            ipu_enable_channel(MEM_SDC_FG);
      }

      acquire_console_sem();
      fb_set_suspend(drv_data->fbi, 0);
      fb_set_suspend(drv_data->fbi_ovl, 0);
      release_console_sem();

      wake_up_interruptible(&drv_data->suspend_wq);
      return 0;
}
#else
#define mxcfb_suspend   NULL
#define mxcfb_resume    NULL
#endif

/*
 * Main framebuffer functions
 */

/*!
 * Allocates the DRAM memory for the frame buffer.      This buffer is remapped
 * into a non-cached, non-buffered, memory region to allow palette and pixel
 * writes to occur without flushing the cache.  Once this area is remapped,
 * all virtual memory access to the video memory should occur at the new region.
 *
 * @param       fbi     framebuffer information pointer
 *
 * @param       use_internal_ram flag on whether to use internal RAM for memory
 *
 * @return      Error code indicating success or failure
 */
00989 static int mxcfb_map_video_memory(struct fb_info *fbi, bool use_internal_ram)
{
      int retval = 0;

#ifdef CONFIG_FB_MXC_INTERNAL_MEM
      if (use_internal_ram) {
            fbi->fix.smem_len = FB_RAM_SIZE;
            fbi->fix.smem_start = FB_RAM_BASE_ADDR;
            if (fbi->fix.smem_len <
                (fbi->var.yres_virtual * fbi->fix.line_length)) {
                  dev_err(fbi->device,
                        "Not enough internal RAM for framebuffer configuration\n");
                  retval = -EINVAL;
                  goto err0;
            }

            if (request_mem_region(fbi->fix.smem_start, fbi->fix.smem_len,
                               fbi->device->driver->name) == NULL) {
                  dev_err(fbi->device,
                        "Unable to request internal RAM\n");
                  retval = -ENOMEM;
                  goto err0;
            }

            if (!(fbi->screen_base = ioremap(fbi->fix.smem_start,
                                     fbi->fix.smem_len))) {
                  dev_err(fbi->device,
                        "Unable to map fb memory to virtual address\n");
                  release_mem_region(fbi->fix.smem_start,
                                 fbi->fix.smem_len);
                  retval = -EIO;
                  goto err0;
            }

            iram_clk = clk_get(NULL, "iram_clk");
            clk_enable(iram_clk);
      } else
#endif
      {
            fbi->fix.smem_len = fbi->var.yres_virtual *
                fbi->fix.line_length;
            fbi->screen_base =
                dma_alloc_writecombine(fbi->device,
                                 fbi->fix.smem_len,
                                 (dma_addr_t *) & fbi->fix.smem_start,
                                 GFP_DMA);

            if (fbi->screen_base == 0) {
                  dev_err(fbi->device,
                        "Unable to allocate framebuffer memory\n");
                  retval = -EBUSY;
                  goto err0;
            }
      }

      dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n",
            (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len);

      fbi->screen_size = fbi->fix.smem_len;

      /* Clear the screen */
      memset((char *)fbi->screen_base, 0, fbi->fix.smem_len);

      return 0;

      err0:
      fbi->fix.smem_len = 0;
      fbi->fix.smem_start = 0;
      fbi->screen_base = NULL;
      return retval;
}

/*!
 * De-allocates the DRAM memory for the frame buffer.
 *
 * @param       fbi     framebuffer information pointer
 *
 * @return      Error code indicating success or failure
 */
01068 static int mxcfb_unmap_video_memory(struct fb_info *fbi)
{
#ifdef CONFIG_FB_MXC_INTERNAL_MEM
      if (fbi->fix.smem_start == FB_RAM_BASE_ADDR) {
            iounmap(fbi->screen_base);
            release_mem_region(fbi->fix.smem_start, fbi->fix.smem_len);
            fbi->fix.smem_start = 0;
            fbi->fix.smem_len = 0;
            clk_disable(iram_clk);
      } else
#endif
      {
            dma_free_writecombine(fbi->device, fbi->fix.smem_len,
                              fbi->screen_base, fbi->fix.smem_start);
      }
      fbi->screen_base = 0;
      fbi->fix.smem_start = 0;
      fbi->fix.smem_len = 0;
      return 0;
}

/*!
 * Initializes the framebuffer information pointer. After allocating
 * sufficient memory for the framebuffer structure, the fields are
 * filled with custom information passed in from the configurable
 * structures.  This includes information such as bits per pixel,
 * color maps, screen width/height and RGBA offsets.
 *
 * @return      Framebuffer structure initialized with our information
 */
01098 static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops)
{
      struct fb_info *fbi;
      struct mxcfb_info *mxcfbi;

      /*
       * Allocate sufficient memory for the fb structure
       */
      fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev);
      if (!fbi)
            return NULL;

      mxcfbi = (struct mxcfb_info *)fbi->par;

      fbi->var.activate = FB_ACTIVATE_NOW;

      fbi->fbops = ops;
      fbi->flags = FBINFO_FLAG_DEFAULT;
      fbi->pseudo_palette = mxcfbi->pseudo_palette;

      spin_lock_init(&mxcfbi->fb_lock);

      /*
       * Allocate colormap
       */
      fb_alloc_cmap(&fbi->cmap, 16, 0);

      return fbi;
}

/*!
 * Probe routine for the framebuffer driver. It is called during the
 * driver binding process.      The following functions are performed in
 * this routine: Framebuffer initialization, Memory allocation and
 * mapping, Framebuffer registration, IPU initialization.
 *
 * @return      Appropriate error code to the kernel common code
 */
01136 static int mxcfb_probe(struct platform_device *pdev)
{
      char *mode = pdev->dev.platform_data;
      struct fb_info *fbi;
      struct mxcfb_info *mxcfbi;
      struct fb_info *fbi_ovl;
      int ret = 0;

      /*
       * Initialize FB structures
       */
      fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops);
      if (!fbi) {
            ret = -ENOMEM;
            goto err0;
      }
      mxcfbi = (struct mxcfb_info *)fbi->par;

      mxcfbi->ipu_ch_irq = IPU_IRQ_SDC_BG_EOF;
      mxcfbi->cur_ipu_buf = 0;
      mxcfbi->ipu_ch = MEM_SDC_BG;

      ipu_sdc_set_global_alpha(true, 0xFF);
      ipu_sdc_set_color_key(MEM_SDC_BG, false, 0);

      if (ipu_request_irq(IPU_IRQ_SDC_BG_EOF, mxcfb_irq_handler, 0,
                      MXCFB_NAME, fbi) != 0) {
            dev_err(&pdev->dev, "Error registering BG irq handler.\n");
            ret = -EBUSY;
            goto err1;
      }
      ipu_disable_irq(IPU_IRQ_SDC_BG_EOF);

      if (fb_mode == NULL) {
            fb_mode = mode;
      }

      if (!fb_find_mode(&fbi->var, fbi, fb_mode, mxcfb_modedb,
                    mxcfb_modedb_sz, NULL, default_bpp)) {
            ret = -EBUSY;
            goto err2;
      }
      fb_videomode_to_modelist(mxcfb_modedb, mxcfb_modedb_sz, &fbi->modelist);

      /* Default Y virtual size is 2x panel size */
#ifndef CONFIG_FB_MXC_INTERNAL_MEM
      fbi->var.yres_virtual = fbi->var.yres * 2;
#endif

      mxcfb_drv_data.fbi = fbi;
      mxcfb_drv_data.backlight_level = 255;
      mxcfb_drv_data.suspended = false;
      init_waitqueue_head(&mxcfb_drv_data.suspend_wq);

      mxcfbi->blank = FB_BLANK_NORMAL;
      ret = mxcfb_set_par(fbi);
      if (ret < 0) {
            goto err2;
      }
      mxcfb_blank(FB_BLANK_UNBLANK, fbi);

      /*
       * Register framebuffer
       */
      ret = register_framebuffer(fbi);
      if (ret < 0) {
            goto err2;
      }

      /*
       * Initialize Overlay FB structures
       */
      fbi_ovl = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ovl_ops);
      if (!fbi_ovl) {
            ret = -ENOMEM;
            goto err3;
      }
      mxcfb_drv_data.fbi_ovl = fbi_ovl;
      mxcfbi = (struct mxcfb_info *)fbi_ovl->par;

      mxcfbi->ipu_ch_irq = IPU_IRQ_SDC_FG_EOF;
      mxcfbi->cur_ipu_buf = 0;
      mxcfbi->ipu_ch = MEM_SDC_FG;

      if (ipu_request_irq(IPU_IRQ_SDC_FG_EOF, mxcfb_irq_handler, 0,
                      MXCFB_NAME, fbi_ovl) != 0) {
            dev_err(fbi->device, "Error registering FG irq handler.\n");
            ret = -EBUSY;
            goto err4;
      }
      ipu_disable_irq(mxcfbi->ipu_ch_irq);

      /* Default Y virtual size is 2x panel size */
      fbi_ovl->var = fbi->var;
      fbi_ovl->var.yres_virtual = fbi->var.yres * 2;

      /* Overlay is blanked by default */
      mxcfbi->blank = FB_BLANK_NORMAL;

      ret = mxcfb_set_par(fbi_ovl);
      if (ret < 0) {
            goto err5;
      }

      /*
       * Register overlay framebuffer
       */
      ret = register_framebuffer(fbi_ovl);
      if (ret < 0) {
            goto err5;
      }

      platform_set_drvdata(pdev, &mxcfb_drv_data);

      init_waitqueue_head(&mxcfb_drv_data.vsync_wq);
      if (!cpu_is_mx31() && !cpu_is_mx32()) {
            if ((ret = ipu_request_irq(IPU_IRQ_SDC_DISP3_VSYNC,
                                 mxcfb_vsync_irq_handler,
                                 0, MXCFB_NAME,
                                 &mxcfb_drv_data)) < 0) {
                  goto err6;
            }
            ipu_disable_irq(IPU_IRQ_SDC_DISP3_VSYNC);
      }

      printk(KERN_INFO "mxcfb: fb registered, using mode %s\n", fb_mode);
      return 0;

      err6:
      unregister_framebuffer(fbi_ovl);
      err5:
      ipu_free_irq(IPU_IRQ_SDC_FG_EOF, fbi_ovl);
      err4:
      fb_dealloc_cmap(&fbi_ovl->cmap);
      framebuffer_release(fbi_ovl);
      err3:
      unregister_framebuffer(fbi);
      err2:
      ipu_free_irq(IPU_IRQ_SDC_BG_EOF, fbi);
      err1:
      fb_dealloc_cmap(&fbi->cmap);
      framebuffer_release(fbi);
      err0:
      printk(KERN_ERR "mxcfb: failed to register fb\n");
      return ret;
}

/*!
 * This structure contains pointers to the power management callback functions.
 */
01286 static struct platform_driver mxcfb_driver = {
      .driver = {
               .name = MXCFB_NAME,
               },
      .probe = mxcfb_probe,
      .suspend = mxcfb_suspend,
      .resume = mxcfb_resume,
};

/*
 * Parse user specified options (`video=trident:')
 * example:
 *    video=trident:800x600,bpp=16,noaccel
 */
int mxcfb_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;
}

/*!
 * Main entry function for the framebuffer. The function registers the power
 * management callback functions with the kernel and also registers the MXCFB
 * callback functions with the core Linux framebuffer driver \b fbmem.c
 *
 * @return      Error code indicating success or failure
 */
01323 int __init mxcfb_init(void)
{
      int ret = 0;
#ifndef MODULE
      char *option = NULL;
#endif

#ifndef MODULE
      if (fb_get_options("mxcfb", &option))
            return -ENODEV;
      mxcfb_setup(option);
#endif

      ret = platform_driver_register(&mxcfb_driver);
      return ret;
}

void mxcfb_exit(void)
{
      struct fb_info *fbi = mxcfb_drv_data.fbi;

      if (fbi) {
            mxcfb_unmap_video_memory(fbi);

            if (&fbi->cmap)
                  fb_dealloc_cmap(&fbi->cmap);

            unregister_framebuffer(fbi);
            framebuffer_release(fbi);
      }

      fbi = mxcfb_drv_data.fbi_ovl;
      if (fbi) {
            mxcfb_unmap_video_memory(fbi);

            if (&fbi->cmap)
                  fb_dealloc_cmap(&fbi->cmap);

            unregister_framebuffer(fbi);
            framebuffer_release(fbi);
      }
#ifndef CONFIG_ARCH_MX3
      ipu_free_irq(IPU_IRQ_SDC_DISP3_VSYNC, &mxcfb_drv_data);
#endif

      platform_driver_unregister(&mxcfb_driver);
}

module_init(mxcfb_init);
module_exit(mxcfb_exit);

MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MXC framebuffer driver");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("fb");

Generated by  Doxygen 1.6.0   Back to index