]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
ENGR00210075-3 - SPDC: Add Sipix driver
authorFugang Duan <B38611@freescale.com>
Sat, 19 May 2012 03:17:03 +0000 (11:17 +0800)
committerOliver Wendt <ow@karo-electronics.de>
Mon, 30 Sep 2013 12:11:55 +0000 (14:11 +0200)
Add Sipix driver for electronic paper dispaly
- Support RGB565 & Y4 formats with 800x600 resolution
- Support synchronization update by waiting the last
  request update completed.
- Support automated update using Linux deferred io mechanism
- Support for panning(y-direction)
- Support rotation with 90,180,and 270 degree.
- Initial integration with ePXP, output Y4 format
- Support specific waveform modes update.
- Support Snapshot, Queue and Queue Merge update sheeme.
- Support full and partial EPD screen updates.
  mode_1 & mode_2: partial update
  mode_0 & mode_3: full update
- Align waveform mode with EPDC as below:
  mode_init = mode_0;
  mode_gc4  = mode_2;
  mode_A2   = mode_4, mode_du =mode_4;
  mode_gc8  = mode_1, mode_gc16 = mode_1, mode_gc32 = mode_1;

Signed-off-by: Fugang Duan <B38611@freescale.com>
drivers/video/mxc/Kconfig
drivers/video/mxc/Makefile
drivers/video/mxc/mxc_spdc_fb.c [new file with mode: 0644]
drivers/video/mxc/mxc_spdc_fb.h [new file with mode: 0644]
include/linux/mxcfb.h
include/linux/mxcfb_epdc_kernel.h

index fe9de73625446d58f04d75492c84d29c91d1fc60..f0afddc95bd36934d485a34d4605823c048cba69 100644 (file)
@@ -104,6 +104,17 @@ config FB_MXC_EINK_AUTO_UPDATE_MODE
     default n
     depends on FB_MXC_EINK_PANEL
 
+config FB_MXC_SIPIX_PANEL
+       depends on FB_MXC
+       depends on DMA_ENGINE
+       select FB_DEFERRED_IO
+       tristate "SIPIX Panel Framebuffer"
+
+config FB_MXC_SIPIX_AUTO_UPDATE_MODE
+       bool "SIPIX Auto-update Mode Support"
+       default n
+       depends on FB_MXC_SIPIX_PANEL
+
 config FB_MXC_ELCDIF_FB
        depends on FB && ARCH_MXC
        tristate "Support MXC ELCDIF framebuffer"
index 084837c709295e81789deb8c7980d7e76acc0a52..21aef052ecd6348642492ea19ef630c7f2bc402d 100644 (file)
@@ -25,4 +25,5 @@ obj-$(CONFIG_FB_MXC_SEIKO_WVGA_SYNC_PANEL)   += mxcfb_seiko_wvga.o
 obj-$(CONFIG_FB_MXC_TVOUT_CH7024)           += ch7024.o
 obj-$(CONFIG_FB_MXC_CH7026)                            += mxcfb_ch7026.o
 obj-$(CONFIG_FB_MXC_EINK_PANEL)             += mxc_epdc_fb.o
+obj-$(CONFIG_FB_MXC_SIPIX_PANEL)            += mxc_spdc_fb.o
 obj-$(CONFIG_FB_MXC_ELCDIF_FB)             += mxc_elcdif_fb.o
diff --git a/drivers/video/mxc/mxc_spdc_fb.c b/drivers/video/mxc/mxc_spdc_fb.c
new file mode 100644 (file)
index 0000000..4ae8bf6
--- /dev/null
@@ -0,0 +1,4166 @@
+/*
+ * Copyright (C) 2012 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+/*
+ * Based on MXC EPDC Driver, Freescale Solutions, Inc All Rights Reserved.
+ */
+
+#include "mxc_spdc_fb.h"
+
+#define MERGE_OK       0
+#define MERGE_FAIL      1
+#define MERGE_BLOCK     2
+
+#define SPDC_DEFAULT_TEMP      30
+#define TEMP_NO_SET            0xFF
+#define POWER_STATE_OFF                0
+#define POWER_STATE_ON         1
+#define POWER_READY_OFF                false
+#define POWER_READY_ON         true
+
+#define INIT_UPDATE_MARKER     0x12345678
+#define PAN_UPDATE_MARKER      0x12345679
+
+#define SPDC_MAX_NUM_UPDATES   32
+#define SPDC_MAX_NUM_BUFFERS   2
+#define SPDC_MAX_NUM_PREPROCESS        15
+#define NUM_SCREENS_MIN                2
+#define SPDC_DEFAULT_BPP       16
+
+mxc_spdc_t *g_fb_data;
+
+static int mxc_spdc_fb_send_update(struct mxcfb_update_data *upd_data,
+                                  struct fb_info *info);
+static int mxc_spdc_fb_wait_update_complete(struct mxcfb_update_marker_data
+               *marker_data, struct fb_info *info);
+
+static const struct mxc_spdc_resolution_map_para spdc_gray_res_map[] = {
+/*define Gray Mode resolution mapping*/
+       {0x18, 600, 800, PORTRAIT},
+       {0x19, 768, 1024, PORTRAIT},
+       {0x1a, 0,   0,    RESERVED},
+       {0x1b, 600, 1024, PORTRAIT},
+       {0x1c, 825, 1200, PORTRAIT},
+       {0x1d, 1024, 1280, PORTRAIT},
+       {0x1e, 1200, 1600, PORTRAIT},
+       {0x10, 800, 1024, PORTRAIT},
+       {0x11, 825, 1280, PORTRAIT},
+       {0x12, 800, 1280, PORTRAIT},
+       {0x13, 768, 1280, PORTRAIT},
+       {0x14, 960, 1280, PORTRAIT},
+       {0x0, 800, 600, LANDSCAPE},
+       {0x1, 1024, 768, LANDSCAPE},
+       {0x2, 0, 0, RESERVED},
+       {0x3, 1024, 600, LANDSCAPE},
+       {0x4, 1200, 825, LANDSCAPE},
+       {0x5, 1280, 1024, LANDSCAPE},
+       {0x6, 1600, 1200, LANDSCAPE},
+       {0x7, 1024, 800, LANDSCAPE},
+       {0x8, 1280, 825, LANDSCAPE},
+       {0x9, 1280, 800, LANDSCAPE},
+       {0xa, 1280, 768, LANDSCAPE},
+       {0xb, 1280, 960, LANDSCAPE},
+       {0xFFFF, 800, 600, LANDSCAPE},
+};
+
+static const struct mxc_spdc_resolution_map_para spdc_rgbw_res_map[] = {
+/*define RGBW Mode resolution mapping*/
+       {0x18, 300, 400, PORTRAIT},
+       {0x19, 384, 512, PORTRAIT},
+       {0x1a, 0,   0,    RESERVED},
+       {0x1b, 300, 512, PORTRAIT},
+       {0x1c, 0, 0, RESERVED},
+       {0x1d, 512, 640, PORTRAIT},
+       {0x1e, 600, 800, PORTRAIT},
+       {0x10, 400, 512, PORTRAIT},
+       {0x11, 0, 0, RESERVED},
+       {0x12, 400, 640, PORTRAIT},
+       {0x13, 384, 640, PORTRAIT},
+       {0x14, 480, 640, PORTRAIT},
+       {0x0, 400, 300, LANDSCAPE},
+       {0x1, 512, 384, LANDSCAPE},
+       {0x2, 0, 0, RESERVED},
+       {0x3, 512, 300, LANDSCAPE},
+       {0x4, 0, 0, RESERVED},
+       {0x5, 640, 512, LANDSCAPE},
+       {0x6, 800, 600, LANDSCAPE},
+       {0x7, 512, 400, LANDSCAPE},
+       {0x8, 0, 0, RESERVED},
+       {0x9, 640, 400, LANDSCAPE},
+       {0xa, 640, 384, LANDSCAPE},
+       {0xb, 640, 480, LANDSCAPE},
+       {0xFFFF, 400, 300, LANDSCAPE},
+};
+
+static void get_panel_init_set(struct imx_spdc_panel_init_set*
+                                       panel_set, u32 *val)
+{
+       *val = panel_set->yoe_pol |
+               (panel_set->dual_gate << 1) |
+               (panel_set->ud << 7) |
+               (panel_set->rl << 8) |
+               (panel_set->data_filter_n  << 9) |
+               (panel_set->power_ready  << 10) |
+               (panel_set->rgbw_mode_enable  << 11) |
+               (panel_set->hburst_len_en << 13) |
+               ((panel_set->resolution & 0x1F) << 2);
+}
+
+static inline void spdc_intr_enable(mxc_spdc_t *fb_data, u32 int_type)
+{
+       u32 status;
+
+       status = __raw_readl(fb_data->hwp + SPDC_INT_ENABLE);
+       status |= (int_type & SPDC_IRQ_ALL_MASK);
+       __raw_writel(status, fb_data->hwp + SPDC_INT_ENABLE);
+}
+
+static bool spdc_is_update_finish(mxc_spdc_t *fb_data)
+{
+       u32 val =  __raw_readl(fb_data->hwp + SPDC_INT_STA_CLR);
+       bool is_finish = (val & SPDC_IRQ_STA_FRAME_UPDATE) ? true : false;
+
+       return is_finish;
+}
+
+static int spdc_get_intr_stat(mxc_spdc_t *fb_data)
+{
+       u32 status = __raw_readl(fb_data->hwp + SPDC_INT_STA_CLR);
+       return status & 0xF;
+}
+
+static inline void
+spdc_intr_stat_clear(mxc_spdc_t *fb_data, u32 int_type)
+{
+       /* write 1 to clear status */
+       u32 status = (int_type & SPDC_IRQ_STA_ALL_MASK);
+       __raw_writel(status, fb_data->hwp + SPDC_INT_STA_CLR);
+}
+
+static inline void spdc_set_nextbuf_addr(mxc_spdc_t *fb_data)
+{
+       u32 addr = fb_data->fresh_param.buf_addr.next_buf_phys_addr;
+       __raw_writel(addr, fb_data->hwp + SPDC_NEXT_BUF);
+       dev_dbg(fb_data->dev, "add: 0x%x\n", addr);
+}
+
+static inline void spdc_set_curbuf_addr(mxc_spdc_t *fb_data)
+{
+       u32 addr = fb_data->fresh_param.buf_addr.cur_buf_phys_addr;
+       __raw_writel(addr, fb_data->hwp + SPDC_CURRENT_BUF);
+}
+
+static inline void spdc_set_prebuf_addr(mxc_spdc_t *fb_data)
+{
+       u32 addr = fb_data->fresh_param.buf_addr.pre_buf_phys_addr;
+       __raw_writel(addr, fb_data->hwp + SPDC_PRE_BUF);
+}
+
+static inline void spdc_set_cntbuf_addr(mxc_spdc_t *fb_data)
+{
+       u32 addr = fb_data->fresh_param.buf_addr.frm_cnt_buf_phys_addr;
+       __raw_writel(addr, fb_data->hwp + SPDC_CNT_BUF);
+}
+
+static inline void spdc_set_lutbuf_addr(mxc_spdc_t *fb_data)
+{
+       u32 addr = fb_data->fresh_param.buf_addr.lut_buf_phys_addr;
+       __raw_writel(addr, fb_data->hwp + SPDC_LUT_BUF);
+}
+
+static inline void spdc_set_update_coord(mxc_spdc_t *fb_data)
+{
+       u32 x = fb_data->fresh_param.update_region.left;
+       u32 y = fb_data->fresh_param.update_region.top;
+
+       if (!x)
+               x++;
+       if (!y)
+               y++;
+
+       dev_dbg(fb_data->dev, "x:%d, y:%d\n", x, y);
+       x = (u32)(((x & SPDC_UPDATE_X_Y_MAX_SIZE) << 16) |
+                       (y & SPDC_UPDATE_X_Y_MAX_SIZE));
+       __raw_writel(x, fb_data->hwp + SPDC_UPDATA_X_Y);
+}
+
+static inline void spdc_set_update_dimensions(mxc_spdc_t *fb_data)
+{
+       u32 w = fb_data->fresh_param.update_region.width;
+       u32 h = fb_data->fresh_param.update_region.height;
+
+       if (!w)
+               w++;
+       if (!h)
+               h++;
+
+       dev_dbg(fb_data->dev, "w:%d, h:%d\n", w, h);
+       w = (u32)(((w & SPDC_UPDATE_W_H_MAX_SIZE) << 16) |
+                       (h & SPDC_UPDATE_W_H_MAX_SIZE));
+       __raw_writel(w, fb_data->hwp + SPDC_UPDATE_W_H);
+}
+
+static inline void spdc_set_update_temper(mxc_spdc_t *fb_data)
+{
+       s8 temper = (s8)(fb_data->fresh_param.temper & 0xFF) << 1;
+
+       if (temper > -110 && temper < 200)
+               __raw_writel(temper, fb_data->hwp + SPDC_TEMP_INFO);
+       else
+               __raw_writel(SPDC_DEFAULT_TEMP, fb_data->hwp + SPDC_TEMP_INFO);
+}
+
+static inline void spdc_trigger_update(mxc_spdc_t *fb_data)
+{
+       u32 val;
+       struct partial_refresh_param *fresh_param = &fb_data->fresh_param;
+
+       if ((fresh_param->wave_mode & SPDC_WAV_MODE_MASK) && fresh_param->flash)
+               val = SPDC_DISP_TRIGGER_FLASH;
+       else
+               val = 0;
+
+       val |= fresh_param->wave_mode << 1;
+       val |= SPDC_DISP_TRIGGER_ENABLE;
+       dev_dbg(fb_data->dev, "wave:%d\n", fresh_param->wave_mode);
+       __raw_writel(val, fb_data->hwp + SPDC_DISP_TRIGGER);
+}
+
+static bool is_lut_checksum_ok(mxc_spdc_t *fb_data)
+{
+       u32 status;
+
+       status = __raw_readl(fb_data->hwp + SPDC_STATUS);
+       status &= SPDC_IRQ_STA_ERR;
+
+       return status ? true : false;
+}
+
+static void spdc_clk_gate(mxc_spdc_t *fb_data, bool enable)
+{
+       if (enable)
+               __raw_writel(SPDC_SW_GATE_CLK_ENABLE,
+               fb_data->hwp + SPDC_SW_GATE_CLK);
+       else
+               __raw_writel(~SPDC_SW_GATE_CLK_ENABLE,
+               fb_data->hwp + SPDC_SW_GATE_CLK);
+}
+
+static int update_panel_init_set(mxc_spdc_t *fb_data)
+{
+       int ret = 0;
+       u32 init_val;
+
+       get_panel_init_set(&fb_data->panel_set, &init_val);
+       dev_dbg(fb_data->dev, "panel init setting:%x\n", init_val);
+
+       __raw_writel(init_val, fb_data->hwp + SPDC_PANEL_INIT_SET);
+
+       /*wait init setting update finish*/
+       ret = wait_for_completion_timeout(&fb_data->init_finish,
+                               msecs_to_jiffies(4000));
+       if (!ret)
+               dev_err(fb_data->dev, "Timed out for init setting!\n");
+
+       return ret;
+}
+
+static void spdc_panel_pwr_on(mxc_spdc_t *fb_data)
+{
+       fb_data->panel_set.power_ready = POWER_READY_ON;
+}
+
+static void spdc_panel_pwr_down(mxc_spdc_t *fb_data)
+{
+       fb_data->panel_set.power_ready = POWER_READY_OFF;
+}
+
+static void spdc_powerdown(mxc_spdc_t *fb_data)
+{
+       mutex_lock(&fb_data->power_mutex);
+
+       /* If powering_down has been cleared, a powerup
+       * request is pre-empting this powerdown request.
+       */
+       if (!fb_data->powering_down
+               || (fb_data->power_state == POWER_STATE_OFF)) {
+               mutex_unlock(&fb_data->power_mutex);
+               return;
+       }
+
+       dev_dbg(fb_data->dev, "spdc Powerdown\n");
+
+       /* Disable power to the AUO panel */
+       regulator_disable(fb_data->vcom_regulator);
+       regulator_disable(fb_data->display_regulator);
+
+       /*enable spdc clock gating*/
+       spdc_clk_gate(fb_data, true);
+       clk_disable(fb_data->spdc_clk_pix);
+       clk_disable(fb_data->spdc_clk_axi);
+
+       /* Disable pins used by SPDC (to prevent leakage current) */
+       if (fb_data->pdata->disable_pins)
+               fb_data->pdata->disable_pins();
+
+       /* turn off the V3p3 */
+       regulator_disable(fb_data->v3p3_regulator);
+
+       fb_data->power_state = POWER_STATE_OFF;
+       fb_data->powering_down = false;
+       spdc_panel_pwr_down(fb_data);
+
+       if (fb_data->wait_for_powerdown) {
+               fb_data->wait_for_powerdown = false;
+               complete(&fb_data->powerdown_compl);
+       }
+
+       mutex_unlock(&fb_data->power_mutex);
+}
+
+static void spdc_powerup(mxc_spdc_t *fb_data)
+{
+       int ret = 0;
+       mutex_lock(&fb_data->power_mutex);
+
+       /*
+        * If power down request is pending, clear
+        * powering_down to cancel the request.
+        */
+       if (fb_data->powering_down)
+               fb_data->powering_down = false;
+
+       if (fb_data->power_state == POWER_STATE_ON) {
+               mutex_unlock(&fb_data->power_mutex);
+               return;
+       }
+
+       dev_dbg(fb_data->dev, "spdc Powerup\n");
+
+       /* Enable the v3p3 regulator */
+       ret = regulator_enable(fb_data->v3p3_regulator);
+       if (IS_ERR((void *)ret)) {
+               dev_err(fb_data->dev, "Unable to enable V3P3 regulator."
+                       "err = 0x%x\n", ret);
+               mutex_unlock(&fb_data->power_mutex);
+               return;
+       }
+
+       msleep(1);
+
+       /* Enable pins used by SPDC */
+       if (fb_data->pdata->enable_pins)
+               fb_data->pdata->enable_pins();
+
+       /* Enable clocks to SPDC */
+       clk_enable(fb_data->spdc_clk_axi);
+       clk_enable(fb_data->spdc_clk_pix);
+
+       /*disable spdc gate*/
+       spdc_clk_gate(fb_data, false);
+
+       /* Enable power to the EPD panel */
+       ret = regulator_enable(fb_data->display_regulator);
+       if (IS_ERR((void *)ret)) {
+               dev_err(fb_data->dev, "Unable to enable DISPLAY regulator."
+                       "err = 0x%x\n", ret);
+               mutex_unlock(&fb_data->power_mutex);
+               return;
+       }
+       ret = regulator_enable(fb_data->vcom_regulator);
+       if (IS_ERR((void *)ret)) {
+               dev_err(fb_data->dev, "Unable to enable VCOM regulator."
+                       "err = 0x%x\n", ret);
+               mutex_unlock(&fb_data->power_mutex);
+               return;
+       }
+
+       fb_data->power_state = POWER_STATE_ON;
+       spdc_panel_pwr_on(fb_data);
+
+       mutex_unlock(&fb_data->power_mutex);
+}
+
+#ifdef DEBUG
+static void
+check_waveform(u32 *wv_buf_orig, u32 *wv_buf_cur, u32 wv_buf_size)
+{
+       int i;
+       bool is_mismatch = false;
+       for (i = 0; i < wv_buf_size; i++) {
+               if (wv_buf_orig[i] != wv_buf_cur[i]) {
+                       is_mismatch = true;
+                       printk(KERN_ERR "Waveform mismatch!\n");
+               }
+       }
+
+       if (!is_mismatch)
+               printk(KERN_DEBUG "No mismatches!\n");
+}
+#else
+static void
+check_waveform(u32 *wv_buf_orig, u32 *wv_buf_cur, u32 wv_buf_size) {}
+#endif
+
+static void get_spdc_version(mxc_spdc_t *fb_data)
+{
+       struct mxc_spdc_version *spdc_ver = &fb_data->spdc_ver;
+       u32 disp_id, tcon_id;
+
+       disp_id = __raw_readl(fb_data->hwp + SPDC_DISP_VER);
+       tcon_id = __raw_readl(fb_data->hwp + SPDC_TCON_VER);
+
+       spdc_ver->disp_ver.product_id = disp_id & 0xFFFF;
+       spdc_ver->disp_ver.lut_ver = (disp_id >> 16) & 0xFF;
+       spdc_ver->disp_ver.epd_type = (disp_id >> 24) & 0xFF;
+       spdc_ver->tcon_ver = tcon_id & 0xFF;
+
+       dev_info(fb_data->dev, "EPD type ID:%x, Tcon ID:%x\n",
+               spdc_ver->disp_ver.product_id, spdc_ver->tcon_ver);
+}
+
+static void spdc_set_update_concurrency(mxc_spdc_t *fb_data)
+{
+       u32 concur_mode;
+
+       concur_mode = fb_data->fresh_param.concur & 0xFF;
+       concur_mode |= (SPDC_LUT_MODE_OFFSET << 8);
+
+       __raw_writel(concur_mode, fb_data->hwp + SPDC_LUT_PARA_UPDATE);
+}
+
+static bool is_preprocess_list_full(mxc_spdc_t *fb_data)
+{
+       /* Check to see if preprocess are full in this list */
+       if (fb_data->upd_preprocess_num >= SPDC_MAX_NUM_PREPROCESS)
+               return true;
+       else
+               return false;
+}
+
+static void spdc_submit_update(mxc_spdc_t *fb_data)
+{
+       fb_data->updates_active = true;
+
+       spdc_set_nextbuf_addr(fb_data);
+       spdc_set_update_coord(fb_data);
+       spdc_set_update_dimensions(fb_data);
+       spdc_set_update_temper(fb_data);
+       spdc_trigger_update(fb_data);
+}
+
+static int spdc_init_sequence(mxc_spdc_t *fb_data)
+{
+       struct fb_var_screeninfo *screeninfo = &fb_data->spdc_fb_var;
+       struct spdc_buffer_addr *buf_addr = &fb_data->fresh_param.buf_addr;
+       struct imx_spdc_panel_init_set *init_set =
+               fb_data->pdata->spdc_mode->init_set;
+       u32 xres, yres;
+       int ret = -EFAULT;
+
+       /*init spdc power*/
+       spdc_powerup(fb_data);
+
+       /* enable all interrupt */
+       spdc_intr_stat_clear(fb_data, SPDC_INT_STA_CLR);
+       spdc_intr_enable(fb_data, SPDC_IRQ_ALL_MASK);
+       /* set ACC concurrency update mode */
+       if (fb_data->fresh_param.concur)
+               spdc_set_update_concurrency(fb_data);
+
+       /* program SPDC register and trigger to process buffer*/
+       buf_addr->next_buf_phys_addr = fb_data->phy_next_buf;
+       buf_addr->cur_buf_phys_addr = fb_data->phy_current_buf;
+       buf_addr->pre_buf_phys_addr = fb_data->phy_pre_buf;
+       buf_addr->frm_cnt_buf_phys_addr = fb_data->phy_cnt_buf;
+       buf_addr->lut_buf_phys_addr = fb_data->phy_lut_buf;
+
+       /* Use unrotated (native) width/height */
+       if ((screeninfo->rotate == FB_ROTATE_CW) ||
+               (screeninfo->rotate == FB_ROTATE_CCW)) {
+               xres = screeninfo->yres;
+               yres = screeninfo->xres;
+       } else {
+               xres = screeninfo->xres;
+               yres = screeninfo->yres;
+       }
+       fb_data->fresh_param.update_region.left = 0;
+       fb_data->fresh_param.update_region.top = 0;
+       fb_data->fresh_param.update_region.width = xres;
+       fb_data->fresh_param.update_region.height = yres;
+
+       /* set panel temperature as environment temperature */
+       fb_data->fresh_param.temper = SPDC_DEFAULT_TEMP;
+       /* set waveform mode */
+       fb_data->fresh_param.wave_mode = SPDC_WAV_MODE_DEFAULT;
+
+       spdc_set_update_coord(fb_data);
+       spdc_set_update_dimensions(fb_data);
+
+       spdc_set_update_temper(fb_data);
+
+       spdc_set_nextbuf_addr(fb_data);
+       spdc_set_curbuf_addr(fb_data);
+       spdc_set_prebuf_addr(fb_data);
+       spdc_set_cntbuf_addr(fb_data);
+
+       /* load waveform*/
+       spdc_set_lutbuf_addr(fb_data);
+       ret = wait_for_completion_timeout(&fb_data->lut_down,
+                                       msecs_to_jiffies(4000));
+       if (!ret) {
+               dev_err(fb_data->dev,
+                       "Timed out for lut!\n");
+               return ret;
+       }
+
+       /* init SPDC setting, the setting get from platform data */
+       fb_data->panel_set.yoe_pol = init_set->yoe_pol;
+       fb_data->panel_set.dual_gate = init_set->dual_gate;
+       fb_data->panel_set.ud = init_set->ud;
+       fb_data->panel_set.rl = init_set->rl;
+       fb_data->panel_set.data_filter_n = init_set->data_filter_n;
+       fb_data->panel_set.rgbw_mode_enable = init_set->rgbw_mode_enable;
+       fb_data->panel_set.hburst_len_en = init_set->hburst_len_en;
+       ret = update_panel_init_set(fb_data);
+
+       return ret;
+}
+
+static u32 mxc_spdc_partial_refresh_low(mxc_spdc_t *fb_data, void *buffer)
+{
+       u8 *fresh_addr;
+       void *pattern = buffer;
+       struct partial_refresh_param *fresh_param = &fb_data->fresh_param;
+       u32 fresh_size;
+       int ret = 0;
+
+       fb_data->updates_active = true;
+
+       fresh_addr = (u8 *)(fb_data->virt_start) +
+               (fresh_param->update_region.top * fresh_param->stride) +
+               ((fresh_param->update_region.left * fb_data->default_bpp) >> 3);
+       fresh_size = (u32)((fresh_param->update_region.width *
+               fresh_param->update_region.height * fb_data->default_bpp) >> 3);
+
+       if (buffer != NULL) {
+               while (fresh_size > 0) {
+                       memcpy((void *)fresh_addr, pattern,
+                               fresh_param->update_region.width);
+                       fresh_size -= fresh_param->update_region.width;
+                       fresh_addr += fresh_param->update_region.top *
+                                       fresh_param->stride;
+                       pattern +=  fresh_param->update_region.width;
+               }
+       }
+
+       /* program SPDC register and trigger to process buffer*/
+       fb_data->fresh_param.buf_addr.next_buf_phys_addr =
+               fb_data->info.fix.smem_start + fb_data->fb_offset;
+       fb_data->fresh_param.wave_mode = fb_data->wv_modes.mode_init;
+       spdc_submit_update(fb_data);
+
+       ret = wait_for_completion_timeout(&fb_data->update_finish,
+                                       msecs_to_jiffies(3000));
+       if (!ret) {
+               dev_err(fb_data->dev,
+                       "display update timeout!\n");
+               return -ETIMEDOUT;
+       }
+
+       return ret;
+}
+
+static u32 spdc_fb_dev_init(mxc_spdc_t *fb_data)
+{
+       fb_data->auto_mode = AUTO_UPDATE_MODE_REGION_MODE;
+       fb_data->fresh_param.wave_mode = SPDC_WAV_MODE_0;
+       fb_data->operation_mode = SPDC_NO_OPERATION;
+       fb_data->is_deep_fresh = false;
+
+       /* Init the concurrency update */
+       fb_data->fresh_param.concur = 0;
+       fb_data->upd_preprocess_num = 0;
+       fb_data->submit_upd_sta = 0;
+
+       fb_data->fresh_param.temper = SPDC_DEFAULT_TEMP;
+
+       return 0;
+}
+
+/**
+ * mxc_spdc_device_is_busy - check spdc device busy status.
+ * Returns 0 if spdc device is idle.
+ */
+static int mxc_spdc_device_is_busy(mxc_spdc_t *fb_data)
+{
+       u32 status;
+       u32 orig_jiffies = jiffies;
+
+       while (1) {
+               status = __raw_readl(fb_data->hwp + SPDC_STATUS);
+               if ((status & SPDC_PANEL_STAUTS_BUSY) &&
+                       ((status & 0xF0) == SPDC_TCON_STATUS_IDLE))
+                       break;
+
+               if (signal_pending(current)) {
+                       dev_dbg(fb_data->dev, "SPDC Interrupted\n");
+                       return -EINTR;
+               }
+
+               if (time_after(jiffies, orig_jiffies +
+                               msecs_to_jiffies(3000))) {
+                       dev_dbg(fb_data->dev, "SPDC is busy\n");
+                        return -ETIMEDOUT;
+               }
+
+               schedule();
+       }
+
+       return 0;
+}
+
+static bool is_free_list_full(mxc_spdc_t *fb_data)
+{
+       int count = 0;
+       struct update_data_list *plist;
+
+       /* Count buffers in free buffer list */
+       list_for_each_entry(plist, &fb_data->upd_buf_free_list, list)
+               count++;
+
+       /* Check to see if all buffers are in this list */
+       if (count == fb_data->max_num_updates)
+               return true;
+       else
+               return false;
+}
+
+static void spdc_draw_mode0(mxc_spdc_t *fb_data)
+{
+       struct mxcfb_update_data update;
+       struct mxcfb_update_marker_data upd_marker_data;
+       struct fb_var_screeninfo *screeninfo = &fb_data->spdc_fb_var;
+       u32 xres, yres;
+       int ret;
+
+       fb_data->fresh_param.buf_addr.next_buf_phys_addr =
+               fb_data->phys_start;
+
+       fb_data->hw_ready = true;
+       fb_data->hw_initializing = false;
+
+       /* Use unrotated (native) width/height */
+       if ((screeninfo->rotate == FB_ROTATE_CW) ||
+               (screeninfo->rotate == FB_ROTATE_CCW)) {
+               xres = screeninfo->yres;
+               yres = screeninfo->xres;
+       } else {
+               xres = screeninfo->xres;
+               yres = screeninfo->yres;
+       }
+
+       update.update_region.left = 0;
+       update.update_region.width = xres;
+       update.update_region.top = 0;
+       update.update_region.height = yres;
+       update.update_mode = UPDATE_MODE_FULL;
+       update.waveform_mode = fb_data->wv_modes.mode_init;
+       update.update_marker = INIT_UPDATE_MARKER;
+       update.temp = SPDC_DEFAULT_TEMP;
+       update.flags = 0;
+
+       upd_marker_data.update_marker = update.update_marker;
+
+       mxc_spdc_fb_send_update(&update, &fb_data->info);
+
+       /* Block on initial update */
+       ret = mxc_spdc_fb_wait_update_complete(&upd_marker_data,
+               &fb_data->info);
+       if (ret < 0)
+               dev_err(fb_data->dev,
+               "Wait for update complete failed, Err:%d", ret);
+}
+
+static void
+spdc_fb_fw_handler(const struct firmware *fw, void *context)
+{
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)context;
+       struct clk *spdc_parent;
+       unsigned long rounded_parent_rate, spdc_pix_rate,
+                       rounded_pix_clk, target_pix_clk;
+       u8 *wv_file;
+       int ret;
+
+       if (fw == NULL) {
+               /* If default FW file load failed, we give up */
+               if (fb_data->fw_default_load)
+                       return;
+
+               /* Try to load default waveform */
+               fb_data->fw_default_load = true;
+
+               ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+                               fb_data->fw_str, fb_data->dev, GFP_KERNEL,
+                               fb_data, spdc_fb_fw_handler);
+               if (ret) {
+                       dev_err(fb_data->dev,
+                       "Failed to load waveform image with err %d\n", ret);
+                       return;
+               }
+       }
+
+       wv_file = (u8 *)fw->data;
+       memcpy(fb_data->virt_lut_buf, wv_file, fw->size);
+
+       check_waveform((u32 *)wv_file, (u32 *)fb_data->virt_lut_buf,
+                                       fw->size / 4);
+       release_firmware(fw);
+
+       /* Enable clocks to access SPDC regs */
+       clk_enable(fb_data->spdc_clk_axi);
+
+       target_pix_clk = fb_data->cur_mode->vmode->pixclock;
+       /* Enable pix clk for SPDC */
+       clk_enable(fb_data->spdc_clk_pix);
+       rounded_pix_clk = clk_round_rate(fb_data->spdc_clk_pix, target_pix_clk);
+
+       if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) ||
+               (rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) {
+               /* Can't get close enough without changing parent clk */
+               spdc_parent = clk_get_parent(fb_data->spdc_clk_pix);
+               rounded_parent_rate =
+                       clk_round_rate(spdc_parent, target_pix_clk);
+
+               spdc_pix_rate = target_pix_clk;
+               while (spdc_pix_rate < rounded_parent_rate)
+                       spdc_pix_rate *= 2;
+               clk_set_rate(spdc_parent, spdc_pix_rate);
+
+               rounded_pix_clk =
+                       clk_round_rate(fb_data->spdc_clk_pix, target_pix_clk);
+               if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) ||
+               (rounded_pix_clk <= target_pix_clk - target_pix_clk/100)))
+                       /* Still can't get a good clock, provide warning */
+                       dev_err(fb_data->dev,
+                               "Unable to get an accurate SPDC pix clk"
+                               "desired = %lu, actual = %lu\n", target_pix_clk,
+                               rounded_pix_clk);
+       }
+
+       clk_set_rate(fb_data->spdc_clk_pix, rounded_pix_clk);
+
+       if (!spdc_init_sequence(fb_data))
+               return;
+
+       /* display log on picture */
+       spdc_draw_mode0(fb_data);
+}
+
+
+static int spdc_fb_init_hw(struct fb_info *info)
+{
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+       int ret;
+
+       fb_data->fw_default_load = false;
+       /*
+        * Create fw search string based on ID string in selected videomode.
+        * Format is "imx/spdc_[wave_timing].fw: spdc_pvi.fw, spdc_auo.fw"
+        */
+       if (fb_data->cur_mode) {
+               memset(fb_data->fw_str, 0, sizeof(fb_data->fw_str));
+               strcat(fb_data->fw_str, "imx/spdc_");
+               strcat(fb_data->fw_str, fb_data->cur_mode->wave_timing);
+               strcat(fb_data->fw_str, ".fw");
+       } else
+               strcat(fb_data->fw_str, "imx/spdc_pvi.fw");
+
+       ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+                               fb_data->fw_str, fb_data->dev, GFP_KERNEL,
+                               fb_data, spdc_fb_fw_handler);
+       if (ret) {
+               dev_err(fb_data->dev,
+                       "Failed to load waveform image with err %d\n", ret);
+               return ret;
+       }
+
+       return ret;
+}
+
+static int mxc_spdc_partial_refresh(mxc_spdc_t *fb_data, void *buffer)
+{
+       int ret = 0;
+       struct partial_refresh_param *fresh_param = &fb_data->fresh_param;
+
+       if (!fb_data->panel_set.power_ready)
+               spdc_powerup(fb_data);
+
+       if (!fresh_param->blocking) {
+               if (mxc_spdc_device_is_busy(fb_data)) {
+                       dev_err(fb_data->dev, "spdc busy!\n");
+                       return (u32) -1;
+               }
+       } else {
+               while (mxc_spdc_device_is_busy(fb_data)) {
+                       dev_err(fb_data->dev, "Waiting for spdc idle..\n");
+                       msleep(500);
+               }
+       }
+
+       ret = mxc_spdc_partial_refresh_low(fb_data, buffer);
+
+       return ret;
+}
+
+static int mxc_operaton_update(mxc_spdc_t *fb_data)
+{
+       int ret = 0;
+       struct partial_refresh_param *fresh_param = &fb_data->fresh_param;
+       u32 operation_mode = fb_data->operation_mode;
+
+       if (!fb_data->panel_set.power_ready)
+               spdc_powerup(fb_data);
+
+       if (operation_mode != SPDC_SW_TCON_RESET) {
+               if (!fresh_param->blocking) {
+                       if (mxc_spdc_device_is_busy(fb_data)) {
+                               dev_err(fb_data->dev,  "spdc busy\n");
+                               return (u32) -1;
+                       }
+               } else {
+                       while (mxc_spdc_device_is_busy(fb_data)) {
+                               dev_err(fb_data->dev, "Waiting spdc idle...\n");
+                               msleep(500);
+                       }
+               }
+       } else
+               operation_mode = SPDC_SW_TCON_RESET_SET;
+
+       /* don't add to queue list */
+       mutex_lock(&fb_data->queue_mutex);
+       __raw_writel(operation_mode, fb_data->hwp + SPDC_OPERATE);
+       mutex_unlock(&fb_data->queue_mutex);
+
+       if (operation_mode == SPDC_SW_TCON_RESET_SET) {
+               dev_dbg(fb_data->dev, "reinit hw\n");
+               mdelay(500);
+
+               fb_data->hw_ready = false;
+               fb_data->operation_mode = SPDC_NO_OPERATION;
+               ret = spdc_fb_init_hw(&fb_data->info);
+               if (ret && !fb_data->hw_ready)
+                       dev_err(fb_data->dev, "Failed to init HW!\n");
+       }
+
+       return ret;
+}
+
+static int mxc_spdc_refresh_display(mxc_spdc_t *fb_data)
+{
+       struct partial_refresh_param *fresh_param = &fb_data->fresh_param;
+       u32 operation_mode = fb_data->operation_mode;
+       int ret = 0;
+
+       fresh_param->update_region.left = 0;
+       fresh_param->update_region.top = 0;
+       fresh_param->update_region.width = fb_data->spdc_fb_var.xres;
+       fresh_param->update_region.height = fb_data->spdc_fb_var.yres;
+       fresh_param->stride = (fb_data->spdc_fb_var.xres *
+                               fb_data->spdc_fb_var.bits_per_pixel) >> 3;
+
+       if (operation_mode && operation_mode < SPDC_FULL_REFRESH)
+               ret = mxc_operaton_update(fb_data);
+       else
+               ret = mxc_spdc_partial_refresh(fb_data, NULL);
+
+       return ret;
+}
+
+static void mxc_spdc_find_match_mode(mxc_spdc_t *fb_data)
+{
+       struct imx_spdc_fb_mode *spdc_mode =
+               &fb_data->pdata->spdc_mode[0];
+       const struct mxc_spdc_resolution_map_para *spdc_res_map;
+       u32 i = 0;
+       u32 j = 0;
+       u32 default_mode = 0xFF;
+
+       if (fb_data->panel_set.rgbw_mode_enable)
+               spdc_res_map = &spdc_rgbw_res_map[0];
+       else
+               spdc_res_map = &spdc_gray_res_map[0];
+
+       while (spdc_mode != NULL) {
+               while (spdc_res_map[j].resolution != 0xFFFF) {
+                       if (spdc_mode->vmode->xres == spdc_res_map[j].res_x
+                       && spdc_mode->vmode->yres == spdc_res_map[j].res_y) {
+                               fb_data->panel_set.resolution =
+                                       spdc_res_map[j].resolution;
+                               default_mode = i;
+                               break;
+                       }
+                       j++;
+               }
+
+               if (default_mode != 0xFF)
+                       break;
+               j = 0;
+               i++;
+               spdc_mode = &fb_data->pdata->spdc_mode[i];
+       }
+
+       fb_data->cur_mode = spdc_mode;
+}
+
+static int mxc_spdc_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+       unsigned long start = vma->vm_start;
+       unsigned long size = vma->vm_end - vma->vm_start;
+       unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+       unsigned long page, pos;
+
+       if (offset + size > info->fix.smem_len)
+               return -EINVAL;
+
+       pos = (unsigned long)info->fix.smem_start + offset;
+
+       /* make buffers bufferable */
+       vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+       vma->vm_flags |= VM_RESERVED | VM_IO;
+
+       while (size > 0) {
+               page = pos;
+               if (io_remap_pfn_range(vma, start, page >> PAGE_SHIFT,
+                                       PAGE_SIZE, vma->vm_page_prot))
+                       return -EAGAIN;
+
+               start += PAGE_SIZE;
+               pos += PAGE_SIZE;
+               if (size > PAGE_SIZE)
+                       size -= PAGE_SIZE;
+               else
+                       size = 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 mxc_spdc_fb_setcolreg(u_int regno, u_int red, u_int green,
+                               u_int blue, u_int transp, struct fb_info *info)
+{
+       if (regno >= 256) /* no. of hw registers */
+               return 1;
+
+       /* grayscale works only partially under directcolor */
+       if (info->var.grayscale) {
+               /* grayscale = 0.30*R + 0.59*G + 0.11*B */
+               red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8;
+       }
+
+#define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16)
+       switch (info->fix.visual) {
+       case FB_VISUAL_TRUECOLOR:
+       case FB_VISUAL_PSEUDOCOLOR:
+               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);
+               break;
+       case FB_VISUAL_DIRECTCOLOR:
+               red = CNVT_TOHW(red, 8); /* expect 8 bit DAC */
+               green = CNVT_TOHW(green, 8);
+               blue = CNVT_TOHW(blue, 8);
+               /* hey, there is bug in transp handling... */
+               transp = CNVT_TOHW(transp, 8);
+               break;
+       }
+#undef CNVT_TOHW
+       /* Truecolor has hardware independent palette */
+       if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
+               if (regno >= 16)
+                       return 1;
+
+               ((u32 *) (info->pseudo_palette))[regno] =
+               (red << info->var.red.offset) |
+               (green << info->var.green.offset) |
+               (blue << info->var.blue.offset) |
+               (transp << info->var.transp.offset);
+       }
+
+       return 0;
+}
+
+void mxc_spdc_fb_flush_updates(mxc_spdc_t *fb_data)
+{
+       int ret;
+
+       /* Grab queue lock to prevent any new updates from being submitted */
+       mutex_lock(&fb_data->queue_mutex);
+
+       /*
+        * 3 places to check for updates that are active or pending:
+        *   1) Updates in the pending list
+        *   2) Update buffers in use (e.g., PxP processing)
+        *   3) Active updates to panel - We can key off of SPDC
+        *      power state to know if we have active updates.
+        */
+       if (!list_empty(&fb_data->upd_pending_list) ||
+               !is_free_list_full(fb_data) ||
+               (fb_data->updates_active == true)) {
+               /* Initialize event signalling updates are done */
+               init_completion(&fb_data->updates_done);
+               fb_data->waiting_for_idle = true;
+
+               mutex_unlock(&fb_data->queue_mutex);
+               /* Wait for any currently active updates to complete */
+               ret = wait_for_completion_timeout(&fb_data->updates_done,
+                                               msecs_to_jiffies(8000));
+               if (!ret)
+                       dev_err(fb_data->dev,
+                               "Flush updates timeout! ret = 0x%x\n", ret);
+
+               mutex_lock(&fb_data->queue_mutex);
+               fb_data->waiting_for_idle = false;
+       }
+
+       mutex_unlock(&fb_data->queue_mutex);
+}
+
+static int mxc_spdc_fb_setcmap(struct fb_cmap *cmap, struct fb_info *info)
+{
+       int count, index, r;
+       u16 *red, *green, *blue, *transp;
+       u16 trans = 0xffff;
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+       int i;
+
+       dev_dbg(fb_data->dev, "setcmap\n");
+
+       if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) {
+               /* Only support an 8-bit, 256 entry lookup */
+               if (cmap->len != 256)
+                       return 1;
+
+               mxc_spdc_fb_flush_updates(fb_data);
+
+               mutex_lock(&fb_data->pxp_mutex);
+               /*
+                * Store colormap in pxp_conf structure for later transmit
+                * to PxP during update process to convert gray pixels.
+                *
+                * Since red=blue=green for pseudocolor visuals, we can
+                * just use red values.
+                */
+               for (i = 0; i < 256; i++)
+                       fb_data->pxp_conf.proc_data.lut_map[i] =
+                                       cmap->red[i] & 0xFF;
+
+               fb_data->pxp_conf.proc_data.lut_map_updated = true;
+
+               mutex_unlock(&fb_data->pxp_mutex);
+       } else {
+               red = cmap->red;
+               green   = cmap->green;
+               blue    = cmap->blue;
+               transp  = cmap->transp;
+               index   = cmap->start;
+
+               for (count = 0; count < cmap->len; count++) {
+                       if (transp)
+                               trans = *transp++;
+                       r = mxc_spdc_fb_setcolreg(index++, *red++,
+                               *green++, *blue++, trans, info);
+                       if (r != 0)
+                               return r;
+               }
+       }
+
+       return 0;
+}
+
+static void adjust_coordinates(u32 xres, u32 yres, u32 rotation,
+       struct mxcfb_rect *update_region, struct mxcfb_rect *adj_update_region)
+{
+       u32 temp;
+
+       /* If adj_update_region == NULL, pass result back in update_region */
+       /* If adj_update_region == valid, use it to pass back result */
+       if (adj_update_region)
+               switch (rotation) {
+               case FB_ROTATE_UR:
+                       adj_update_region->top = update_region->top;
+                       adj_update_region->left = update_region->left;
+                       adj_update_region->width = update_region->width;
+                       adj_update_region->height = update_region->height;
+                       break;
+               case FB_ROTATE_CW:
+                       adj_update_region->top = update_region->left;
+                       adj_update_region->left = yres -
+                               (update_region->top + update_region->height);
+                       adj_update_region->width = update_region->height;
+                       adj_update_region->height = update_region->width;
+                       break;
+               case FB_ROTATE_UD:
+                       adj_update_region->width = update_region->width;
+                       adj_update_region->height = update_region->height;
+                       adj_update_region->top = yres -
+                               (update_region->top + update_region->height);
+                       adj_update_region->left = xres -
+                               (update_region->left + update_region->width);
+                       break;
+               case FB_ROTATE_CCW:
+                       adj_update_region->left = update_region->top;
+                       adj_update_region->top = xres -
+                               (update_region->left + update_region->width);
+                       adj_update_region->width = update_region->height;
+                       adj_update_region->height = update_region->width;
+                       break;
+               }
+       else
+               switch (rotation) {
+               case FB_ROTATE_UR:
+                       /* No adjustment needed */
+                       break;
+               case FB_ROTATE_CW:
+                       temp = update_region->top;
+                       update_region->top = update_region->left;
+                       update_region->left = yres -
+                               (temp + update_region->height);
+                       temp = update_region->width;
+                       update_region->width = update_region->height;
+                       update_region->height = temp;
+                       break;
+               case FB_ROTATE_UD:
+                       update_region->top = yres -
+                               (update_region->top + update_region->height);
+                       update_region->left = xres -
+                               (update_region->left + update_region->width);
+                       break;
+               case FB_ROTATE_CCW:
+                       temp = update_region->left;
+                       update_region->left = update_region->top;
+                       update_region->top = xres -
+                               (temp + update_region->width);
+                       temp = update_region->width;
+                       update_region->width = update_region->height;
+                       update_region->height = temp;
+                       break;
+               }
+}
+
+static int mxc_spdc_fb_set_fix(struct fb_info *info)
+{
+       struct fb_fix_screeninfo *fix = &info->fix;
+       struct fb_var_screeninfo *var = &info->var;
+
+       fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
+
+       fix->type = FB_TYPE_PACKED_PIXELS;
+       fix->accel = FB_ACCEL_NONE;
+       if (var->grayscale)
+               fix->visual = FB_VISUAL_STATIC_PSEUDOCOLOR;
+       else
+               fix->visual = FB_VISUAL_TRUECOLOR;
+               fix->xpanstep = 1;
+       fix->ypanstep = 1;
+
+       return 0;
+}
+
+static int mxc_spdc_fb_set_par(struct fb_info *info)
+{
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+       struct fb_var_screeninfo *screeninfo = &fb_data->info.var;
+       struct imx_spdc_fb_mode *spdc_modes = fb_data->pdata->spdc_mode;
+       struct pxp_config_data *pxp_conf = &fb_data->pxp_conf;
+       struct pxp_proc_data *proc_data = &pxp_conf->proc_data;
+       int i, ret;
+       __u32 xoffset_old, yoffset_old;
+
+       /*
+        * Can't change the FB parameters until current updates have completed.
+        * This function returns when all active updates are done.
+        */
+       mxc_spdc_fb_flush_updates(fb_data);
+
+       mutex_lock(&fb_data->queue_mutex);
+       /*
+        * Set all screeninfo except for xoffset/yoffset
+        * Subsequent call to pan_display will handle those.
+        */
+       xoffset_old = fb_data->spdc_fb_var.xoffset;
+       yoffset_old = fb_data->spdc_fb_var.yoffset;
+       fb_data->spdc_fb_var = *screeninfo;
+       fb_data->spdc_fb_var.xoffset = xoffset_old;
+       fb_data->spdc_fb_var.yoffset = yoffset_old;
+       mutex_unlock(&fb_data->queue_mutex);
+
+       mutex_lock(&fb_data->pxp_mutex);
+
+       /*
+        * Update PxP config data (used to process FB regions for updates)
+        * based on FB info and processing tasks required
+        */
+       /* Initialize non-channel-specific PxP parameters */
+       proc_data->drect.left = proc_data->srect.left = 0;
+       proc_data->drect.top = proc_data->srect.top = 0;
+       proc_data->drect.width = proc_data->srect.width = screeninfo->xres;
+       proc_data->drect.height = proc_data->srect.height = screeninfo->yres;
+       proc_data->scaling = 0;
+       proc_data->hflip = 0;
+       proc_data->vflip = 0;
+       proc_data->rotate = screeninfo->rotate;
+       proc_data->bgcolor = 0;
+       proc_data->overlay_state = 0;
+       proc_data->lut_transform = PXP_LUT_NONE;
+
+       /*
+        * configure S0 channel parameters
+        * Parameters should match FB format/width/height
+        */
+       if (screeninfo->grayscale)
+               pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_GY04;
+       else {
+               switch (screeninfo->bits_per_pixel) {
+               case 16:
+                       pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565;
+                       break;
+               case 24:
+                       pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB24;
+                       break;
+               case 32:
+                       pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB32;
+                       break;
+               default:
+                       pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565;
+                       break;
+               }
+       }
+       pxp_conf->s0_param.width = screeninfo->xres_virtual;
+       pxp_conf->s0_param.height = screeninfo->yres;
+       pxp_conf->s0_param.color_key = -1;
+       pxp_conf->s0_param.color_key_enable = false;
+
+       /*
+        * Initialize Output channel parameters
+        * Output is Y-only greyscale
+        * Output width/height will vary based on update region size
+        */
+       pxp_conf->out_param.width = screeninfo->xres;
+       pxp_conf->out_param.height = screeninfo->yres;
+       pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GY04;
+
+       mutex_unlock(&fb_data->pxp_mutex);
+
+       /* active new config, If HW not yet initialized,
+        * check to see if we are being sent
+        * an initialization request.
+        */
+       if (!fb_data->hw_ready) {
+               struct fb_videomode mode;
+               bool found_match = false;
+               u32 xres_temp;
+
+               fb_var_to_videomode(&mode, screeninfo);
+
+               /* When comparing requested fb mode,
+                * we need to use unrotated dimensions
+                */
+               if ((screeninfo->rotate == FB_ROTATE_CW) ||
+                       (screeninfo->rotate == FB_ROTATE_CCW)) {
+                       xres_temp = mode.xres;
+                       mode.xres = mode.yres;
+                       mode.yres = xres_temp;
+               }
+
+               /* Match videomode against spdc modes */
+               for (i = 0; i < fb_data->pdata->num_modes; i++) {
+                       if (!fb_mode_is_equal(spdc_modes[i].vmode, &mode))
+                               continue;
+                       fb_data->cur_mode = &spdc_modes[i];
+                       found_match = true;
+                       break;
+               }
+
+               if (!found_match) {
+                       dev_err(fb_data->dev,
+                               "Failed to match requested video mode\n");
+                       return EINVAL;
+               }
+
+               /* Initialize SPDC settings and init panel */
+               ret =
+               spdc_fb_init_hw((struct fb_info *)fb_data);
+               if (ret) {
+                       dev_err(fb_data->dev,
+                               "Failed to load panel waveform data\n");
+                       return ret;
+               }
+       }
+
+       mxc_spdc_fb_set_fix(info);
+
+       return 0;
+}
+
+static int
+mxc_spdc_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+
+       if (!var->xres)
+               var->xres = 1;
+       if (!var->yres)
+               var->yres = 1;
+
+       if (var->xres_virtual < var->xoffset + var->xres)
+               var->xres_virtual = var->xoffset + var->xres;
+       if (var->yres_virtual < var->yoffset + var->yres)
+               var->yres_virtual = var->yoffset + var->yres;
+
+       if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) &&
+               (var->bits_per_pixel != 16) && (var->bits_per_pixel != 8) &&
+               (var->bits_per_pixel != 4))
+               var->bits_per_pixel = SPDC_DEFAULT_BPP;
+
+       switch (var->bits_per_pixel) {
+       case 4:
+               var->red.offset = 0;
+               var->red.length = var->bits_per_pixel;
+               var->green = var->red;
+               var->blue = var->red;
+               var->transp.offset = 0;
+               var->transp.length = 0;
+               break;
+       case 8:
+               if (var->grayscale != 0) {
+                       var->red.length = 8;
+                       var->red.offset = 0;
+                       var->red.msb_right = 0;
+
+                       var->green = var->red;
+                       var->blue = var->red;
+                       var->transp.length = 0;
+                       var->transp.offset = 0;
+                       var->transp.msb_right = 0;
+               } else {
+                       var->red.length = 3;
+                       var->red.offset = 5;
+                       var->red.msb_right = 0;
+
+                       var->green.length = 3;
+                       var->green.offset = 2;
+                       var->green.msb_right = 0;
+
+                       var->blue.length = 2;
+                       var->blue.offset = 0;
+                       var->blue.msb_right = 0;
+
+                       var->transp.length = 0;
+                       var->transp.offset = 0;
+                       var->transp.msb_right = 0;
+               }
+               break;
+       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;
+       }
+
+       switch (var->rotate) {
+       case FB_ROTATE_UR:
+       case FB_ROTATE_UD:
+               var->xres = fb_data->native_width;
+               var->yres = fb_data->native_height;
+               break;
+       case FB_ROTATE_CW:
+       case FB_ROTATE_CCW:
+               var->xres = fb_data->native_height;
+               var->yres = fb_data->native_width;
+               break;
+       default:
+               /* Invalid rotation value */
+               var->rotate = 0;
+               dev_dbg(fb_data->dev, "Invalid rotation request\n");
+               return -EINVAL;
+       }
+
+       var->xres_virtual = ALIGN(var->xres, 32);
+       var->yres_virtual = ALIGN(var->yres, 128) * fb_data->num_screens;
+
+       var->height = -1;
+       var->width = -1;
+
+       return 0;
+}
+
+void mxc_spdc_fb_set_waveform_modes(struct mxcfb_waveform_modes *modes,
+       struct fb_info *info)
+{
+       mxc_spdc_t *fb_data = info ?
+               (mxc_spdc_t *)info:g_fb_data;
+
+       mutex_lock(&fb_data->queue_mutex);
+
+       memcpy(&fb_data->wv_modes, modes, sizeof(struct mxcfb_waveform_modes));
+
+       mutex_unlock(&fb_data->queue_mutex);
+}
+EXPORT_SYMBOL(mxc_spdc_fb_set_waveform_modes);
+
+/* To stick with non-fractional degrees for the sake
+ *  of API consistency with EPDC.
+ */
+int mxc_spdc_fb_set_temperature(int temperature, struct fb_info *info)
+{
+       mxc_spdc_t *fb_data = info ?
+               (mxc_spdc_t *)info:g_fb_data;
+       s8 temper = (s8)(temperature & 0xFF) << 1;
+
+       mutex_lock(&fb_data->queue_mutex);
+
+       if (temper > -110 && temper < 200)
+               __raw_writel(temper, fb_data->hwp + SPDC_TEMP_INFO);
+       else
+               __raw_writel(SPDC_DEFAULT_TEMP, fb_data->hwp + SPDC_TEMP_INFO);
+
+       mutex_unlock(&fb_data->queue_mutex);
+
+       return 0;
+}
+EXPORT_SYMBOL(mxc_spdc_fb_set_temperature);
+
+
+int mxc_spdc_fb_set_auto_update(u32 auto_mode, struct fb_info *info)
+{
+       mxc_spdc_t *fb_data = info ?
+               (mxc_spdc_t *)info:g_fb_data;
+
+       dev_dbg(fb_data->dev, "Setting auto update mode to %d\n", auto_mode);
+
+       if ((auto_mode == AUTO_UPDATE_MODE_AUTOMATIC_MODE)
+               || (auto_mode == AUTO_UPDATE_MODE_REGION_MODE))
+               fb_data->auto_mode = auto_mode;
+       else {
+               dev_err(fb_data->dev, "Invalid auto update mode parameter.\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(mxc_spdc_fb_set_auto_update);
+
+
+int mxc_spdc_fb_set_upd_scheme(u32 upd_scheme, struct fb_info *info)
+{
+       mxc_spdc_t *fb_data = info ?
+               (mxc_spdc_t *)info:g_fb_data;
+
+       dev_dbg(fb_data->dev, "Setting optimization level to %d\n", upd_scheme);
+
+       /*
+        * Can't change the scheme until current updates have completed.
+        * This function returns when all active updates are done.
+        */
+       mxc_spdc_fb_flush_updates(fb_data);
+
+       if ((upd_scheme == UPDATE_SCHEME_SNAPSHOT)
+               || (upd_scheme == UPDATE_SCHEME_QUEUE)
+               || (upd_scheme == UPDATE_SCHEME_QUEUE_AND_MERGE))
+               fb_data->upd_scheme = upd_scheme;
+       else {
+               dev_err(fb_data->dev, "Invalid update scheme specified.\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(mxc_spdc_fb_set_upd_scheme);
+
+/* Callback function triggered after PxP receives an EOF interrupt */
+static void pxp_dma_done(void *arg)
+{
+       struct pxp_tx_desc *tx_desc = to_tx_desc(arg);
+       struct dma_chan *chan = tx_desc->txd.chan;
+       struct pxp_channel *pxp_chan = to_pxp_channel(chan);
+       mxc_spdc_t *fb_data = pxp_chan->client;
+
+       /* This call will signal wait_for_completion_timeout()
+        * in send_buffer_to_pxp
+        */
+       complete(&fb_data->pxp_tx_cmpl);
+}
+
+static bool chan_filter(struct dma_chan *chan, void *arg)
+{
+       if (imx_dma_is_pxp(chan))
+               return true;
+       else
+               return false;
+}
+
+/* Function to request PXP DMA channel */
+static int pxp_chan_init(mxc_spdc_t *fb_data)
+{
+       dma_cap_mask_t mask;
+       struct dma_chan *chan;
+
+       /*
+        * Request a free channel
+        */
+       dma_cap_zero(mask);
+       dma_cap_set(DMA_SLAVE, mask);
+       dma_cap_set(DMA_PRIVATE, mask);
+       chan = dma_request_channel(mask, chan_filter, NULL);
+       if (!chan) {
+               dev_err(fb_data->dev, "Unsuccessfully received channel!!!!\n");
+               return -EBUSY;
+       }
+
+       fb_data->pxp_chan = to_pxp_channel(chan);
+       fb_data->pxp_chan->client = fb_data;
+
+       init_completion(&fb_data->pxp_tx_cmpl);
+
+       return 0;
+}
+
+/*
+ * Function to call PxP DMA driver and send our latest FB update region
+ * through the PxP and out to an intermediate buffer.
+ * Note: This is a blocking call, so upon return the PxP tx should be complete.
+ */
+static int pxp_process_update(mxc_spdc_t *fb_data,
+                         u32 src_width, u32 src_height,
+                         struct mxcfb_rect *update_region)
+{
+       dma_cookie_t cookie;
+       struct scatterlist *sg = fb_data->sg;
+       struct dma_chan *dma_chan;
+       struct pxp_tx_desc *desc;
+       struct dma_async_tx_descriptor *txd;
+       struct pxp_config_data *pxp_conf = &fb_data->pxp_conf;
+       struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data;
+       int i, ret;
+       int length;
+
+       /* First, check to see that we have acquired a PxP Channel object */
+       if (fb_data->pxp_chan == NULL) {
+               /*
+                * PxP Channel has not yet been created and initialized,
+                * so let's go ahead and try
+                */
+               ret = pxp_chan_init(fb_data);
+               if (ret) {
+                       /*
+                        * PxP channel init failed, and we can't use the
+                        * PxP until the PxP DMA driver has loaded, so we abort
+                        */
+                       dev_err(fb_data->dev, "PxP chan init failed\n");
+                       return -ENODEV;
+               }
+       }
+
+       /*
+        * Init completion, so that we
+        * can be properly informed of the completion
+        * of the PxP task when it is done.
+        */
+       init_completion(&fb_data->pxp_tx_cmpl);
+
+       dma_chan = &fb_data->pxp_chan->dma_chan;
+
+       txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2,
+                                                DMA_TO_DEVICE,
+                                                DMA_PREP_INTERRUPT);
+       if (!txd) {
+               dev_err(fb_data->info.device,
+                       "Error preparing a DMA transaction descriptor.\n");
+               return -EIO;
+       }
+
+       txd->callback_param = txd;
+       txd->callback = pxp_dma_done;
+
+       /*
+        * Configure PxP for processing of new update region
+        * The rest of our config params were set up in
+        * probe() and should not need to be changed.
+        */
+       pxp_conf->s0_param.width = src_width;
+       pxp_conf->s0_param.height = src_height;
+       proc_data->srect.top = update_region->top;
+       proc_data->srect.left = update_region->left;
+       proc_data->srect.width = update_region->width;
+       proc_data->srect.height = update_region->height;
+
+       /*
+        * Because only YUV/YCbCr image can be scaled, configure
+        * drect equivalent to srect, as such do not perform scaling.
+        */
+       proc_data->drect.top = 0;
+       proc_data->drect.left = 0;
+       proc_data->drect.width = proc_data->srect.width;
+       proc_data->drect.height = proc_data->srect.height;
+
+       /* PXP expects rotation in terms of degrees */
+       proc_data->rotate = fb_data->spdc_fb_var.rotate * 90;
+       if (proc_data->rotate > 270)
+               proc_data->rotate = 0;
+
+       pxp_conf->out_param.width = update_region->width;
+       pxp_conf->out_param.height = update_region->height;
+
+       if ((proc_data->rotate == 90) || (proc_data->rotate == 270))
+               pxp_conf->out_param.stride = update_region->height;
+       else
+               pxp_conf->out_param.stride = update_region->width;
+
+       desc = to_tx_desc(txd);
+       length = desc->len;
+       for (i = 0; i < length; i++) {
+               if (i == 0) {/* S0 */
+                       memcpy(&desc->proc_data, proc_data,
+                               sizeof(struct pxp_proc_data));
+                       pxp_conf->s0_param.paddr = sg_dma_address(&sg[0]);
+                       memcpy(&desc->layer_param.s0_param, &pxp_conf->s0_param,
+                               sizeof(struct pxp_layer_param));
+               } else if (i == 1) {
+                       pxp_conf->out_param.paddr = sg_dma_address(&sg[1]);
+                       memcpy(&desc->layer_param.out_param,
+                                       &pxp_conf->out_param,
+                                       sizeof(struct pxp_layer_param));
+               }
+               /* TODO: OverLay */
+
+               desc = desc->next;
+       }
+
+       /* Submitting our TX starts the PxP processing task */
+       cookie = txd->tx_submit(txd);
+       if (cookie < 0) {
+               dev_err(fb_data->info.device, "Error sending FB through PxP\n");
+               return -EIO;
+       }
+
+       fb_data->txd = txd;
+
+       /* trigger ePxP */
+       dma_async_issue_pending(dma_chan);
+
+       return 0;
+}
+
+static int pxp_complete_update(mxc_spdc_t *fb_data, u32 *hist_stat)
+{
+       int ret;
+       /*
+        * Wait for completion event, which will be set
+        * through our TX callback function.
+        */
+       ret = wait_for_completion_timeout(&fb_data->pxp_tx_cmpl, HZ / 10);
+       if (ret <= 0) {
+               dev_info(fb_data->info.device,
+                        "PxP operation failed due to %s\n",
+                        ret < 0 ? "user interrupt" : "timeout");
+               dma_release_channel(&fb_data->pxp_chan->dma_chan);
+               fb_data->pxp_chan = NULL;
+               return ret ? : -ETIMEDOUT;
+       }
+
+       if ((fb_data->pxp_conf.proc_data.lut_transform & EPDC_FLAG_USE_CMAP) &&
+               fb_data->pxp_conf.proc_data.lut_map_updated)
+               fb_data->pxp_conf.proc_data.lut_map_updated = false;
+
+       *hist_stat = to_tx_desc(fb_data->txd)->hist_status;
+       dma_release_channel(&fb_data->pxp_chan->dma_chan);
+       fb_data->pxp_chan = NULL;
+
+       dev_dbg(fb_data->dev, "TX completed\n");
+
+       return 0;
+}
+
+static void copy_to_next_buffer(mxc_spdc_t *fb_data,
+       struct update_data_list *upd_data_list)
+{
+       struct mxcfb_update_data *upd_data =
+               &upd_data_list->update_desc->upd_data;
+       unsigned char *temp_buf_ptr = fb_data->virt_addr_copybuf;
+       unsigned char *dst_ptr = upd_data_list->virt_addr;
+       struct mxcfb_rect adj_update_region;
+       int dst_stride, left_offs, line_width;
+       int i;
+
+       switch (fb_data->spdc_fb_var.rotate) {
+       case FB_ROTATE_UR:
+               adj_update_region.top = upd_data->update_region.top;
+               adj_update_region.left = upd_data->update_region.left;
+               adj_update_region.width = upd_data->update_region.width;
+               adj_update_region.height = upd_data->update_region.height;
+               dst_stride = fb_data->spdc_fb_var.xres_virtual / 2;
+               break;
+       case FB_ROTATE_CW:
+               adj_update_region.top = upd_data->update_region.left;
+               adj_update_region.left = fb_data->spdc_fb_var.yres -
+                               (upd_data->update_region.top +
+                               upd_data->update_region.height);
+               adj_update_region.width = upd_data->update_region.height;
+               adj_update_region.height = upd_data->update_region.width;
+               dst_stride = fb_data->spdc_fb_var.yres / 2;
+               break;
+       case FB_ROTATE_UD:
+               adj_update_region.width = upd_data->update_region.width;
+               adj_update_region.height = upd_data->update_region.height;
+               adj_update_region.top = fb_data->spdc_fb_var.yres -
+               (upd_data->update_region.top + upd_data->update_region.height);
+               adj_update_region.left = fb_data->spdc_fb_var.xres -
+                               (upd_data->update_region.left +
+                               upd_data->update_region.width);
+               dst_stride = fb_data->spdc_fb_var.xres_virtual / 2;
+               break;
+       case FB_ROTATE_CCW:
+               adj_update_region.left = upd_data->update_region.top;
+               adj_update_region.top = fb_data->spdc_fb_var.xres -
+                               (upd_data->update_region.left +
+                               upd_data->update_region.width);
+               adj_update_region.width = upd_data->update_region.height;
+               adj_update_region.height = upd_data->update_region.width;
+               dst_stride = fb_data->spdc_fb_var.yres / 2;
+               break;
+       }
+
+       /* pxp output Y4 data.
+        * Copy the raw data to related region in next buffer.
+        */
+       left_offs = adj_update_region.left / 2;
+       line_width = adj_update_region.width / 2;
+
+       dst_ptr += (adj_update_region.top * dst_stride + left_offs);
+       for (i = 0; i < adj_update_region.height; i++) {
+               /* Copy the full line */
+               memcpy(dst_ptr, temp_buf_ptr, line_width);
+
+               dst_ptr += dst_stride;
+               temp_buf_ptr += line_width;
+       }
+}
+
+static int spdc_process_update(struct update_data_list *upd_data_list,
+                                  mxc_spdc_t *fb_data)
+{
+       /* Region of src buffer for update */
+       struct mxcfb_rect *src_upd_region;
+       struct mxcfb_rect pxp_upd_region;
+       struct update_desc_list *upd_desc_list = upd_data_list->update_desc;
+       u32 src_width, src_height;
+       u32 offset_from_4, bytes_per_pixel;
+       u32 post_rotation_xcoord, post_rotation_ycoord, width_pxp_blocks;
+       u32 pxp_input_offs, pxp_output_offs, pxp_output_shift;
+       bool input_unaligned = false;
+       u32 hist_stat = 0;
+       bool use_temp_buf = false;
+       int ret;
+
+       /*
+        * Are we using FB or an alternate (overlay)
+        * buffer for source of update?
+        */
+       if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) {
+               src_width = upd_desc_list->upd_data.alt_buffer_data.width;
+               src_height = upd_desc_list->upd_data.alt_buffer_data.height;
+               src_upd_region =
+               &upd_desc_list->upd_data.alt_buffer_data.alt_update_region;
+       } else {
+               src_width = fb_data->spdc_fb_var.xres_virtual;
+               src_height = fb_data->spdc_fb_var.yres;
+               src_upd_region = &upd_desc_list->upd_data.update_region;
+       }
+
+       if (!(src_upd_region->width == fb_data->spdc_fb_var.xres_virtual &&
+               fb_data->spdc_fb_var.rotate == FB_ROTATE_UR))
+               use_temp_buf = true;
+
+       bytes_per_pixel = fb_data->spdc_fb_var.bits_per_pixel / 8;
+
+       /* Grab pxp_mutex here so that we protect access
+        * to copybuf in addition to the PxP structures */
+       mutex_lock(&fb_data->pxp_mutex);
+
+       offset_from_4 = src_upd_region->left & 0x3;
+       input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ?
+                               true : false;
+
+       if (input_unaligned) {
+               /* Leave a gap between PxP input addr
+                * and update region pixels
+                */
+               pxp_input_offs =
+                       (src_upd_region->top * src_width + src_upd_region->left)
+                       * bytes_per_pixel & 0xFFFFFFFC;
+               /* Update region left changes to reflect
+                * relative position to input ptr
+                */
+               pxp_upd_region.left = (offset_from_4 * bytes_per_pixel % 4)
+                                       / bytes_per_pixel;
+       } else {
+               pxp_input_offs =
+                       (src_upd_region->top * src_width + src_upd_region->left)
+                       * bytes_per_pixel;
+               pxp_upd_region.left = 0;
+       }
+       pxp_upd_region.top = 0;
+
+       /* Update region dimensions to meet 8x8 pixel requirement */
+       if (fb_data->spdc_fb_var.rotate == 0) {
+               pxp_upd_region.width = ALIGN(src_upd_region->width, 8);
+               pxp_upd_region.height = ALIGN(src_upd_region->height, 8);
+       } else {
+               pxp_upd_region.width =
+                       ALIGN(src_upd_region->width + pxp_upd_region.left, 8);
+               pxp_upd_region.height = ALIGN(src_upd_region->height, 8);
+       }
+
+       switch (fb_data->spdc_fb_var.rotate) {
+       case FB_ROTATE_UR:
+       default:
+               post_rotation_xcoord = pxp_upd_region.left;
+               post_rotation_ycoord = pxp_upd_region.top;
+               width_pxp_blocks = pxp_upd_region.width;
+               break;
+       case FB_ROTATE_CW:
+               width_pxp_blocks = pxp_upd_region.height;
+               post_rotation_xcoord = width_pxp_blocks -
+                                       src_upd_region->height;
+               post_rotation_ycoord = pxp_upd_region.left;
+               break;
+       case FB_ROTATE_UD:
+               width_pxp_blocks = pxp_upd_region.width;
+               post_rotation_xcoord = width_pxp_blocks -
+                       src_upd_region->width - pxp_upd_region.left;
+               post_rotation_ycoord = pxp_upd_region.height -
+                       src_upd_region->height - pxp_upd_region.top;
+               break;
+       case FB_ROTATE_CCW:
+               width_pxp_blocks = pxp_upd_region.height;
+               post_rotation_xcoord = pxp_upd_region.top;
+               post_rotation_ycoord = pxp_upd_region.width -
+                       src_upd_region->width - pxp_upd_region.left;
+               break;
+       }
+
+       /* Update region start coord to force PxP to
+        * process full 8x8 regions
+        */
+       pxp_upd_region.top &= ~0x7;
+       pxp_upd_region.left &= ~0x7;
+
+       pxp_output_shift = ALIGN(post_rotation_xcoord, 8)
+               - post_rotation_xcoord;
+       pxp_output_offs = post_rotation_ycoord * width_pxp_blocks
+               + pxp_output_shift;
+       upd_desc_list->spdc_offs = ALIGN(pxp_output_offs, 8);
+
+       /* Source address either comes from alternate buffer
+          provided in update data, or from the framebuffer. */
+       if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER)
+               sg_dma_address(&fb_data->sg[0]) =
+                       upd_desc_list->upd_data.alt_buffer_data.phys_addr
+                               + pxp_input_offs;
+       else {
+               sg_dma_address(&fb_data->sg[0]) =
+                       fb_data->info.fix.smem_start + fb_data->fb_offset
+                       + pxp_input_offs;
+               sg_set_page(&fb_data->sg[0],
+                       virt_to_page(fb_data->info.screen_base),
+                       fb_data->info.fix.smem_len,
+                       offset_in_page(fb_data->info.screen_base));
+       }
+
+       /* Update sg[1] to point to output of PxP proc task */
+       if (!use_temp_buf) {
+               sg_dma_address(&fb_data->sg[1]) = upd_data_list->phys_addr;
+               sg_set_page(&fb_data->sg[1],
+                               virt_to_page(upd_data_list->virt_addr),
+               fb_data->max_pix_size,
+               offset_in_page(upd_data_list->virt_addr));
+       } else {
+               sg_dma_address(&fb_data->sg[1]) = fb_data->phys_addr_copybuf;
+               sg_set_page(&fb_data->sg[1],
+                       virt_to_page(fb_data->virt_addr_copybuf),
+               fb_data->max_pix_size,
+               offset_in_page(fb_data->virt_addr_copybuf));
+       }
+
+       /*
+        * Set PxP LUT transform type based on update flags.
+        */
+       fb_data->pxp_conf.proc_data.lut_transform = 0;
+       if (upd_desc_list->upd_data.flags & EPDC_FLAG_ENABLE_INVERSION)
+               fb_data->pxp_conf.proc_data.lut_transform |= PXP_LUT_INVERT;
+       if (upd_desc_list->upd_data.flags & EPDC_FLAG_FORCE_MONOCHROME)
+               fb_data->pxp_conf.proc_data.lut_transform |=
+                       PXP_LUT_BLACK_WHITE;
+       if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_CMAP)
+               fb_data->pxp_conf.proc_data.lut_transform |=
+                       PXP_LUT_USE_CMAP;
+
+       /*
+        * Toggle inversion processing if 8-bit
+        * inverted is the current pixel format.
+        */
+       if (fb_data->spdc_fb_var.grayscale == GRAYSCALE_4BIT_INVERTED)
+               fb_data->pxp_conf.proc_data.lut_transform ^= PXP_LUT_INVERT;
+
+       /* This is a blocking call, so upon return PxP tx should be done */
+       ret = pxp_process_update(fb_data, src_width, src_height,
+               &pxp_upd_region);
+       if (ret) {
+               dev_err(fb_data->dev, "Unable to submit PxP update task.\n");
+               mutex_unlock(&fb_data->pxp_mutex);
+               return ret;
+       }
+
+       /* If needed, enable SPDC HW while ePxP is processing */
+       if ((fb_data->power_state == POWER_STATE_OFF)
+               || fb_data->powering_down) {
+               spdc_powerup(fb_data);
+       }
+
+       /* This is a blocking call, so upon return PxP tx should be done */
+       ret = pxp_complete_update(fb_data, &hist_stat);
+       if (ret) {
+               dev_err(fb_data->dev, "Unable to complete PxP update task.\n");
+               mutex_unlock(&fb_data->pxp_mutex);
+               return ret;
+       }
+
+       if (use_temp_buf)
+               copy_to_next_buffer(fb_data, upd_data_list);
+
+       mutex_unlock(&fb_data->pxp_mutex);
+
+       /* Update waveform mode from PxP histogram results */
+       if (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO) {
+               if (hist_stat & 0x1)
+                       upd_desc_list->upd_data.waveform_mode =
+                               fb_data->wv_modes.mode_du;
+               else if (hist_stat & 0x2)
+                       upd_desc_list->upd_data.waveform_mode =
+                               fb_data->wv_modes.mode_gc4;
+               else if (hist_stat & 0x4)
+                       upd_desc_list->upd_data.waveform_mode =
+                               fb_data->wv_modes.mode_gc8;
+               else if (hist_stat & 0x8)
+                       upd_desc_list->upd_data.waveform_mode =
+                               fb_data->wv_modes.mode_gc16;
+               else
+                       upd_desc_list->upd_data.waveform_mode =
+                               fb_data->wv_modes.mode_gc32;
+
+               dev_dbg(fb_data->dev, "hist_stat = 0x%x, new waveform = 0x%x\n",
+                       hist_stat, upd_desc_list->upd_data.waveform_mode);
+       }
+
+       return 0;
+}
+
+static bool spdc_submit_concur(mxc_spdc_t *fb_data,
+                       struct update_desc_list *update_to_concur)
+{
+       struct mxcfb_update_data *a, *b;
+       struct mxcfb_rect *arect, *brect;
+       struct update_data_list *next_upd;
+       int i = 0;
+
+       a = &update_to_concur->upd_data;
+       arect = &update_to_concur->upd_data.update_region;
+
+       list_for_each_entry(next_upd,
+               &fb_data->upd_buf_preprocess_list, list) {
+               b = &next_upd->update_desc->upd_data;
+               brect = &next_upd->update_desc->upd_data.update_region;
+
+               /* Updates with different waveform
+                * must be executed sequentially.
+                */
+               if (a->waveform_mode != b->waveform_mode)
+                       break;
+
+               /*
+                 * Concurrency update must has no overlay
+                 */
+               if (!(arect->left > (brect->left + brect->width) ||
+                       brect->left > (arect->left + arect->width) ||
+                       arect->top > (brect->top + brect->height) ||
+                       brect->top > (arect->top + arect->height)))
+                       break;
+
+               i++;
+       }
+
+       if (i != fb_data->upd_preprocess_num)
+               return false;
+
+       return true;
+}
+
+static int spdc_submit_merge(struct update_desc_list *upd_desc_list,
+                               struct update_desc_list *update_to_merge)
+{
+       struct mxcfb_update_data *a, *b;
+       struct mxcfb_rect *arect, *brect;
+       struct mxcfb_rect combine;
+       bool use_flags = false;
+
+       a = &upd_desc_list->upd_data;
+       b = &update_to_merge->upd_data;
+       arect = &upd_desc_list->upd_data.update_region;
+       brect = &update_to_merge->upd_data.update_region;
+
+       /*
+        * Updates with different flags must be executed sequentially.
+        * Halt the merge process to ensure this.
+        */
+       if (a->flags != b->flags) {
+               /*
+                * Special exception: if update regions are identical,
+                * we may be able to merge them.
+                */
+               if ((arect->left != brect->left) ||
+                       (arect->top != brect->top) ||
+                       (arect->width != brect->width) ||
+                       (arect->height != brect->height))
+                       return MERGE_BLOCK;
+
+               use_flags = true;
+       }
+
+       if (a->waveform_mode != b->waveform_mode)
+               a->waveform_mode = WAVEFORM_MODE_AUTO;
+
+       if (arect->left > (brect->left + brect->width) ||
+               brect->left > (arect->left + arect->width) ||
+               arect->top > (brect->top + brect->height) ||
+               brect->top > (arect->top + arect->height))
+               return MERGE_FAIL;
+
+       combine.left = arect->left < brect->left ? arect->left : brect->left;
+       combine.top = arect->top < brect->top ? arect->top : brect->top;
+       combine.width = (arect->left + arect->width) >
+                       (brect->left + brect->width) ?
+                       (arect->left + arect->width - combine.left) :
+                       (brect->left + brect->width - combine.left);
+       combine.height = (arect->top + arect->height) >
+                       (brect->top + brect->height) ?
+                       (arect->top + arect->height - combine.top) :
+                       (brect->top + brect->height - combine.top);
+
+       *arect = combine;
+
+       /* Use flags of the later update */
+       if (use_flags)
+               a->flags = b->flags;
+
+       /* Merge markers */
+       list_splice_tail(&update_to_merge->upd_marker_list,
+               &upd_desc_list->upd_marker_list);
+
+       return MERGE_OK;
+
+}
+
+static void spdc_submit_work_func(struct work_struct *work)
+{
+       struct update_desc_list *next_desc, *temp_desc;
+       mxc_spdc_t *fb_data =
+               container_of(work, mxc_spdc_t, spdc_submit_work);
+       struct update_data_list *upd_data_list = NULL;
+       struct mxcfb_rect adj_update_region, *upd_region;
+       struct update_marker_data *current_marker;
+       bool end_merge = false;
+       bool is_transform;
+       u32 update_addr;
+
+       /* Protect access to buffer queues and to update HW */
+       mutex_lock(&fb_data->queue_mutex);
+
+       /* get a buffer from free list */
+       if (list_empty(&fb_data->upd_buf_free_list)) {
+               mutex_unlock(&fb_data->queue_mutex);
+               return;
+       }
+
+       if (fb_data->fresh_param.concur == SPDC_LUT_ACC_MODE) {
+               list_for_each_entry_safe(next_desc, temp_desc,
+                       &fb_data->upd_pending_list, list) {
+
+                       current_marker =
+                       list_entry((&next_desc->upd_marker_list)->next,
+                               struct update_marker_data, upd_list);
+
+                       if (current_marker->update_marker) {
+                               fb_data->submit_upd_sta = 0;
+                               break;
+                       }
+
+                       /* require free buffer list */
+                       if (list_empty(&fb_data->upd_buf_free_list)) {
+                               dev_dbg(fb_data->dev,
+                                       "buf free list is empty\n");
+                               break;
+                       }
+
+                       upd_data_list =
+                       list_entry(fb_data->upd_buf_free_list.next,
+                               struct update_data_list, list);
+                       upd_data_list->update_desc = next_desc;
+
+                       if (!is_preprocess_list_full(fb_data)) {
+                               if (fb_data->cur_update == NULL &&
+                                       !fb_data->upd_preprocess_num)
+                                       list_del_init(&next_desc->list);
+                               else if (spdc_submit_concur(fb_data,
+                                               next_desc)) {
+                                       list_del_init(&next_desc->list);
+                                       list_add_tail(&upd_data_list->list,
+                                       &fb_data->upd_buf_preprocess_list);
+                                       fb_data->upd_preprocess_num++;
+                                       fb_data->submit_upd_sta =
+                                                       SPDC_CONCUR_UPD;
+                               } else
+                                       break;
+                       } else
+                               break;
+
+                       /* submit to pxp process */
+                       list_del_init(&upd_data_list->list);
+                       goto pxp_process;
+               }
+
+               upd_data_list = NULL;
+               if (fb_data->submit_upd_sta == SPDC_CONCUR_UPD)
+                       fb_data->submit_upd_sta |= SPDC_QUEUE_UPD;
+               else
+                       fb_data->submit_upd_sta = SPDC_QUEUE_UPD;
+       }
+
+       list_for_each_entry_safe(next_desc, temp_desc,
+                       &fb_data->upd_pending_list, list) {
+
+               if (!upd_data_list) {
+
+                       if (list_empty(&fb_data->upd_buf_free_list)) {
+                               dev_dbg(fb_data->dev,
+                                       "buf_free_list is empty\n");
+                               break;
+                       }
+                       upd_data_list =
+                               list_entry(fb_data->upd_buf_free_list.next,
+                                       struct update_data_list, list);
+                       list_del_init(&upd_data_list->list);
+                       upd_data_list->update_desc = next_desc;
+                       list_del_init(&next_desc->list);
+
+                       if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE)
+                               break;
+               } else {
+                       switch (spdc_submit_merge(upd_data_list->update_desc,
+                                       next_desc)) {
+                       case MERGE_OK:
+                               dev_dbg(fb_data->dev,
+                                       "Update merged [queue]\n");
+                               list_del_init(&next_desc->list);
+                               kfree(next_desc);
+                               break;
+                       case MERGE_FAIL:
+                               dev_dbg(fb_data->dev,
+                                       "Update not merged [queue]\n");
+                               break;
+                       case MERGE_BLOCK:
+                               dev_dbg(fb_data->dev,
+                                       "Merge blocked [collision]\n");
+                               end_merge = true;
+                               break;
+                       }
+
+                       if (end_merge)
+                               break;
+               }
+       }
+
+       /* Is update list empty? */
+       if (!upd_data_list) {
+               mutex_unlock(&fb_data->queue_mutex);
+               return;
+       }
+
+pxp_process:
+       /*
+        * If no processing required, skip update processing
+        * No processing means:
+        *   - FB unrotated
+        *   - FB pixel format = 4-bit grayscale
+        *   - No look-up transformations (inversion, posterization, etc.)
+        */
+       is_transform = upd_data_list->update_desc->upd_data.flags &
+               (EPDC_FLAG_ENABLE_INVERSION |
+               EPDC_FLAG_FORCE_MONOCHROME | EPDC_FLAG_USE_CMAP) ?
+               true : false;
+       if ((fb_data->spdc_fb_var.rotate == FB_ROTATE_UR) &&
+               (fb_data->spdc_fb_var.grayscale == GRAYSCALE_4BIT) &&
+               !is_transform) {
+
+               /* If needed, enable SPDC HW while ePxP is processing */
+               if ((fb_data->power_state == POWER_STATE_OFF)
+                       || fb_data->powering_down)
+                       spdc_powerup(fb_data);
+
+               /*
+                * Set update buffer pointer to the start of
+                * the update region in the frame buffer.
+                */
+               upd_region =
+                       &upd_data_list->update_desc->upd_data.update_region;
+               update_addr = fb_data->info.fix.smem_start +
+                       ((upd_region->top * fb_data->info.var.xres_virtual) +
+                       upd_region->left) * fb_data->info.var.bits_per_pixel/8;
+       } else {
+
+               /* Select from PxP output buffers */
+               upd_data_list->phys_addr =
+                       fb_data->phys_addr_updbuf[fb_data->upd_buffer_num];
+               upd_data_list->virt_addr =
+                       fb_data->virt_addr_updbuf[fb_data->upd_buffer_num];
+               fb_data->upd_buffer_num++;
+               if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1)
+                       fb_data->upd_buffer_num = 0;
+               dev_dbg(fb_data->dev,
+                       "pxp out addr:0x%x\n", upd_data_list->phys_addr);
+
+               /* Release buffer queues */
+               mutex_unlock(&fb_data->queue_mutex);
+
+               /* Perform PXP processing - SPDC power will also be enabled */
+               if (spdc_process_update(upd_data_list, fb_data)) {
+
+                       dev_dbg(fb_data->dev, "PXP processing error.\n");
+                       /* Protect access to buffer queues and to update HW */
+                       mutex_lock(&fb_data->queue_mutex);
+                       list_del_init(&upd_data_list->update_desc->list);
+                       kfree(upd_data_list->update_desc);
+                       upd_data_list->update_desc = NULL;
+
+                       /* Add to free buffer list */
+                       list_add_tail(&upd_data_list->list,
+                               &fb_data->upd_buf_free_list);
+                       /* Release buffer queues */
+                       mutex_unlock(&fb_data->queue_mutex);
+                       return;
+               }
+
+               /* Protect access to buffer queues and to update HW */
+               mutex_lock(&fb_data->queue_mutex);
+
+               /* output Y4 format */
+               update_addr = upd_data_list->phys_addr +
+                       + (upd_data_list->update_desc->spdc_offs / 2);
+       }
+
+       /* Get rotation-adjusted coordinates */
+       adjust_coordinates(fb_data->spdc_fb_var.xres,
+               fb_data->spdc_fb_var.yres, fb_data->spdc_fb_var.rotate,
+               &upd_data_list->update_desc->upd_data.update_region,
+               &adj_update_region);
+
+       /*
+        * Is the working buffer idle?
+        * If the working buffer is busy, we must wait for the resource
+        * to become free.
+        */
+       if (fb_data->cur_update != NULL &&
+       fb_data->submit_upd_sta != SPDC_CONCUR_UPD) {
+               /* Initialize event signalling an update resource is free */
+               init_completion(&fb_data->update_res_free);
+
+               fb_data->waiting_for_wb = true;
+
+               /* Leave spinlock while waiting for WB to complete */
+               mutex_unlock(&fb_data->queue_mutex);
+               wait_for_completion(&fb_data->update_res_free);
+               mutex_lock(&fb_data->queue_mutex);
+       }
+
+       if (fb_data->submit_upd_sta != SPDC_CONCUR_UPD)
+               fb_data->cur_update = upd_data_list;
+
+       /* program SPDC register and trigger to process buffer*/
+       fb_data->fresh_param.buf_addr.next_buf_phys_addr = update_addr;
+
+       fb_data->fresh_param.update_region.left = adj_update_region.left;
+       fb_data->fresh_param.update_region.top = adj_update_region.top;
+       fb_data->fresh_param.update_region.width = adj_update_region.width;
+       fb_data->fresh_param.update_region.height = adj_update_region.height;
+       fb_data->fresh_param.temper = upd_data_list->update_desc->upd_data.temp;
+       fb_data->fresh_param.wave_mode =
+               upd_data_list->update_desc->upd_data.waveform_mode;
+
+       spdc_submit_update(fb_data);
+
+       /* Release buffer queues */
+       mutex_unlock(&fb_data->queue_mutex);
+}
+
+static int mxc_spdc_fb_send_update(struct mxcfb_update_data *upd_data,
+                                  struct fb_info *info)
+{
+       mxc_spdc_t *fb_data = info ?
+               (mxc_spdc_t *)info:g_fb_data;
+       struct update_data_list *upd_data_list = NULL;
+       struct mxcfb_rect *screen_upd_region; /* Region on screen to update */
+       struct update_desc_list *upd_desc;
+       struct update_marker_data *marker_data;
+       int ret;
+
+       /* Has SPDC HW been initialized? */
+       if (!fb_data->hw_ready) {
+               /* Throw message if we are not mid-initialization */
+               if (!fb_data->hw_initializing)
+                       dev_err(fb_data->dev, "Display HW not properly"
+                               "initialized. Aborting update.\n");
+               return -EPERM;
+       }
+
+       if ((upd_data->waveform_mode > SPDC_WAV_MODE_5) &&
+               (upd_data->waveform_mode != WAVEFORM_MODE_AUTO)) {
+               dev_err(fb_data->dev,
+                       "Update waveform mode 0x%x is invalid."
+                       "  Aborting update.\n",
+                       upd_data->waveform_mode);
+               return -EINVAL;
+       }
+       if ((upd_data->update_region.left + upd_data->update_region.width >
+               fb_data->spdc_fb_var.xres + 1) ||
+               (upd_data->update_region.top + upd_data->update_region.height >
+               fb_data->spdc_fb_var.yres + 1)) {
+               dev_err(fb_data->dev,
+                       "Update region is outside bounds of framebuffer."
+                       "Aborting update.\n");
+               return -EINVAL;
+       }
+       if (upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) {
+               if ((upd_data->update_region.width !=
+                       upd_data->alt_buffer_data.alt_update_region.width) ||
+                       (upd_data->update_region.height !=
+                       upd_data->alt_buffer_data.alt_update_region.height)) {
+                       dev_err(fb_data->dev,
+                               "Alternate update region dimensions must "
+                               "match screen update region dimensions.\n");
+                       return -EINVAL;
+               }
+               /* Validate physical address parameter */
+               if ((upd_data->alt_buffer_data.phys_addr <
+                       fb_data->info.fix.smem_start) ||
+                       (upd_data->alt_buffer_data.phys_addr >
+                       fb_data->info.fix.smem_start + fb_data->map_size)) {
+                       dev_err(fb_data->dev,
+                               "Invalid physical address for alternate "
+                               "buffer.  Aborting update...\n");
+                       return -EINVAL;
+               }
+       }
+
+       mutex_lock(&fb_data->queue_mutex);
+
+       /*
+        * If we are waiting to go into suspend, or the FB is blanked,
+        * we do not accept new updates
+        */
+       if (fb_data->waiting_for_idle) {
+               dev_dbg(fb_data->dev, "SPDC not active."
+                       "Update request abort.\n");
+               mutex_unlock(&fb_data->queue_mutex);
+               return -EPERM;
+       }
+
+       if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) {
+               int count = 0;
+               struct update_data_list *plist;
+
+               /* Count buffers in free buffer list */
+               list_for_each_entry(plist, &fb_data->upd_buf_free_list, list)
+                       count++;
+
+               /* Use count to determine if we have enough
+                * free buffers to handle this update request */
+               if (count + fb_data->max_num_buffers
+                       <= fb_data->max_num_updates) {
+                       dev_err(fb_data->dev,
+                               "No free intermediate buffers available.\n");
+                       mutex_unlock(&fb_data->queue_mutex);
+                       return -ENOMEM;
+               }
+
+               /* Grab first available buffer and delete from the free list */
+               upd_data_list =
+               list_entry(fb_data->upd_buf_free_list.next,
+                          struct update_data_list, list);
+
+               list_del_init(&upd_data_list->list);
+       }
+
+       /*
+        * Create new update data structure, fill it with new update
+        * data and add it to the list of pending updates
+        */
+       upd_desc = kzalloc(sizeof(struct update_desc_list), GFP_KERNEL);
+       if (!upd_desc) {
+               dev_err(fb_data->dev,
+                       "Insufficient system memory for update! Aborting.\n");
+               if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) {
+                       list_add(&upd_data_list->list,
+                               &fb_data->upd_buf_free_list);
+               }
+               mutex_unlock(&fb_data->queue_mutex);
+               return -EPERM;
+       }
+       /* Initialize per-update marker list */
+       INIT_LIST_HEAD(&upd_desc->upd_marker_list);
+       upd_desc->upd_data = *upd_data;
+       list_add_tail(&upd_desc->list, &fb_data->upd_pending_list);
+
+       /* If marker specified, associate it with a completion */
+       if (upd_data->update_marker != 0) {
+
+               /* Allocate new update marker and set it up */
+               marker_data = kzalloc(sizeof(struct update_marker_data),
+                               GFP_KERNEL);
+               if (!marker_data) {
+                       dev_err(fb_data->dev, "No memory for marker!\n");
+                       mutex_unlock(&fb_data->queue_mutex);
+                       return -ENOMEM;
+               }
+               list_add_tail(&marker_data->upd_list,
+                       &upd_desc->upd_marker_list);
+               marker_data->update_marker = upd_data->update_marker;
+               init_completion(&marker_data->update_completion);
+
+               /* Add marker to master marker list */
+               list_add_tail(&marker_data->full_list,
+                       &fb_data->full_marker_list);
+       }
+
+       if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) {
+               /* Queued update scheme processing */
+
+               mutex_unlock(&fb_data->queue_mutex);
+
+               /* Signal workqueue to handle new update */
+               queue_work(fb_data->spdc_submit_workqueue,
+                       &fb_data->spdc_submit_work);
+
+               return 0;
+       }
+
+       /* Set descriptor for current update, delete from pending list */
+       upd_data_list->update_desc = upd_desc;
+       list_del_init(&upd_desc->list);
+
+       mutex_unlock(&fb_data->queue_mutex);
+
+       /*
+        * Hold on to original screen update region, which we
+        * will ultimately use when telling SPDC where to update on panel
+        */
+       screen_upd_region = &upd_desc->upd_data.update_region;
+
+       /* Select from PxP output buffers */
+       upd_data_list->phys_addr =
+               fb_data->phys_addr_updbuf[fb_data->upd_buffer_num];
+       upd_data_list->virt_addr =
+               fb_data->virt_addr_updbuf[fb_data->upd_buffer_num];
+       fb_data->upd_buffer_num++;
+       if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1)
+               fb_data->upd_buffer_num = 0;
+
+       ret = spdc_process_update(upd_data_list, fb_data);
+       if (ret) {
+               mutex_unlock(&fb_data->pxp_mutex);
+               return ret;
+       }
+
+       /* Pass selected waveform mode back to user */
+       upd_data->waveform_mode = upd_desc->upd_data.waveform_mode;
+
+       /* Get rotation-adjusted coordinates */
+       adjust_coordinates(fb_data->spdc_fb_var.xres,
+               fb_data->spdc_fb_var.yres, fb_data->spdc_fb_var.rotate,
+               &upd_desc->upd_data.update_region, NULL);
+
+       /* Grab lock for queue manipulation and update submission */
+       mutex_lock(&fb_data->queue_mutex);
+
+       /*
+        * Is the working buffer idle?
+        * If either the working buffer is busy, or there are no LUTs available,
+        * then we return and let the ISR handle the update later
+        */
+       if (fb_data->cur_update != NULL) {
+
+               /* Add processed Y buffer to update list */
+               list_add_tail(&upd_data_list->list, &fb_data->upd_buf_queue);
+
+               /* Return and allow the update to be submitted by the ISR. */
+               mutex_unlock(&fb_data->queue_mutex);
+               return 0;
+       }
+
+       /* Save current update */
+       fb_data->cur_update = upd_data_list;
+
+       /* program SPDC register and trigger to process buffer*/
+       fb_data->fresh_param.buf_addr.next_buf_phys_addr =
+       upd_data_list->phys_addr + (upd_data_list->update_desc->spdc_offs / 2);
+
+       fb_data->fresh_param.update_region.left = screen_upd_region->left;
+       fb_data->fresh_param.update_region.top = screen_upd_region->top;
+       fb_data->fresh_param.update_region.width = screen_upd_region->width;
+       fb_data->fresh_param.update_region.height = screen_upd_region->height;
+       fb_data->fresh_param.temper = upd_desc->upd_data.temp;
+       fb_data->fresh_param.wave_mode = upd_desc->upd_data.waveform_mode;
+       spdc_submit_update(fb_data);
+
+       mutex_unlock(&fb_data->queue_mutex);
+       return 0;
+}
+
+/*
+ * return 0 : spdc update is update
+ */
+static int
+mxc_spdc_fb_wait_update_complete(struct mxcfb_update_marker_data *marker_data,
+                               struct fb_info *info)
+{
+       mxc_spdc_t *fb_data = info ?
+               (mxc_spdc_t *)info:g_fb_data;
+       struct update_marker_data *next_marker;
+       struct update_marker_data *temp;
+       bool marker_found = false;
+       int ret = 0;
+
+       /* 0 is an invalid update_marker value */
+       if (marker_data->update_marker == 0)
+               return -EINVAL;
+
+       /*
+        * Find completion associated with update_marker requested.
+        * Note: If update completed already, marker will have been
+        * cleared, it won't be found, and function will just return.
+        */
+
+       /* Grab queue lock to protect access to marker list */
+       mutex_lock(&fb_data->queue_mutex);
+
+       list_for_each_entry_safe(next_marker, temp,
+               &fb_data->full_marker_list, full_list) {
+               if (next_marker->update_marker == marker_data->update_marker) {
+                       dev_dbg(fb_data->dev, "Waiting for marker %d\n",
+                               marker_data->update_marker);
+                       next_marker->waiting = true;
+                       marker_found = true;
+                       break;
+               }
+       }
+
+       mutex_unlock(&fb_data->queue_mutex);
+
+       /*
+        * If marker not found, it has either been signalled already
+        * or the update request failed.  In either case, just return.
+        */
+       if (!marker_found)
+               return ret;
+
+       ret = wait_for_completion_timeout(&next_marker->update_completion,
+                                               msecs_to_jiffies(8000));
+       if (!ret) {
+               dev_err(fb_data->dev,
+                       "Timed out waiting for update completion\n");
+               return -ETIMEDOUT;
+       }
+
+       /** since SPDC don't support auto collision detect,
+        *  there alway returns no collision
+        */
+       marker_data->collision_test = false;
+
+       /* Free update marker object */
+       kfree(next_marker);
+
+       return ret;
+}
+
+int mxc_spdc_fb_set_pwrdown_delay(u32 pwrdown_delay,
+                               struct fb_info *info)
+{
+       mxc_spdc_t *fb_data = info ?
+               (mxc_spdc_t *)info:g_fb_data;
+
+       fb_data->pwrdown_delay = pwrdown_delay;
+
+       return 0;
+}
+EXPORT_SYMBOL(mxc_spdc_fb_set_pwrdown_delay);
+
+int mxc_spdc_get_pwrdown_delay(struct fb_info *info)
+{
+       mxc_spdc_t *fb_data = info ?
+               (mxc_spdc_t *)info:g_fb_data;
+
+       return fb_data->pwrdown_delay;
+}
+EXPORT_SYMBOL(mxc_spdc_get_pwrdown_delay);
+
+static int
+mxc_spdc_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
+{
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+       void __user *argp = (void __user *)arg;
+       int ret = -EINVAL;
+
+       dev_dbg(fb_data->dev, "cmd = %08X, arg = %08X\n", cmd, (u32)arg);
+
+       switch (cmd) {
+       case MXCFB_SET_WAVEFORM_MODES:
+               {
+                       struct mxcfb_waveform_modes modes;
+                       if (!copy_from_user(&modes, argp, sizeof(modes))) {
+                               mxc_spdc_fb_set_waveform_modes(&modes, info);
+                               ret = 0;
+                       }
+                       break;
+               }
+       case MXCFB_SET_TEMPERATURE:
+               {
+                       int temperature;
+                       if (!get_user(temperature, (int32_t __user *) arg))
+                               ret = mxc_spdc_fb_set_temperature(temperature,
+                                       info);
+                       break;
+               }
+       case MXCFB_SET_AUTO_UPDATE_MODE:
+               {
+                       u32 auto_mode = 0;
+                       if (!get_user(auto_mode, (__u32 __user *) arg))
+                               ret = mxc_spdc_fb_set_auto_update(auto_mode,
+                                       info);
+                       break;
+               }
+       case MXCFB_SET_UPDATE_SCHEME:
+               {
+                       u32 upd_scheme = 0;
+                       if (!get_user(upd_scheme, (__u32 __user *) arg))
+                               ret = mxc_spdc_fb_set_upd_scheme(upd_scheme,
+                                       info);
+                       break;
+               }
+       case MXCFB_SEND_UPDATE:
+               {
+                       struct mxcfb_update_data upd_data;
+                       if (!copy_from_user(&upd_data, argp,
+                               sizeof(upd_data))) {
+                               ret = mxc_spdc_fb_send_update(&upd_data, info);
+                               if (ret == 0 && copy_to_user(argp, &upd_data,
+                                       sizeof(upd_data)))
+                                       ret = -EFAULT;
+                       } else {
+                               ret = -EFAULT;
+                       }
+
+                       break;
+               }
+       case MXCFB_WAIT_FOR_UPDATE_COMPLETE:
+               {
+                       struct mxcfb_update_marker_data upd_marker_data;
+                       if (!copy_from_user(&upd_marker_data, argp,
+                               sizeof(upd_marker_data))) {
+                               ret = mxc_spdc_fb_wait_update_complete(
+                                       &upd_marker_data, info);
+                               if (copy_to_user(argp, &upd_marker_data,
+                                       sizeof(upd_marker_data)))
+                                       ret = -EFAULT;
+                       } else {
+                               ret = -EFAULT;
+                       }
+
+                       break;
+               }
+       case MXCFB_SET_PWRDOWN_DELAY:
+               {
+                       int delay = 0;
+                       if (!get_user(delay, (__u32 __user *) arg))
+                               ret =
+                               mxc_spdc_fb_set_pwrdown_delay(delay, info);
+                       break;
+               }
+       case MXCFB_GET_PWRDOWN_DELAY:
+               {
+                       int pwrdown_delay = mxc_spdc_get_pwrdown_delay(info);
+                       if (put_user(pwrdown_delay, (int __user *)arg))
+                               ret = -EFAULT;
+                       break;
+               }
+       default:
+               dev_err(fb_data->dev, "IOCTL_CMD: not such command\n");
+               return -ENOTTY;
+       }
+
+       return ret;
+}
+
+static void mxc_spdc_fb_update_pages(mxc_spdc_t *fb_data, u16 y1, u16 y2)
+{
+       struct mxcfb_update_data update;
+
+       /* Do partial screen update, Update full horizontal lines */
+       update.update_region.left = 0;
+       update.update_region.width = fb_data->spdc_fb_var.xres;
+       update.update_region.top = y1;
+       update.update_region.height = y2 - y1;
+       update.waveform_mode = WAVEFORM_MODE_AUTO;
+       update.update_mode = UPDATE_MODE_FULL;
+       update.update_marker = 0;
+       update.temp = SPDC_DEFAULT_TEMP;
+       update.flags = 0;
+
+       mxc_spdc_fb_send_update(&update, &fb_data->info);
+}
+
+/* this is called back from the deferred io workqueue */
+static void mxc_spdc_fb_deferred_io(struct fb_info *info,
+                               struct list_head *pagelist)
+{
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+       struct page *page;
+       unsigned long beg, end;
+       int y1, y2, miny, maxy;
+
+       if (fb_data->auto_mode != AUTO_UPDATE_MODE_AUTOMATIC_MODE)
+               return;
+
+       miny = INT_MAX;
+       maxy = 0;
+       list_for_each_entry(page, pagelist, lru) {
+               beg = page->index << PAGE_SHIFT;
+               end = beg + PAGE_SIZE - 1;
+               y1 = beg / info->fix.line_length;
+               y2 = end / info->fix.line_length;
+               if (y2 >= fb_data->spdc_fb_var.yres)
+                       y2 = fb_data->spdc_fb_var.yres - 1;
+               if (miny > y1)
+                       miny = y1;
+               if (maxy < y2)
+                       maxy = y2;
+       }
+
+       mxc_spdc_fb_update_pages(fb_data, miny, maxy);
+}
+
+static int mxc_spdc_fb_blank(int blank, struct fb_info *info)
+{
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+       int ret = 0;
+
+       dev_dbg(fb_data->dev, "blank = %d\n", blank);
+
+       if (fb_data->blank == blank)
+               return 0;
+
+       fb_data->blank = blank;
+       switch (blank) {
+       case FB_BLANK_POWERDOWN:
+               mxc_spdc_fb_flush_updates(fb_data);
+               /* Wait for powerdown */
+               mutex_lock(&fb_data->power_mutex);
+               if ((fb_data->power_state == POWER_STATE_ON) &&
+                       (fb_data->pwrdown_delay == FB_POWERDOWN_DISABLE)) {
+
+                       /* Powerdown disabled, so we disable SPDC manually */
+                       int count = 0;
+                       int sleep_ms = 10;
+
+                       mutex_unlock(&fb_data->power_mutex);
+
+                       /* If any active updates, wait for them to complete */
+                       while (fb_data->updates_active) {
+                               /* Timeout after 1 sec */
+                               if ((count * sleep_ms) > 1000)
+                                       break;
+                               msleep(sleep_ms);
+                               count++;
+                       }
+
+                       fb_data->powering_down = true;
+                       spdc_powerdown(fb_data);
+               } else if (fb_data->power_state != POWER_STATE_OFF) {
+                       fb_data->wait_for_powerdown = true;
+                       init_completion(&fb_data->powerdown_compl);
+                       mutex_unlock(&fb_data->power_mutex);
+                       ret =
+                       wait_for_completion_timeout(&fb_data->powerdown_compl,
+                               msecs_to_jiffies(5000));
+                       if (!ret) {
+                               dev_err(fb_data->dev,
+                                       "No powerdown received!\n");
+                               return -ETIMEDOUT;
+                       }
+               } else
+                       mutex_unlock(&fb_data->power_mutex);
+               break;
+       case FB_BLANK_VSYNC_SUSPEND:
+       case FB_BLANK_HSYNC_SUSPEND:
+       case FB_BLANK_NORMAL:
+               mxc_spdc_fb_flush_updates(fb_data);
+               break;
+       }
+
+       return ret;
+}
+
+static int mxc_spdc_fb_pan_display(struct fb_var_screeninfo *var,
+                                  struct fb_info *info)
+{
+       mxc_spdc_t *fb_data = info ?
+               (mxc_spdc_t *)info:g_fb_data;
+       u_int y_bottom;
+
+       dev_dbg(info->device, "%s: var->yoffset %d, info->var.yoffset %d\n",
+                __func__, var->yoffset, info->var.yoffset);
+       /* check if var is valid; also, xpan is not supported */
+       if (!var || (var->xoffset != info->var.xoffset) ||
+       (var->yoffset + var->yres > var->yres_virtual)) {
+               dev_dbg(info->device, "x panning not supported\n");
+               return -EINVAL;
+       }
+
+       if ((fb_data->spdc_fb_var.xoffset == var->xoffset) &&
+               (fb_data->spdc_fb_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;
+
+       mutex_lock(&fb_data->queue_mutex);
+
+       fb_data->fb_offset = (var->yoffset * var->xres_virtual + var->xoffset)
+               * (var->bits_per_pixel) / 8;
+
+       fb_data->spdc_fb_var.xoffset = var->xoffset;
+       fb_data->spdc_fb_var.yoffset = var->yoffset;
+
+       if (var->vmode & FB_VMODE_YWRAP)
+               info->var.vmode |= FB_VMODE_YWRAP;
+       else
+               info->var.vmode &= ~FB_VMODE_YWRAP;
+
+       mutex_unlock(&fb_data->queue_mutex);
+
+       return 0;
+}
+
+static struct fb_ops mxc_spdc_fb_ops = {
+       .owner = THIS_MODULE,
+       .fb_check_var = mxc_spdc_fb_check_var,
+       .fb_set_par = mxc_spdc_fb_set_par,
+       .fb_setcmap = mxc_spdc_fb_setcmap,
+       .fb_setcolreg = mxc_spdc_fb_setcolreg,
+       .fb_pan_display = mxc_spdc_fb_pan_display,
+       .fb_ioctl = mxc_spdc_fb_ioctl,
+       .fb_mmap = mxc_spdc_fb_mmap,
+       .fb_blank = mxc_spdc_fb_blank,
+       .fb_fillrect = cfb_fillrect,
+       .fb_copyarea = cfb_copyarea,
+       .fb_imageblit = cfb_imageblit,
+};
+
+static struct fb_deferred_io mxc_spdc_fb_defio = {
+       .delay = HZ,
+       .deferred_io = mxc_spdc_fb_deferred_io,
+};
+
+static void spdc_done_work_func(struct work_struct *work)
+{
+       mxc_spdc_t *fb_data =
+               container_of(work, mxc_spdc_t, spdc_done_work.work);
+       spdc_powerdown(fb_data);
+}
+
+static irqreturn_t mxc_spdc_irq_handler(int irq, void *dev_id)
+{
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)dev_id;
+       ulong flags;
+       u32 int_events;
+       irqreturn_t ret = IRQ_NONE;
+
+       spin_lock_irqsave(&fb_data->lock, flags);
+
+       int_events = spdc_get_intr_stat(fb_data);
+       dev_dbg(fb_data->dev, "spdc int:%x\n", int_events);
+
+       if (int_events & SPDC_IRQ_STA_ERR) {
+               ret = IRQ_HANDLED;
+               spdc_intr_stat_clear(fb_data, SPDC_IRQ_STA_ERR);
+               dev_err(fb_data->dev, "Error IRQ\n");
+               return ret;
+       }
+
+       /*
+        * If we just completed one-time panel init, bypass
+        * queue handling, clear interrupt and return
+        */
+       if (fb_data->operation_mode &&
+               (int_events & SPDC_IRQ_STA_FRAME_UPDATE)) {
+               mutex_lock(&fb_data->queue_mutex);
+               fb_data->updates_active = false;
+               complete(&fb_data->update_finish);
+               spdc_intr_stat_clear(fb_data, SPDC_IRQ_STA_FRAME_UPDATE);
+
+               if ((fb_data->operation_mode == SPDC_DEEP_REFRESH) ||
+                       fb_data->is_deep_fresh)
+                       fb_data->is_deep_fresh = false;
+               else
+                       fb_data->operation_mode = SPDC_NO_OPERATION;
+
+               mutex_unlock(&fb_data->queue_mutex);
+
+               return IRQ_HANDLED;
+       }
+
+       /* waveform loading to SPDC  from memory */
+       if (int_events & SPDC_IRQ_STA_LUT_DOWNLOAD) {
+               if (is_lut_checksum_ok(fb_data)) {
+                       complete(&fb_data->lut_down);
+                       spdc_intr_stat_clear(fb_data,
+                               SPDC_IRQ_STA_LUT_DOWNLOAD);
+               } else
+                       dev_dbg(fb_data->dev, "Lut checksum is err!\n");
+
+               return IRQ_HANDLED;
+       }
+
+       /* SPDC init setting IRQ */
+       if (int_events & SPDC_IRQ_STA_TCON_INIT) {
+               complete(&fb_data->init_finish);
+               spdc_intr_stat_clear(fb_data, SPDC_IRQ_STA_TCON_INIT);
+
+               return IRQ_HANDLED;
+       }
+
+       spin_unlock_irqrestore(&fb_data->lock, flags);
+
+       if (spdc_is_update_finish(fb_data)) {
+               /* clear interrupt status */
+               spdc_intr_stat_clear(fb_data, SPDC_IRQ_STA_FRAME_UPDATE);
+               queue_work(fb_data->spdc_intr_workqueue,
+                       &fb_data->spdc_intr_work);
+       }
+
+        return ret;
+}
+
+static void spdc_intr_work_func(struct work_struct *work)
+{
+       mxc_spdc_t *fb_data =
+               container_of(work, mxc_spdc_t, spdc_intr_work);
+       struct mxcfb_rect *next_upd_region;
+       struct update_marker_data *next_marker;
+       struct update_marker_data *temp;
+       struct update_data_list *next_upd, *temp_upd;
+
+       /* Protect access to buffer queues and to update HW */
+       mutex_lock(&fb_data->queue_mutex);
+
+       /* Check to see if all updates have completed */
+       if (list_empty(&fb_data->upd_pending_list) &&
+               is_free_list_full(fb_data) &&
+               (fb_data->cur_update == NULL)) {
+
+               fb_data->updates_active = false;
+
+               if (fb_data->pwrdown_delay != FB_POWERDOWN_DISABLE) {
+                       /*
+                        * Set variable to prevent overlapping
+                        * enable/disable requests
+                        */
+                       fb_data->powering_down = true;
+
+                       /* Schedule task to disable SPDC HW until next update */
+                       schedule_delayed_work(&fb_data->spdc_done_work,
+                               msecs_to_jiffies(fb_data->pwrdown_delay));
+
+                       /* Reset counter to reduce chance of overflow */
+                       fb_data->order_cnt = 0;
+               }
+
+               if (fb_data->waiting_for_idle)
+                       complete(&fb_data->updates_done);
+       }
+
+       if (mxc_spdc_device_is_busy(fb_data)) {
+               /* Can't submit another update until SPDC is idle */
+               mutex_unlock(&fb_data->queue_mutex);
+               return;
+       }
+
+       /*
+        * Were we waiting on working buffer?
+        * If so, update queues and check for collisions
+        */
+       if (fb_data->cur_update != NULL) {
+               list_for_each_entry_safe(next_marker, temp,
+                       &fb_data->cur_update->update_desc->upd_marker_list,
+                       upd_list) {
+
+                       /* Del from per-update & full list */
+                       list_del_init(&next_marker->upd_list);
+                       list_del_init(&next_marker->full_list);
+
+                       /* Signal completion of update */
+                       dev_dbg(fb_data->dev,
+                               "Signaling marker %d\n",
+                               next_marker->update_marker);
+                       if (next_marker->waiting)
+                               complete(&next_marker->update_completion);
+                       else
+                               kfree(next_marker);
+               }
+
+               /* Free marker list and update descriptor */
+               kfree(fb_data->cur_update->update_desc);
+
+               /* Add to free buffer list */
+               list_add_tail(&fb_data->cur_update->list,
+                        &fb_data->upd_buf_free_list);
+
+               if (fb_data->submit_upd_sta & SPDC_CONCUR_UPD) {
+                       fb_data->upd_preprocess_num = 0;
+                       fb_data->submit_upd_sta &= (~SPDC_CONCUR_UPD);
+               }
+
+               /* free ACC update list */
+               list_for_each_entry_safe(next_upd, temp_upd,
+                       &fb_data->upd_buf_preprocess_list, list) {
+                       next_marker = list_entry(
+                       (&next_upd->update_desc->upd_marker_list)->next,
+                               struct update_marker_data, upd_list);
+
+                       list_del_init(&next_marker->upd_list);
+                       list_del_init(&next_marker->full_list);
+
+                       /* Signal completion of update */
+                       dev_dbg(fb_data->dev,
+                               "Signaling marker %d\n",
+                               next_marker->update_marker);
+
+                       if (next_marker->waiting)
+                               complete(&next_marker->update_completion);
+                       else
+                               kfree(next_marker);
+
+
+                       list_del_init(&next_upd->list);
+                       /* Add to free buffer list */
+                       list_add_tail(&next_upd->list,
+                               &fb_data->upd_buf_free_list);
+                       kfree(next_upd->update_desc);
+               }
+
+               /* Check to see if all updates have completed */
+               if (list_empty(&fb_data->upd_pending_list) &&
+                       is_free_list_full(fb_data)) {
+
+                       fb_data->updates_active = false;
+
+                       if (fb_data->pwrdown_delay !=
+                                       FB_POWERDOWN_DISABLE) {
+                               /*
+                                * Set variable to prevent overlapping
+                                * enable/disable requests
+                                */
+                               fb_data->powering_down = true;
+
+                               /* Schedule SPDC disable */
+                               schedule_delayed_work(&fb_data->spdc_done_work,
+                               msecs_to_jiffies(fb_data->pwrdown_delay));
+
+                               /* Reset counter to reduce chance of overflow */
+                               fb_data->order_cnt = 0;
+                       }
+
+                       if (fb_data->waiting_for_idle)
+                               complete(&fb_data->updates_done);
+               }
+
+               /* Signal completion if submit workqueue was waiting on WB */
+               if (fb_data->waiting_for_wb) {
+                       dev_dbg(fb_data->dev, "free update_res_free\n");
+                       complete(&fb_data->update_res_free);
+                       fb_data->waiting_for_wb = false;
+               }
+
+               /* Clear current update */
+               fb_data->cur_update = NULL;
+       }
+
+       if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) {
+               /* Queued update scheme processing */
+               /* Schedule task to submit collision and pending update */
+               if (!fb_data->powering_down)
+                       queue_work(fb_data->spdc_submit_workqueue,
+                               &fb_data->spdc_submit_work);
+
+               /* Release buffer queues */
+               mutex_unlock(&fb_data->queue_mutex);
+               return;
+       }
+
+       /* Snapshot update scheme processing */
+       if (list_empty(&fb_data->upd_buf_queue)) {
+               dev_dbg(fb_data->dev, "No pending updates.\n");
+
+               /* No updates pending, so we are done */
+               mutex_unlock(&fb_data->queue_mutex);
+               return;
+       } else {
+               dev_dbg(fb_data->dev, "Found a pending update!\n");
+
+               /* Process next item in update list */
+               fb_data->cur_update =
+                       list_entry(fb_data->upd_buf_queue.next,
+                               struct update_data_list, list);
+               list_del_init(&fb_data->cur_update->list);
+       }
+
+       /* program SPDC register and trigger to process buffer*/
+       next_upd_region =
+               &fb_data->cur_update->update_desc->upd_data.update_region;
+       fb_data->fresh_param.buf_addr.next_buf_phys_addr =
+               fb_data->cur_update->phys_addr +
+               (fb_data->cur_update->update_desc->spdc_offs / 2);
+
+       fb_data->fresh_param.update_region.left = next_upd_region->left;
+       fb_data->fresh_param.update_region.top = next_upd_region->top;
+       fb_data->fresh_param.update_region.width = next_upd_region->width;
+       fb_data->fresh_param.update_region.height = next_upd_region->height;
+       fb_data->fresh_param.temper =
+               fb_data->cur_update->update_desc->upd_data.temp;
+       fb_data->fresh_param.wave_mode =
+               fb_data->cur_update->update_desc->upd_data.waveform_mode;
+       spdc_submit_update(fb_data);
+
+       mutex_unlock(&fb_data->queue_mutex);
+
+       return;
+}
+
+/*
+ * Sysfs functions
+ */
+static ssize_t show_update(struct device *device,
+                               struct device_attribute *attr, char *buf) {
+       struct fb_info *info = dev_get_drvdata(device);
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+
+       return sprintf(buf, "mode%d\n", fb_data->fresh_param.wave_mode);
+}
+
+static ssize_t store_update(struct device *device,
+                        struct device_attribute *attr,
+                        const char *buf, size_t count)
+{
+       struct mxcfb_update_data update;
+       struct fb_info *info = dev_get_drvdata(device);
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+       if (strncmp(buf, "direct", 6) == 0)
+               update.waveform_mode = fb_data->wv_modes.mode_du;
+       else if (strncmp(buf, "gc16", 4) == 0)
+               update.waveform_mode = fb_data->wv_modes.mode_gc16;
+       else if (strncmp(buf, "gc4", 3) == 0)
+               update.waveform_mode = fb_data->wv_modes.mode_gc4;
+       else if (strncmp(buf, "init", 4) == 0)
+               update.waveform_mode = fb_data->wv_modes.mode_init;
+       else if (strncmp(buf, "gu", 2) == 0)
+               update.waveform_mode = SPDC_WAV_MODE_3;
+       else if (strncmp(buf, "auto", 4) == 0)
+               update.waveform_mode = WAVEFORM_MODE_AUTO;
+
+       /* Now, request full screen update */
+       update.update_region.left = 0;
+       update.update_region.width = fb_data->spdc_fb_var.xres;
+       update.update_region.top = 0;
+       update.update_region.height = fb_data->spdc_fb_var.yres;
+       update.update_mode = UPDATE_MODE_FULL;
+       update.temp = SPDC_DEFAULT_TEMP;
+       update.update_marker = 0;
+       update.flags = 0;
+       dev_dbg(fb_data->dev, "rotation:%d, gray:%d\n",
+       fb_data->spdc_fb_var.rotate, fb_data->spdc_fb_var.grayscale);
+
+       mxc_spdc_fb_send_update(&update, info);
+
+       return count;
+}
+
+static ssize_t fresh_show(struct device *device, struct device_attribute *attr,
+                         char *buf)
+{
+       struct fb_info *info = dev_get_drvdata(device);
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+
+       return sprintf(buf, "%d\n", fb_data->operation_mode);
+}
+
+static ssize_t fresh_store(struct device *device, struct device_attribute *attr,
+                          const char *buf, size_t count)
+{
+       struct fb_info *info = dev_get_drvdata(device);
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+       int ret, operation;
+
+       ret = kstrtoint(buf, 10, &operation);
+       if (ret)
+               return ret;
+
+       if (operation == SPDC_DEEP_REFRESH)
+               fb_data->is_deep_fresh = true;
+       fb_data->operation_mode = operation;
+       if (operation > SPDC_NO_OPERATION &&
+               operation < SPDC_FULL_REFRESH)
+               mxc_operaton_update(fb_data);
+       else
+               mxc_spdc_refresh_display(fb_data);
+
+       return count;
+}
+
+static ssize_t temp_show(struct device *device,
+               struct device_attribute *attr, char *buf)
+{
+       struct fb_info *info = dev_get_drvdata(device);
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+       int temp;
+
+       temp = fb_data->fresh_param.temper >> 1;
+       return sprintf(buf, "%d\n", temp);
+}
+
+static ssize_t initset_show(struct device *device,
+               struct device_attribute *attr, char *buf)
+{
+       struct fb_info *info = dev_get_drvdata(device);
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+       u32 init_val;
+
+       get_panel_init_set(&fb_data->panel_set, &init_val);
+       return sprintf(buf, "%x\n", init_val);
+}
+
+static ssize_t concurrency_show(struct device *device,
+               struct device_attribute *attr, char *buf)
+{
+       struct fb_info *info = dev_get_drvdata(device);
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+       bool temp;
+
+       temp = (fb_data->fresh_param.concur ? 1 : 0);
+       return sprintf(buf, "%d\n", temp);
+}
+
+static ssize_t concurrency_update(struct device *device,
+       struct device_attribute *attr, const char *buf, size_t count)
+{
+       struct fb_info *info = dev_get_drvdata(device);
+       mxc_spdc_t *fb_data = (mxc_spdc_t *)info;
+       int ret, concur;
+
+       ret = kstrtoint(buf, 10, &concur);
+       if (ret)
+               return ret;
+
+       if (fb_data->fresh_param.concur != concur) {
+               fb_data->fresh_param.concur = concur;
+               spdc_set_update_concurrency(fb_data);
+       }
+
+       return count;
+}
+
+static DEVICE_ATTR(store_update, 0644, show_update, store_update);
+static DEVICE_ATTR(fresh, 0644, fresh_show, fresh_store);
+static DEVICE_ATTR(temp, 0644, temp_show, NULL);
+static DEVICE_ATTR(initset, 0644, initset_show, NULL);
+static DEVICE_ATTR(concurrency, 0644, concurrency_show, concurrency_update);
+
+static struct attribute *spdc_attributes[] = {
+       &dev_attr_store_update.attr,
+       &dev_attr_fresh.attr,
+       &dev_attr_temp.attr,
+       &dev_attr_initset.attr,
+       &dev_attr_concurrency.attr,
+       NULL
+};
+
+static const struct attribute_group spdc_attr_group = {
+       .attrs = spdc_attributes,
+};
+
+static int __devinit mxc_spdc_fb_probe(struct platform_device *pdev)
+{
+       struct fb_info *info;
+       mxc_spdc_t *fb_data;
+       struct resource *res, *mem;
+       struct fb_videomode *vmode;
+       int xres_virt, yres_virt, buf_size;
+       int xres_virt_rot, yres_virt_rot, pix_size_rot;
+       struct fb_var_screeninfo *var_info;
+       struct fb_fix_screeninfo *fix_info;
+       struct pxp_config_data *pxp_conf;
+       struct pxp_proc_data *proc_data;
+       struct scatterlist *sg;
+       struct update_data_list *upd_list;
+       struct update_data_list *plist, *temp_list;
+       char *options, *opt;
+       char *panel_str = NULL;
+       char name[] = "mxcspdcfb";
+       unsigned long x_mem_size = 0;
+       u32 i, cmap_size;
+       int ret = 0;
+
+       fb_data = (mxc_spdc_t *)framebuffer_alloc(sizeof(mxc_spdc_t),
+                                                       &pdev->dev);
+       if (!fb_data) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       info = &fb_data->info;
+       fb_data->dev = &pdev->dev;
+       platform_set_drvdata(pdev, fb_data);
+
+       fb_data->pdata = pdev->dev.platform_data;
+       if ((fb_data->pdata == NULL) || (fb_data->pdata->num_modes < 1)
+               || (fb_data->pdata->spdc_mode == NULL)
+               || (fb_data->pdata->spdc_mode->vmode == NULL)) {
+               ret = -EINVAL;
+               goto dealloc_fb;
+       }
+
+       /*get panel para from command*/
+       if (fb_get_options(name, &options)) {
+               ret = -ENODEV;
+               goto dealloc_fb;
+       }
+       if (options)
+               while ((opt = strsep(&options, ",")) != NULL) {
+                       if (!*opt)
+                               continue;
+                       if (!strncmp(opt, "bpp=", 4)) {
+                               if (kstrtoint(opt + 4, 0,
+                               &fb_data->default_bpp) < 0)
+                                       fb_data->default_bpp = 0;
+                       } else if (!strncmp(opt, "x_mem=", 6))
+                               x_mem_size = memparse(opt + 6, NULL);
+                       else
+                               panel_str = opt;
+               }
+
+       if (!fb_data->default_bpp)
+               fb_data->default_bpp = SPDC_DEFAULT_BPP;
+
+       /* Set default (first defined mode) for a match */
+       mxc_spdc_find_match_mode(fb_data);
+
+       if (panel_str)
+               for (i = 0; i < fb_data->pdata->num_modes; i++)
+                       if (!strcmp(fb_data->pdata->spdc_mode[i].vmode->name,
+                                                       panel_str)) {
+                               fb_data->cur_mode =
+                                        &fb_data->pdata->spdc_mode[i];
+                               break;
+                       }
+       vmode = fb_data->cur_mode->vmode;
+
+       /* Allocate color map for the FB */
+       cmap_size = 256;
+       ret = fb_alloc_cmap(&info->cmap, cmap_size, 0);
+       if (ret)
+               goto dealloc_fb;
+
+       /*
+        * GPU alignment restrictions dictate framebuffer parameters:
+        * - 32-byte alignment for buffer width
+        * - 128-byte alignment for buffer height
+        * => 4K buffer alignment for buffer start
+        */
+       xres_virt = ALIGN(vmode->xres, 32);
+       yres_virt = ALIGN(vmode->yres, 128);
+       fb_data->max_pix_size = PAGE_ALIGN(xres_virt * yres_virt);
+
+       /*
+        * Have to check to see if aligned buffer size when rotated
+        * is bigger than when not rotated, and use the max
+        */
+       xres_virt_rot = ALIGN(vmode->yres, 32);
+       yres_virt_rot = ALIGN(vmode->xres, 128);
+       pix_size_rot = PAGE_ALIGN(xres_virt_rot * yres_virt_rot);
+       fb_data->max_pix_size = (fb_data->max_pix_size > pix_size_rot) ?
+                               fb_data->max_pix_size : pix_size_rot;
+       buf_size = fb_data->max_pix_size * fb_data->default_bpp/8;
+
+       /* Compute the number of screens needed based on X memory requested */
+       if (x_mem_size > 0) {
+               fb_data->num_screens = DIV_ROUND_UP(x_mem_size, buf_size);
+               if (fb_data->num_screens < NUM_SCREENS_MIN)
+                       fb_data->num_screens = NUM_SCREENS_MIN;
+               else if (buf_size * fb_data->num_screens > SZ_16M)
+                       fb_data->num_screens = SZ_16M / buf_size;
+       } else
+               fb_data->num_screens = NUM_SCREENS_MIN;
+
+       fb_data->map_size = buf_size * fb_data->num_screens;
+       dev_dbg(&pdev->dev, "memory allocate: %d\n", fb_data->map_size);
+
+       /* get IO memory*/
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (res == NULL) {
+               dev_err(&pdev->dev, "failed to get memory register\n");
+               ret = -ENXIO;
+               goto release_cmap;
+       }
+
+       mem = request_mem_region(res->start, resource_size(res), pdev->name);
+       if (res == NULL) {
+               dev_err(&pdev->dev, "failed to get memory region\n");
+               ret = -ENOENT;
+               goto release_cmap;
+       }
+
+       fb_data->hwp = ioremap(res->start, SZ_4K);
+       if (fb_data->hwp == NULL) {
+               dev_err(&pdev->dev, "ioremap registers failed\n");
+               ret = -ENOENT;
+               goto release_mem;
+       }
+
+       /* Allocate FB memory */
+       fb_data->virt_start = dma_alloc_writecombine(&pdev->dev,
+               fb_data->map_size, &fb_data->phys_start, GFP_DMA);
+       if (fb_data->virt_start == NULL) {
+               dev_err(&pdev->dev, "probe err - dma_alloc for framebuffer\n");
+               ret = -ENOMEM;
+               goto release_hwp;
+       }
+
+       var_info = &info->var;
+       var_info->activate = FB_ACTIVATE_TEST;
+       var_info->bits_per_pixel = fb_data->default_bpp;
+       var_info->xres = vmode->xres;
+       var_info->yres = vmode->yres;
+       var_info->xres_virtual = xres_virt;
+       var_info->yres_virtual = yres_virt * fb_data->num_screens;
+       var_info->pixclock = vmode->pixclock;
+       var_info->left_margin = vmode->left_margin;
+       var_info->right_margin = vmode->right_margin;
+       var_info->upper_margin = vmode->upper_margin;
+       var_info->lower_margin = vmode->lower_margin;
+       var_info->hsync_len = vmode->hsync_len;
+       var_info->vsync_len = vmode->vsync_len;
+       var_info->vmode = FB_VMODE_NONINTERLACED;
+
+       switch (fb_data->default_bpp) {
+       case 32:
+       case 24:
+               var_info->red.offset = 16;
+               var_info->red.length = 8;
+               var_info->green.offset = 8;
+               var_info->green.length = 8;
+               var_info->blue.offset = 0;
+               var_info->blue.length = 8;
+               break;
+       case 16:
+               var_info->red.offset = 11;
+               var_info->red.length = 5;
+               var_info->green.offset = 5;
+               var_info->green.length = 6;
+               var_info->blue.offset = 0;
+               var_info->blue.length = 5;
+               break;
+       case 8:
+               var_info->grayscale = GRAYSCALE_8BIT;
+
+               var_info->red.length = 8;
+               var_info->red.offset = 0;
+               var_info->red.msb_right = 0;
+               var_info->green.length = 8;
+               var_info->green.offset = 0;
+               var_info->green.msb_right = 0;
+               var_info->blue.length = 8;
+               var_info->blue.offset = 0;
+               var_info->blue.msb_right = 0;
+               break;
+       case 4:
+               var_info->grayscale = GRAYSCALE_4BIT;
+
+               var_info->red.length = 4;
+               var_info->red.offset = 0;
+               var_info->red.msb_right = 0;
+               var_info->green.length = 4;
+               var_info->green.offset = 0;
+               var_info->green.msb_right = 0;
+               var_info->blue.length = 4;
+               var_info->blue.offset = 0;
+               var_info->blue.msb_right = 0;
+               break;
+       default:
+               dev_err(&pdev->dev, "unsupported bit-width:%d\n",
+                       fb_data->default_bpp);
+               ret = -EINVAL;
+               goto release_dma_fb;
+       }
+
+       fix_info = &info->fix;
+       strcpy(fix_info->id, SPDC_DRIVER_NAME);
+       fix_info->type = FB_TYPE_PACKED_PIXELS;
+       fix_info->visual = FB_VISUAL_TRUECOLOR;
+       fix_info->xpanstep = 0;
+       fix_info->ypanstep = 0;
+       fix_info->ywrapstep = 0;
+       fix_info->accel = FB_ACCEL_NONE;
+       fix_info->smem_start = fb_data->phys_start;
+       fix_info->smem_len = fb_data->map_size;
+       fix_info->ypanstep = 0;
+
+       /* Set up FB info */
+       fb_data->native_width = vmode->xres;
+       fb_data->native_height = vmode->yres;
+       info->screen_base = fb_data->virt_start;
+       info->screen_size = info->fix.smem_len;
+       info->fbops = &mxc_spdc_fb_ops;
+       info->var.activate = FB_ACTIVATE_NOW;
+       info->pseudo_palette = fb_data->pseudo_palette;
+       info->flags = FBINFO_FLAG_DEFAULT;
+
+       mxc_spdc_fb_set_fix(info);
+
+       /* use the same AXI and  PIX clock source */
+       fb_data->spdc_clk_axi = clk_get(fb_data->dev, "epdc_axi");
+       if (IS_ERR(fb_data->spdc_clk_axi)) {
+               dev_err(&pdev->dev, "Unable to get AXI clk."
+                       "err = 0x%x\n", (int)fb_data->spdc_clk_axi);
+               ret = -ENODEV;
+               goto release_dma_fb;
+       }
+       fb_data->spdc_clk_pix = clk_get(fb_data->dev, "epdc_pix");
+       if (IS_ERR(fb_data->spdc_clk_pix)) {
+               dev_err(&pdev->dev, "Unable to get pix clk."
+                       "err = 0x%x\n", (int)fb_data->spdc_clk_pix);
+               ret = -ENODEV;
+               goto release_dma_fb;
+       }
+
+       /*
+        * Initialize update list and allocate buffer.
+        */
+       INIT_LIST_HEAD(&fb_data->upd_pending_list);
+       INIT_LIST_HEAD(&fb_data->upd_buf_queue);
+       INIT_LIST_HEAD(&fb_data->upd_buf_free_list);
+       INIT_LIST_HEAD(&fb_data->upd_buf_preprocess_list);
+       INIT_LIST_HEAD(&fb_data->full_marker_list);
+
+       fb_data->max_num_updates = SPDC_MAX_NUM_UPDATES;
+       fb_data->max_num_buffers = SPDC_MAX_NUM_BUFFERS;
+
+       /* Allocate free buffers */
+       for (i = 0; i < fb_data->max_num_updates; i++) {
+               upd_list = kzalloc(sizeof(*upd_list), GFP_KERNEL);
+               if (upd_list == NULL) {
+                       ret = -ENOMEM;
+                       goto release_dma_fb;
+               }
+
+               /* Add newly allocated buffer to free list */
+               list_add(&upd_list->list, &fb_data->upd_buf_free_list);
+       }
+
+       /* Allocate PXP output buffer */
+       fb_data->virt_addr_updbuf =
+               kzalloc(sizeof(void *) * fb_data->max_num_buffers, GFP_KERNEL);
+       fb_data->phys_addr_updbuf =
+               kzalloc(sizeof(dma_addr_t) * fb_data->max_num_buffers,
+                       GFP_KERNEL);
+       for (i = 0; i < fb_data->max_num_buffers; i++) {
+               /*
+                * Allocate memory for PxP output buffer.
+                * Output raw data is Y4 format.
+                * Each update buffer is 1/2 byte per pixel, and can
+                * be as big as the full-screen frame buffer
+                */
+               fb_data->virt_addr_updbuf[i] =
+                       dma_alloc_coherent(fb_data->info.device,
+                               fb_data->max_pix_size / 2,
+                                  &fb_data->phys_addr_updbuf[i], GFP_DMA);
+               if (fb_data->virt_addr_updbuf[i] == NULL) {
+                       ret = -ENOMEM;
+                       goto release_freebuf_lists;
+               }
+       }
+       /* Counter indicating which update buffer should be used next. */
+       fb_data->upd_buffer_num = 0;
+
+       /* Allocate memory for partical process region buffer.
+        *  Output raw data is Y4 format.
+        */
+       fb_data->virt_addr_copybuf =
+       dma_alloc_coherent(fb_data->info.device,
+                       fb_data->max_pix_size / 2,
+                       &fb_data->phys_addr_copybuf, GFP_DMA);
+       if (fb_data->virt_addr_copybuf == NULL) {
+               ret = -ENOMEM;
+               goto release_output_buf;
+       }
+
+       /* Allocate next & current & privious & count & lut buffers.
+        *  next buffer size is Y4 raw data
+        */
+       fb_data->next_buf_size =
+               (fb_data->map_size / fb_data->num_screens) >> 1;
+       fb_data->virt_next_buf =
+               dma_alloc_coherent(&pdev->dev, fb_data->next_buf_size,
+                               &fb_data->phy_next_buf, GFP_DMA);
+       if (fb_data->virt_next_buf == NULL) {
+               dev_err(&pdev->dev, "Can't allocate mem for next buf!\n");
+               ret = -ENOMEM;
+               goto release_copy_buf;
+       }
+
+       fb_data->current_buf_size =
+               (fb_data->map_size / fb_data->num_screens) >> 1;
+       fb_data->virt_current_buf =
+               dma_alloc_coherent(&pdev->dev, fb_data->current_buf_size,
+                               &fb_data->phy_current_buf, GFP_DMA);
+       if (fb_data->virt_current_buf == NULL) {
+               dev_err(&pdev->dev, "Can't allocate mem for current buf!\n");
+               ret = -ENOMEM;
+               goto release_next_buf;
+       }
+
+       fb_data->pre_buf_size =
+               (fb_data->map_size / fb_data->num_screens) >> 1;
+       fb_data->virt_pre_buf =
+               dma_alloc_coherent(&pdev->dev, fb_data->pre_buf_size,
+                               &fb_data->phy_pre_buf, GFP_DMA);
+       if (fb_data->virt_pre_buf == NULL) {
+               dev_err(&pdev->dev, "Can't allocate mem for current buf!\n");
+               ret = -ENOMEM;
+               goto release_current_buf;
+       }
+
+       fb_data->cnt_buf_size = info->var.xres * info->var.yres;
+       fb_data->virt_cnt_buf =
+               dma_alloc_coherent(&pdev->dev, fb_data->cnt_buf_size,
+                               &fb_data->phy_cnt_buf, GFP_DMA);
+       if (fb_data->virt_cnt_buf == NULL) {
+               dev_err(&pdev->dev, "Can't allocate mem for current buf!\n");
+               ret = -ENOMEM;
+               goto release_pre_buf;
+       }
+
+       fb_data->lut_buf_size = SZ_1M;
+       fb_data->virt_lut_buf =
+               dma_alloc_coherent(&pdev->dev, fb_data->lut_buf_size,
+                               &fb_data->phy_lut_buf, GFP_DMA);
+       if (fb_data->virt_lut_buf == NULL) {
+               dev_err(&pdev->dev, "Can't allocate mem for current buf!\n");
+               ret = -ENOMEM;
+               goto release_cnt_buf;
+       }
+
+       /* Initialize SPDC pins */
+       if (fb_data->pdata->get_pins)
+               fb_data->pdata->get_pins();
+
+       fb_data->hw_ready = false;
+       fb_data->hw_initializing = false;
+
+       /*
+        * Set default waveform mode values.
+        * Should be overwritten via ioctl.
+        */
+       fb_data->wv_modes.mode_init = SPDC_WAV_MODE_DEFAULT;
+       fb_data->wv_modes.mode_du = SPDC_WAV_MODE_4;
+       fb_data->wv_modes.mode_gc4 = SPDC_WAV_MODE_2;
+       fb_data->wv_modes.mode_gc8 = SPDC_WAV_MODE_1;
+       fb_data->wv_modes.mode_gc16 = SPDC_WAV_MODE_1;
+       fb_data->wv_modes.mode_gc32 = SPDC_WAV_MODE_1;
+
+       fb_data->auto_mode = AUTO_UPDATE_MODE_REGION_MODE;
+       fb_data->upd_scheme = UPDATE_SCHEME_QUEUE_AND_MERGE;
+       fb_data->spdc_fb_var = *var_info;
+       fb_data->fb_offset = 0;
+
+       fb_data->blank = FB_BLANK_UNBLANK;
+       fb_data->powering_down = false;
+       fb_data->power_state = POWER_STATE_OFF;
+       fb_data->pwrdown_delay = 0;
+       fb_data->cur_update = NULL;
+       fb_data->waiting_for_idle = false;
+       fb_data->order_cnt = 0;
+       fb_data->waiting_for_wb = false;
+       fb_data->wait_for_powerdown = false;
+       fb_data->updates_active = false;
+
+       spin_lock_init(&fb_data->lock);
+       mutex_init(&fb_data->queue_mutex);
+       mutex_init(&fb_data->pxp_mutex);
+       mutex_init(&fb_data->power_mutex);
+       init_completion(&fb_data->lut_down);
+       init_completion(&fb_data->init_finish);
+       init_completion(&fb_data->update_finish);
+       INIT_DELAYED_WORK(&fb_data->spdc_done_work, spdc_done_work_func);
+
+       fb_data->spdc_submit_workqueue = alloc_workqueue("SPDC Submit",
+                                       WQ_MEM_RECLAIM | WQ_HIGHPRI |
+                                       WQ_CPU_INTENSIVE | WQ_UNBOUND, 1);
+       INIT_WORK(&fb_data->spdc_submit_work, spdc_submit_work_func);
+       fb_data->spdc_intr_workqueue = alloc_workqueue("SPDC Interrupt",
+                                       WQ_MEM_RECLAIM | WQ_HIGHPRI |
+                                       WQ_CPU_INTENSIVE | WQ_UNBOUND, 1);
+       INIT_WORK(&fb_data->spdc_intr_work, spdc_intr_work_func);
+
+       /* Retrieve spdc IRQ num */
+       res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+       if (res == NULL) {
+               dev_err(&pdev->dev, "cannot get IRQ resource\n");
+               ret = -ENODEV;
+               goto release_lut_buf;
+       }
+       fb_data->spdc_irq = res->start;
+       ret = request_irq(fb_data->spdc_irq, mxc_spdc_irq_handler, 0,
+                       "fb_dma", fb_data);
+       if (ret) {
+               dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n",
+                       fb_data->spdc_irq, ret);
+               ret = -ENODEV;
+               goto release_lut_buf;
+       }
+
+       /* define deferred io */
+       info->fbdefio = &mxc_spdc_fb_defio;
+#ifdef CONFIG_FB_MXC_SIPIX_AUTO_UPDATE_MODE
+       fb_deferred_io_init(info);
+#endif
+
+       /* get pmic regulators */
+       fb_data->display_regulator = regulator_get(NULL, "DISPLAY");
+       if (IS_ERR(fb_data->display_regulator)) {
+               dev_err(&pdev->dev, "Unable to get display PMIC regulator."
+                       "err = 0x%x\n", (int)fb_data->display_regulator);
+               ret = -ENODEV;
+               goto release_irq;
+       }
+       fb_data->vcom_regulator = regulator_get(NULL, "VCOM");
+       if (IS_ERR(fb_data->vcom_regulator)) {
+               regulator_put(fb_data->display_regulator);
+               dev_err(&pdev->dev, "Unable to get VCOM regulator."
+                       "err = 0x%x\n", (int)fb_data->vcom_regulator);
+               ret = -ENODEV;
+               goto release_regulator1;
+       }
+       fb_data->v3p3_regulator = regulator_get(NULL, "V3P3");
+       if (IS_ERR(fb_data->v3p3_regulator)) {
+               regulator_put(fb_data->vcom_regulator);
+               regulator_put(fb_data->display_regulator);
+               dev_err(&pdev->dev, "Unable to get V3P3 regulator."
+                       "err = 0x%x\n", (int)fb_data->vcom_regulator);
+               ret = -ENODEV;
+               goto release_regulator2;
+       }
+
+       /*
+        * Fill out PxP config data structure based on FB info and
+        * processing tasks required
+        */
+       pxp_conf = &fb_data->pxp_conf;
+       proc_data = &pxp_conf->proc_data;
+
+       /* Initialize non-channel-specific PxP parameters */
+       proc_data->drect.left = proc_data->srect.left = 0;
+       proc_data->drect.top = proc_data->srect.top = 0;
+       proc_data->drect.width = fb_data->info.var.xres;
+       proc_data->srect.width = fb_data->info.var.xres;
+       proc_data->drect.height = fb_data->info.var.yres;
+       proc_data->srect.height = fb_data->info.var.yres;
+       proc_data->scaling = 0;
+       proc_data->hflip = 0;
+       proc_data->vflip = 0;
+       proc_data->rotate = 0;
+       proc_data->bgcolor = 0;
+       proc_data->overlay_state = 0;
+       proc_data->lut_transform = PXP_LUT_NONE;
+       proc_data->lut_map = NULL;
+
+       /*
+        * We initially configure PxP for RGB->YUV conversion,
+        * and only write out Y component of the result.
+        */
+
+       /*
+        * Initialize S0 channel parameters
+        * Parameters should match FB format/width/height
+        */
+       pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565;
+       pxp_conf->s0_param.width = fb_data->info.var.xres_virtual;
+       pxp_conf->s0_param.height = fb_data->info.var.yres;
+       pxp_conf->s0_param.color_key = -1;
+       pxp_conf->s0_param.color_key_enable = false;
+
+       /*
+        * Initialize OL0 channel parameters
+        * No overlay will be used for PxP operation
+        */
+       for (i = 0; i < 8; i++) {
+               pxp_conf->ol_param[i].combine_enable = false;
+               pxp_conf->ol_param[i].width = 0;
+               pxp_conf->ol_param[i].height = 0;
+               pxp_conf->ol_param[i].pixel_fmt = PXP_PIX_FMT_RGB565;
+               pxp_conf->ol_param[i].color_key_enable = false;
+               pxp_conf->ol_param[i].color_key = -1;
+               pxp_conf->ol_param[i].global_alpha_enable = false;
+               pxp_conf->ol_param[i].global_alpha = 0;
+               pxp_conf->ol_param[i].local_alpha_enable = false;
+       }
+
+       /*
+        * Initialize Output channel parameters
+        * Output is Y-only greyscale
+        * Output width/height will vary based on update region size
+        */
+       pxp_conf->out_param.width = fb_data->info.var.xres;
+       pxp_conf->out_param.height = fb_data->info.var.yres;
+       pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GY04;
+       pxp_conf->out_param.stride = pxp_conf->out_param.width;
+
+       /* Initialize color map for conversion of 8-bit gray pixels */
+       fb_data->pxp_conf.proc_data.lut_map = kmalloc(256, GFP_KERNEL);
+       if (fb_data->pxp_conf.proc_data.lut_map == NULL) {
+               dev_err(&pdev->dev, "Can't allocate mem for lut map!\n");
+               ret = -ENOMEM;
+               goto release_regulator3;
+       }
+       for (i = 0; i < 256; i++)
+               fb_data->pxp_conf.proc_data.lut_map[i] = i;
+
+       fb_data->pxp_conf.proc_data.lut_map_updated = true;
+
+       /*
+        * Ensure this is set to NULL here...we will initialize pxp_chan
+        * later in our thread.
+        */
+       fb_data->pxp_chan = NULL;
+
+       /* Initialize Scatter-gather list containing 2 buffer addresses. */
+       sg = fb_data->sg;
+       sg_init_table(sg, 2);
+
+       /*
+        * For use in PxP transfers:
+        * sg[0] holds the FB buffer pointer
+        * sg[1] holds the Output buffer pointer (configured before TX request)
+        */
+       sg_dma_address(&sg[0]) = info->fix.smem_start;
+       sg_set_page(&sg[0], virt_to_page(info->screen_base),
+               info->fix.smem_len, offset_in_page(info->screen_base));
+
+       /* Register FB */
+       ret = register_framebuffer(info);
+       if (ret) {
+               dev_err(&pdev->dev,
+                       "register framebuffer failed\n");
+               goto release_lutmap;
+       }
+
+       ret = sysfs_create_group(&info->device->kobj, &spdc_attr_group);
+       if (ret)
+               dev_err(&pdev->dev, "Unable to create file from fb_attrs\n");
+
+       /* use for spdc test */
+       g_fb_data = fb_data;
+
+       /* hw init */
+       spdc_fb_dev_init(fb_data);
+
+       /*detect spdc epd disp & tcon version*/
+       get_spdc_version(fb_data);
+
+       goto out;
+
+release_lutmap:
+       kfree(fb_data->pxp_conf.proc_data.lut_map);
+release_regulator3:
+       regulator_put(fb_data->v3p3_regulator);
+release_regulator2:
+       regulator_put(fb_data->vcom_regulator);
+release_regulator1:
+       regulator_put(fb_data->display_regulator);
+release_irq:
+       free_irq(fb_data->spdc_irq, fb_data);
+release_lut_buf:
+       dma_free_coherent(&pdev->dev, fb_data->pre_buf_size,
+               fb_data->virt_lut_buf, fb_data->phy_lut_buf);
+release_cnt_buf:
+       dma_free_coherent(&pdev->dev, fb_data->cnt_buf_size,
+               fb_data->virt_cnt_buf, fb_data->phy_cnt_buf);
+release_pre_buf:
+       dma_free_coherent(&pdev->dev, fb_data->pre_buf_size,
+               fb_data->virt_pre_buf, fb_data->phy_pre_buf);
+release_current_buf:
+       dma_free_coherent(&pdev->dev, fb_data->current_buf_size,
+               fb_data->virt_current_buf, fb_data->phy_current_buf);
+release_next_buf:
+       dma_free_coherent(&pdev->dev, fb_data->next_buf_size,
+               fb_data->virt_next_buf, fb_data->phy_next_buf);
+release_copy_buf:
+       dma_free_writecombine(&pdev->dev, fb_data->max_pix_size / 2,
+               fb_data->virt_addr_copybuf, fb_data->phys_addr_copybuf);
+release_output_buf:
+       for (i = 0; i < fb_data->max_num_buffers; i++)
+               if (fb_data->virt_addr_updbuf[i] != NULL)
+                       dma_free_writecombine(&pdev->dev,
+                       fb_data->max_pix_size / 2, fb_data->virt_addr_updbuf[i],
+                       fb_data->phys_addr_updbuf[i]);
+       if (fb_data->virt_addr_updbuf != NULL)
+               kfree(fb_data->virt_addr_updbuf);
+       if (fb_data->phys_addr_updbuf != NULL)
+               kfree(fb_data->phys_addr_updbuf);
+release_freebuf_lists:
+       list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list,
+                       list) {
+               list_del(&plist->list);
+               kfree(plist);
+       }
+release_dma_fb:
+       dma_free_writecombine(&pdev->dev,
+       fb_data->map_size, fb_data->virt_start, fb_data->phys_start);
+release_hwp:
+       iounmap(fb_data->hwp);
+release_mem:
+       release_resource(mem);
+release_cmap:
+       fb_dealloc_cmap(&info->cmap);
+dealloc_fb:
+       framebuffer_release(info);
+out:
+       return ret;
+}
+
+static int mxc_spdc_fb_remove(struct platform_device *pdev)
+{
+       struct update_data_list *plist, *temp_list;
+       mxc_spdc_t *fb_data = platform_get_drvdata(pdev);
+       struct fb_info *info = &fb_data->info;
+       int i;
+
+       mxc_spdc_fb_blank(FB_BLANK_POWERDOWN, &fb_data->info);
+
+       flush_workqueue(fb_data->spdc_submit_workqueue);
+       destroy_workqueue(fb_data->spdc_submit_workqueue);
+
+       regulator_put(fb_data->display_regulator);
+       regulator_put(fb_data->vcom_regulator);
+       regulator_put(fb_data->v3p3_regulator);
+
+       for (i = 0; i < fb_data->max_num_buffers; i++)
+               if (fb_data->virt_addr_updbuf[i] != NULL)
+                       dma_free_writecombine(&pdev->dev,
+                               fb_data->max_pix_size,
+                               fb_data->virt_addr_updbuf[i],
+                               fb_data->phys_addr_updbuf[i]);
+       if (fb_data->virt_addr_updbuf != NULL)
+               kfree(fb_data->virt_addr_updbuf);
+       if (fb_data->phys_addr_updbuf != NULL)
+               kfree(fb_data->phys_addr_updbuf);
+
+       /* free output temporary buffer */
+       dma_free_writecombine(&pdev->dev, fb_data->max_pix_size / 2,
+               fb_data->virt_addr_copybuf, fb_data->phys_addr_copybuf);
+
+       list_for_each_entry_safe(plist, temp_list,
+                       &fb_data->upd_buf_free_list,
+                       list) {
+               list_del(&plist->list);
+               kfree(plist);
+       }
+
+#if defined(CONFIG_FB_MXC_SIPIX_AUTO_UPDATE_MODE)
+       fb_deferred_io_cleanup(&fb_data->info);
+#endif
+
+       /* free frame buffer */
+       dma_free_writecombine(&pdev->dev, fb_data->map_size,
+                       info->screen_base, fb_data->phys_start);
+
+       /* free SPDC hw allocate buffer */
+       dma_free_coherent(&pdev->dev, fb_data->next_buf_size,
+               fb_data->virt_next_buf, fb_data->phy_next_buf);
+       dma_free_coherent(&pdev->dev, fb_data->current_buf_size,
+               fb_data->virt_current_buf, fb_data->phy_current_buf);
+       dma_free_coherent(&pdev->dev, fb_data->pre_buf_size,
+               fb_data->virt_pre_buf, fb_data->phy_pre_buf);
+       dma_free_coherent(&pdev->dev, fb_data->cnt_buf_size,
+               fb_data->virt_cnt_buf, fb_data->phy_cnt_buf);
+       dma_free_coherent(&pdev->dev, fb_data->pre_buf_size,
+               fb_data->virt_lut_buf, fb_data->phy_lut_buf);
+
+       sysfs_remove_group(&info->device->kobj, &spdc_attr_group);
+       unregister_framebuffer(info);
+
+       if (fb_data->pdata->put_pins)
+               fb_data->pdata->put_pins();
+
+       free_irq(fb_data->spdc_irq, fb_data);
+       iounmap(fb_data->hwp);
+
+       fb_dealloc_cmap(&info->cmap);
+
+       framebuffer_release(info);
+       platform_set_drvdata(pdev, NULL);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int mxc_spdc_fb_suspend(struct platform_device *pdev,
+                               pm_message_t state)
+{
+       mxc_spdc_t *data = platform_get_drvdata(pdev);
+       int ret;
+
+       ret = mxc_spdc_fb_blank(FB_BLANK_POWERDOWN, &data->info);
+
+       return ret;
+}
+
+static int mxc_spdc_fb_resume(struct platform_device *pdev)
+{
+       mxc_spdc_t *data = platform_get_drvdata(pdev);
+
+       mxc_spdc_fb_blank(FB_BLANK_UNBLANK, &data->info);
+       return 0;
+}
+#else
+#define mxc_spdc_fb_suspend    NULL
+#define mxc_spdc_fb_resume     NULL
+#endif
+
+static struct platform_driver mxc_spdc_fb_driver = {
+       .probe = mxc_spdc_fb_probe,
+       .remove = mxc_spdc_fb_remove,
+       .suspend = mxc_spdc_fb_suspend,
+       .resume = mxc_spdc_fb_resume,
+       .driver = {
+               .name = SPDC_DRIVER_NAME,
+               .owner = THIS_MODULE,
+       },
+};
+
+static int __init mxc_spdc_fb_init(void)
+{
+       return platform_driver_register(&mxc_spdc_fb_driver);
+}
+late_initcall(mxc_spdc_fb_init);
+
+static void __exit mxc_spdc_fb_exit(void)
+{
+       platform_driver_unregister(&mxc_spdc_fb_driver);
+}
+module_exit(mxc_spdc_fb_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC SPDC framebuffer driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("fb");
diff --git a/drivers/video/mxc/mxc_spdc_fb.h b/drivers/video/mxc/mxc_spdc_fb.h
new file mode 100644 (file)
index 0000000..3437378
--- /dev/null
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2012 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __MXC_SPDC_FB_H__
+#define __MXC_SPDC_FB_H__
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/dmaengine.h>
+#include <linux/pxp_dma.h>
+#include <linux/mxcfb.h>
+#include <linux/list.h>
+#include <linux/uaccess.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/regulator/driver.h>
+#include <linux/dmaengine.h>
+#include <linux/gpio.h>
+#include <linux/kthread.h>
+#include <mach/epdc.h>
+#include <mach/dma.h>
+#include <linux/platform_device.h>
+
+#include <linux/fsl_devices.h>
+#include <linux/mxcfb.h>
+
+/*************************************
+*Register addresses
+*************************************/
+#define SPDC_DISP_TRIGGER              (0x000)
+#define SPDC_UPDATA_X_Y                        (0x004)
+#define SPDC_UPDATE_W_H                        (0x008)
+#define SPDC_LUT_PARA_UPDATE           (0x00C)
+#define SPDC_OPERATE                   (0x010)
+#define SPDC_PANEL_INIT_SET            (0x014)
+#define SPDC_TEMP_INFO                 (0x018)
+#define SPDC_NEXT_BUF                  (0x01C)
+#define SPDC_CURRENT_BUF               (0x020)
+#define SPDC_PRE_BUF                   (0x024)
+#define SPDC_CNT_BUF                   (0x028)
+#define SPDC_LUT_BUF                   (0x02C)
+#define SPDC_INT_ENABLE                        (0x030)
+#define SPDC_INT_STA_CLR               (0x034)
+#define SPDC_INIT_STA_CLR              (0x038)
+#define SPDC_STATUS                    (0x03C)
+#define SPDC_DISP_VER                  (0x040)
+#define SPDC_TCON_VER                  (0x044)
+#define SPDC_SW_GATE_CLK               (0x048)
+
+/*
+ * Register field definitions
+ */
+enum {
+       SPDC_DISP_TRIGGER_ENABLE = 0x1,
+       SPDC_DISP_TRIGGER_FLASH = 0x10,
+
+       /*SPDC clock gate*/
+       SPDC_SW_GATE_CLK_ENABLE = 0x1,
+
+       /* waveform mode mask */
+       SPDC_WAV_MODE_MASK = (0x7 << 1),
+
+       /* SPDC interrupt IRQ mask define */
+       SPDC_IRQ_FRAME_UPDATE = (0x1 << 0),
+       SPDC_IRQ_TCON_INIT = (0x1 << 1),
+       SPDC_IRQ_LUT_DOWNLOAD = (0x1 << 2),
+       SPDC_IRQ_ERR = (0x1 << 3),
+       SPDC_IRQ_ALL_MASK = 0xF,
+
+       /* SPDC interrupt status */
+       SPDC_IRQ_STA_FRAME_UPDATE = (0x1 << 0),
+       SPDC_IRQ_STA_TCON_INIT = (0x1 << 1),
+       SPDC_IRQ_STA_LUT_DOWNLOAD = (0x1 << 2),
+       SPDC_IRQ_STA_ERR = (0x1 << 3),
+       SPDC_IRQ_STA_ALL_MASK = 0xF,
+
+       /* SPDC update coordinate angle */
+       SPDC_UPDATE_W_H_MAX_SIZE = ((0x1 << 12) - 1),
+       SPDC_UPDATE_X_Y_MAX_SIZE = ((0x1 << 12) - 1),
+
+       /* SPDC TCON status */
+       SPDC_PANEL_STAUTS_BUSY = 0x1,
+       SPDC_TCON_STATUS_IDLE = (0x4 << 4),
+
+       /* SPDC EPD Operation mode */
+       SPDC_NO_OPERATION = 0,
+       SPDC_DISP_REFRESH = 1,
+       SPDC_DEEP_REFRESH = 0x2,
+       SPDC_DISP_RESET   = 0x4,
+       SPDC_SW_TCON_RESET = 0x5,
+       SPDC_SW_TCON_RESET_SET = 0x80000000,
+       SPDC_FULL_REFRESH = 0x6,
+
+       /* SPDC Concurrency mechanism: ACC */
+       SPDC_LUT_MODE_OFFSET = 0xb3,
+       SPDC_LUT_ACC_MODE = 0x4,
+
+       /* SPDC waveform */
+       SPDC_WAVEFORM_LUT_OFFSET_ADDR = 0x7600,
+       SPDC_LUT_PARA_LENTH = 0x100,
+       SPDC_LUT_FROM_MEM = 1,
+       SPDC_LUT_TO_MEM = 0,
+
+       /* SPDC submit update status */
+       SPDC_CONCUR_UPD = 0x4,
+       SPDC_QUEUE_UPD = 0x8,
+       SPDC_CONCUR_QUEUE = 0xc,
+};
+
+#define        SPDC_DRIVER_NAME        "imx_spdc_fb"
+
+/**
+ * SPDC EPD waveform display trigger mode
+ */
+enum {
+       SPDC_WAV_MODE_0 = 0,
+       SPDC_WAV_MODE_1,
+       SPDC_WAV_MODE_2,
+       SPDC_WAV_MODE_3,
+       SPDC_WAV_MODE_4,
+       SPDC_WAV_MODE_5,
+       SPDC_WAV_MODE_DEFAULT = SPDC_WAV_MODE_0,
+};
+
+/**
+ * SPDC controller ip version
+ */
+struct mxc_epd_disp_version {
+       u8 epd_type;
+       u8 lut_ver;
+       u16 product_id;
+};
+
+struct mxc_spdc_version {
+       struct mxc_epd_disp_version disp_ver;
+       u8 tcon_ver;
+};
+
+struct spdc_buffer_addr {
+       u32 next_buf_phys_addr;
+       u32 cur_buf_phys_addr;
+       u32 pre_buf_phys_addr;
+       u32 frm_cnt_buf_phys_addr;
+       u32 lut_buf_phys_addr;
+};
+
+struct partial_refresh_param {
+       struct spdc_buffer_addr buf_addr;
+       struct mxcfb_rect update_region;
+       int temper;
+       u32 blocking;
+       u32 wave_mode;
+       u32 stride;
+       u32 flash; /* only for waveform mode7 */
+       u32 concur; /* Concurrency mechanism: ACC */
+};
+
+/**
+ * SPDC lut data
+ */
+struct mxc_spdc_lut_para {
+       u8 lut_data[SPDC_LUT_PARA_LENTH];
+       u8 lut_addr[SPDC_LUT_PARA_LENTH];
+       bool lut_para_update_sta;
+};
+
+#define PORTRAIT       "portrait"
+#define LANDSCAPE      "Landscape"
+#define RESERVED       "Reserved"
+struct mxc_spdc_resolution_map_para {
+       u16 resolution;
+       u16 res_x;
+       u16 res_y;
+       char res_name[12];
+};
+
+struct update_marker_data {
+       struct list_head full_list;
+       struct list_head upd_list;
+       u32 update_marker;
+       struct completion update_completion;
+       int lut_num;
+       bool collision_test;
+       bool waiting;
+};
+
+struct update_desc_list {
+       struct list_head list;
+       struct mxcfb_update_data upd_data;
+       u32 spdc_offs;
+       u32 spdc_stride;
+       struct list_head upd_marker_list;
+       u32 update_order;
+};
+
+/* This structure represents a list node containing both
+ * a memory region allocated as an output buffer for the PxP
+ * update processing task, and the update
+ * description (mode, region, etc.)
+ */
+struct update_data_list {
+       struct list_head list;
+       dma_addr_t phys_addr;/* Pointer to phys address of processed Y buf */
+       void *virt_addr;
+       struct update_desc_list *update_desc;
+       int lut_num;
+       u64 collision_mask; /* SPDC cannot support collision detect,
+                            * align with EPDC driver struct */
+};
+
+typedef struct mxc_spdc_fb_param {
+       struct fb_info info;
+       struct fb_var_screeninfo spdc_fb_var;
+
+       char fw_str[24];
+       u32 pseudo_palette[16];
+       struct mxc_spdc_version spdc_ver;
+       struct imx_spdc_fb_mode *cur_mode;
+       struct imx_spdc_fb_platform_data *pdata;
+       struct partial_refresh_param fresh_param;
+       int blank;
+       bool updates_active;
+
+       u32 fb_offset;
+       u32 max_pix_size;
+       ssize_t map_size;
+       int native_width;
+       int native_height;
+       int default_bpp;
+
+       dma_addr_t phys_start;
+       void *virt_start;
+       struct device *dev;
+
+       int num_screens;
+       u32 order_cnt;
+       int max_num_updates;
+       u32 auto_mode;
+       u32 upd_scheme;
+       u32 operation_mode;
+       bool is_deep_fresh;
+       struct list_head full_marker_list;
+       struct list_head upd_pending_list;
+       struct list_head upd_buf_queue;
+       struct list_head upd_buf_free_list;
+       struct list_head upd_buf_preprocess_list;
+       u32 upd_preprocess_num;
+       int submit_upd_sta;
+       struct update_data_list *cur_update;
+       struct mutex queue_mutex;
+       struct imx_spdc_panel_init_set panel_set;
+       struct mxc_spdc_lut_para lut_para;
+       struct mxcfb_waveform_modes wv_modes;
+
+       dma_addr_t phy_next_buf;
+       dma_addr_t phy_pre_buf;
+       dma_addr_t phy_current_buf;
+       dma_addr_t phy_cnt_buf;
+       dma_addr_t phy_lut_buf;
+       void *virt_next_buf;
+       void *virt_current_buf;
+       void *virt_pre_buf;
+       void *virt_cnt_buf;
+       void *virt_lut_buf;
+       u32 next_buf_size;
+       u32 current_buf_size;
+       u32 pre_buf_size;
+       u32 cnt_buf_size;
+       u32 lut_buf_size;
+
+       dma_addr_t *phys_addr_updbuf;
+       void **virt_addr_updbuf;
+       u32 upd_buffer_num;
+       u32 max_num_buffers;
+
+       /* copy the processed data to next buffer relative region */
+       dma_addr_t phys_addr_copybuf;
+       void *virt_addr_copybuf;
+
+       int spdc_irq;
+       char __iomem *hwp;
+       struct clk *spdc_clk_axi;
+       struct clk *spdc_clk_pix;
+       struct regulator *display_regulator;
+       struct regulator *vcom_regulator;
+       struct regulator *v3p3_regulator;
+
+       spinlock_t lock;
+       int power_state;
+       bool powering_down;
+       int pwrdown_delay;
+       int wait_for_powerdown;
+       struct completion powerdown_compl;
+       struct mutex power_mutex;
+       bool fw_default_load;
+       bool hw_ready;
+       bool hw_initializing;
+       bool waiting_for_idle;
+       bool waiting_for_wb;
+       struct completion update_res_free;
+       struct completion lut_down;
+       struct completion init_finish;
+       struct completion update_finish;
+       struct completion updates_done;
+       struct delayed_work spdc_done_work;
+       struct workqueue_struct *spdc_submit_workqueue;
+       struct work_struct spdc_submit_work;
+       struct workqueue_struct *spdc_intr_workqueue;
+       struct work_struct spdc_intr_work;
+
+       /* FB elements related to PxP DMA */
+       struct completion pxp_tx_cmpl;
+       struct pxp_channel *pxp_chan;
+       struct pxp_config_data pxp_conf;
+       struct dma_async_tx_descriptor *txd;
+       dma_cookie_t cookie;
+       struct scatterlist sg[2];
+       struct mutex pxp_mutex; /* protects access to PxP */
+} mxc_spdc_t;
+
+#endif
index 5fb07b44063d6d741a2e81c03e9d3736caf07904..60e0aa09d0b8ce4127a3344a3509106140d678a8 100644 (file)
@@ -69,6 +69,8 @@ struct mxcfb_rect {
 
 #define GRAYSCALE_8BIT                         0x1
 #define GRAYSCALE_8BIT_INVERTED                        0x2
+#define GRAYSCALE_4BIT                          0x3
+#define GRAYSCALE_4BIT_INVERTED                 0x4
 
 #define AUTO_UPDATE_MODE_REGION_MODE           0
 #define AUTO_UPDATE_MODE_AUTOMATIC_MODE                1
index 017202f8a861c7f3da65abc9d2bcc5818b2d13bc..06fea6fbb459725fdad4eb80f115b006c25f5dfc 100644 (file)
@@ -33,4 +33,17 @@ int mxc_epdc_fb_set_pwrdown_delay(u32 pwrdown_delay,
 int mxc_epdc_get_pwrdown_delay(struct fb_info *info);
 int mxc_epdc_fb_set_upd_scheme(u32 upd_scheme, struct fb_info *info);
 
+void mxc_spdc_fb_set_waveform_modes(struct mxcfb_waveform_modes *modes,
+                                               struct fb_info *info);
+int mxc_spdc_fb_set_temperature(int temperature, struct fb_info *info);
+int mxc_spdc_fb_set_auto_update(u32 auto_mode, struct fb_info *info);
+int mxc_spdc_fb_send_update(struct mxcfb_update_data *upd_data,
+                                  struct fb_info *info);
+int mxc_spdc_fb_wait_update_complete(
+                               struct mxcfb_update_marker_data *marker_data,
+                               struct fb_info *info);
+int mxc_spdc_fb_set_pwrdown_delay(u32 pwrdown_delay,
+                                           struct fb_info *info);
+int mxc_spdc_get_pwrdown_delay(struct fb_info *info);
+int mxc_spdc_fb_set_upd_scheme(u32 upd_scheme, struct fb_info *info);
 #endif