diff options
Diffstat (limited to 'drivers/video/mxc/mxc_epdc_fb.c')
-rw-r--r-- | drivers/video/mxc/mxc_epdc_fb.c | 3187 |
1 files changed, 3187 insertions, 0 deletions
diff --git a/drivers/video/mxc/mxc_epdc_fb.c b/drivers/video/mxc/mxc_epdc_fb.c new file mode 100644 index 000000000000..e9aa9fc72f9c --- /dev/null +++ b/drivers/video/mxc/mxc_epdc_fb.c @@ -0,0 +1,3187 @@ +/* + * Copyright (C) 2010 Freescale Semiconductor, Inc. + * + * 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 STMP378X LCDIF + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/uaccess.h> +#include <linux/cpufreq.h> +#include <linux/firmware.h> +#include <linux/kthread.h> +#include <linux/dmaengine.h> +#include <linux/pxp_dma.h> +#include <linux/mxcfb.h> +#include <linux/gpio.h> +#include <linux/regulator/driver.h> + +#include "epdc_regs.h" + +/* + * Enable this define to have a default panel + * loaded during driver initialization + */ +/*#define DEFAULT_PANEL_HW_INIT*/ + +#define NUM_SCREENS 2 +#define NUM_SCREENS_X 16 +#define EPDC_NUM_LUTS 16 +#define EPDC_MAX_NUM_UPDATES 20 +#define INVALID_LUT -1 +#define TEMP_USE_DEFAULT 8 +#define INIT_UPDATE_MARKER 0x12345678 +#define PAN_UPDATE_MARKER 0x12345679 + +#define LUT_UPDATE_NONE 0 +#define LUT_UPDATE_NEW 1 +#define LUT_UPDATE_COLLISION 2 + +#define POWER_STATE_OFF 0 +#define POWER_STATE_ON 1 + +static unsigned long default_bpp = 16; +static unsigned long g_num_screens = NUM_SCREENS; + +struct update_marker_data { + u32 update_marker; + struct completion update_completion; + int lut_num; +}; + +/* 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; + struct mxcfb_update_data upd_data; /* Update parameters */ + dma_addr_t phys_addr; /* Pointer to phys address of processed Y buf */ + void *virt_addr; + u32 epdc_offs; /* Add to buffer pointer to resolve alignment */ + u32 size; + int lut_num; /* Assigned before update is processed into working buffer */ + int collision_mask; /* Set when update results in collision */ + /* Represents other LUTs that we collide with */ + struct update_marker_data *upd_marker_data; + bool is_collision; +}; + +struct mxc_epdc_fb_data { + struct fb_info info; + u32 pseudo_palette[16]; + char *fb_panel_str; + struct list_head list; + struct mxc_epdc_fb_mode *cur_mode; + struct mxc_epdc_fb_platform_data *pdata; + int blank; + ssize_t map_size; + dma_addr_t phys_start; + u32 fb_offset; + int default_bpp; + int native_width; + int native_height; + int epdc_irq; + struct device *dev; + wait_queue_head_t vsync_wait_q; + u32 vsync_count; + void *par; + int power_state; + struct clk *epdc_clk_axi; + struct clk *epdc_clk_pix; + struct regulator *display_regulator; + struct regulator *vcom_regulator; + + /* FB elements related to EPDC updates */ + bool in_init; + bool hw_ready; + bool waiting_for_idle; + u32 auto_mode; + struct update_data_list *upd_buf_queue; + struct update_data_list *upd_buf_free_list; + struct update_data_list *upd_buf_collision_list; + struct update_data_list *cur_update; + spinlock_t queue_lock; + int trt_entries; + u8 *temp_range_bounds; + struct mxcfb_waveform_modes wv_modes; + u32 *waveform_buffer_virt; + u32 waveform_buffer_phys; + u32 waveform_buffer_size; + u32 *working_buffer_virt; + u32 working_buffer_phys; + u32 working_buffer_size; + struct update_marker_data update_marker_array[EPDC_MAX_NUM_UPDATES]; + u32 lut_update_type[EPDC_NUM_LUTS]; + struct completion updates_done; + struct delayed_work epdc_done_work; + struct mutex power_mutex; + bool powering_down; + int pwrdown_delay; + + /* 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 */ +}; + +struct waveform_data_header { + unsigned int wi0; + unsigned int wi1; + unsigned int wi2; + unsigned int wi3; + unsigned int wi4; + unsigned int wi5; + unsigned int wi6; + unsigned int xwia:24; + unsigned int cs1:8; + unsigned int wmta:24; + unsigned int fvsn:8; + unsigned int luts:8; + unsigned int mc:8; + unsigned int trc:8; + unsigned int reserved0_0:8; + unsigned int eb:8; + unsigned int sb:8; + unsigned int reserved0_1:8; + unsigned int reserved0_2:8; + unsigned int reserved0_3:8; + unsigned int reserved0_4:8; + unsigned int reserved0_5:8; + unsigned int cs2:8; +}; + +struct mxcfb_waveform_data_file { + struct waveform_data_header wdh; + u32 *data; /* Temperature Range Table + Waveform Data */ +}; + +void __iomem *epdc_base; + +/* forward declaration */ +static int mxc_epdc_fb_blank(int blank, struct fb_info *info); +static int mxc_epdc_fb_init_hw(struct fb_info *info); +static int pxp_process_update(struct mxc_epdc_fb_data *fb_data, + struct mxcfb_rect *update_region, + int x_start_offs); +static int pxp_complete_update(struct mxc_epdc_fb_data *fb_data, u32 *hist_stat); + +static void draw_mode0(struct mxc_epdc_fb_data *fb_data); + +#ifdef DEBUG +static void dump_pxp_config(struct mxc_epdc_fb_data *fb_data, + struct pxp_config_data *pxp_conf) +{ + dev_err(fb_data->dev, "S0 fmt 0x%x", + pxp_conf->s0_param.pixel_fmt); + dev_err(fb_data->dev, "S0 width 0x%x", + pxp_conf->s0_param.width); + dev_err(fb_data->dev, "S0 height 0x%x", + pxp_conf->s0_param.height); + dev_err(fb_data->dev, "S0 ckey 0x%x", + pxp_conf->s0_param.color_key); + dev_err(fb_data->dev, "S0 ckey en 0x%x", + pxp_conf->s0_param.color_key_enable); + + dev_err(fb_data->dev, "OL0 combine en 0x%x", + pxp_conf->ol_param[0].combine_enable); + dev_err(fb_data->dev, "OL0 fmt 0x%x", + pxp_conf->ol_param[0].pixel_fmt); + dev_err(fb_data->dev, "OL0 width 0x%x", + pxp_conf->ol_param[0].width); + dev_err(fb_data->dev, "OL0 height 0x%x", + pxp_conf->ol_param[0].height); + dev_err(fb_data->dev, "OL0 ckey 0x%x", + pxp_conf->ol_param[0].color_key); + dev_err(fb_data->dev, "OL0 ckey en 0x%x", + pxp_conf->ol_param[0].color_key_enable); + dev_err(fb_data->dev, "OL0 alpha 0x%x", + pxp_conf->ol_param[0].global_alpha); + dev_err(fb_data->dev, "OL0 alpha en 0x%x", + pxp_conf->ol_param[0].global_alpha_enable); + dev_err(fb_data->dev, "OL0 local alpha en 0x%x", + pxp_conf->ol_param[0].local_alpha_enable); + + dev_err(fb_data->dev, "Out fmt 0x%x", + pxp_conf->out_param.pixel_fmt); + dev_err(fb_data->dev, "Out width 0x%x", + pxp_conf->out_param.width); + dev_err(fb_data->dev, "Out height 0x%x", + pxp_conf->out_param.height); + + dev_err(fb_data->dev, + "drect left 0x%x right 0x%x width 0x%x height 0x%x", + pxp_conf->proc_data.drect.left, pxp_conf->proc_data.drect.top, + pxp_conf->proc_data.drect.width, + pxp_conf->proc_data.drect.height); + dev_err(fb_data->dev, + "srect left 0x%x right 0x%x width 0x%x height 0x%x", + pxp_conf->proc_data.srect.left, pxp_conf->proc_data.srect.top, + pxp_conf->proc_data.srect.width, + pxp_conf->proc_data.srect.height); + dev_err(fb_data->dev, "Scaling en 0x%x", pxp_conf->proc_data.scaling); + dev_err(fb_data->dev, "HFlip en 0x%x", pxp_conf->proc_data.hflip); + dev_err(fb_data->dev, "VFlip en 0x%x", pxp_conf->proc_data.vflip); + dev_err(fb_data->dev, "Rotation 0x%x", pxp_conf->proc_data.rotate); + dev_err(fb_data->dev, "BG Color 0x%x", pxp_conf->proc_data.bgcolor); +} + +static void dump_epdc_reg(void) +{ + printk(KERN_DEBUG "\n\n"); + printk(KERN_DEBUG "EPDC_CTRL 0x%x\n", __raw_readl(EPDC_CTRL)); + printk(KERN_DEBUG "EPDC_WVADDR 0x%x\n", __raw_readl(EPDC_WVADDR)); + printk(KERN_DEBUG "EPDC_WB_ADDR 0x%x\n", __raw_readl(EPDC_WB_ADDR)); + printk(KERN_DEBUG "EPDC_RES 0x%x\n", __raw_readl(EPDC_RES)); + printk(KERN_DEBUG "EPDC_FORMAT 0x%x\n", __raw_readl(EPDC_FORMAT)); + printk(KERN_DEBUG "EPDC_FIFOCTRL 0x%x\n", __raw_readl(EPDC_FIFOCTRL)); + printk(KERN_DEBUG "EPDC_UPD_ADDR 0x%x\n", __raw_readl(EPDC_UPD_ADDR)); + printk(KERN_DEBUG "EPDC_UPD_FIXED 0x%x\n", __raw_readl(EPDC_UPD_FIXED)); + printk(KERN_DEBUG "EPDC_UPD_CORD 0x%x\n", __raw_readl(EPDC_UPD_CORD)); + printk(KERN_DEBUG "EPDC_UPD_SIZE 0x%x\n", __raw_readl(EPDC_UPD_SIZE)); + printk(KERN_DEBUG "EPDC_UPD_CTRL 0x%x\n", __raw_readl(EPDC_UPD_CTRL)); + printk(KERN_DEBUG "EPDC_TEMP 0x%x\n", __raw_readl(EPDC_TEMP)); + printk(KERN_DEBUG "EPDC_TCE_CTRL 0x%x\n", __raw_readl(EPDC_TCE_CTRL)); + printk(KERN_DEBUG "EPDC_TCE_SDCFG 0x%x\n", __raw_readl(EPDC_TCE_SDCFG)); + printk(KERN_DEBUG "EPDC_TCE_GDCFG 0x%x\n", __raw_readl(EPDC_TCE_GDCFG)); + printk(KERN_DEBUG "EPDC_TCE_HSCAN1 0x%x\n", __raw_readl(EPDC_TCE_HSCAN1)); + printk(KERN_DEBUG "EPDC_TCE_HSCAN2 0x%x\n", __raw_readl(EPDC_TCE_HSCAN2)); + printk(KERN_DEBUG "EPDC_TCE_VSCAN 0x%x\n", __raw_readl(EPDC_TCE_VSCAN)); + printk(KERN_DEBUG "EPDC_TCE_OE 0x%x\n", __raw_readl(EPDC_TCE_OE)); + printk(KERN_DEBUG "EPDC_TCE_POLARITY 0x%x\n", __raw_readl(EPDC_TCE_POLARITY)); + printk(KERN_DEBUG "EPDC_TCE_TIMING1 0x%x\n", __raw_readl(EPDC_TCE_TIMING1)); + printk(KERN_DEBUG "EPDC_TCE_TIMING2 0x%x\n", __raw_readl(EPDC_TCE_TIMING2)); + printk(KERN_DEBUG "EPDC_TCE_TIMING3 0x%x\n", __raw_readl(EPDC_TCE_TIMING3)); + printk(KERN_DEBUG "EPDC_IRQ_MASK 0x%x\n", __raw_readl(EPDC_IRQ_MASK)); + printk(KERN_DEBUG "EPDC_IRQ 0x%x\n", __raw_readl(EPDC_IRQ)); + printk(KERN_DEBUG "EPDC_STATUS_LUTS 0x%x\n", __raw_readl(EPDC_STATUS_LUTS)); + printk(KERN_DEBUG "EPDC_STATUS_NEXTLUT 0x%x\n", __raw_readl(EPDC_STATUS_NEXTLUT)); + printk(KERN_DEBUG "EPDC_STATUS_COL 0x%x\n", __raw_readl(EPDC_STATUS_COL)); + printk(KERN_DEBUG "EPDC_STATUS 0x%x\n", __raw_readl(EPDC_STATUS)); + printk(KERN_DEBUG "EPDC_DEBUG 0x%x\n", __raw_readl(EPDC_DEBUG)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT0 0x%x\n", __raw_readl(EPDC_DEBUG_LUT0)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT1 0x%x\n", __raw_readl(EPDC_DEBUG_LUT1)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT2 0x%x\n", __raw_readl(EPDC_DEBUG_LUT2)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT3 0x%x\n", __raw_readl(EPDC_DEBUG_LUT3)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT4 0x%x\n", __raw_readl(EPDC_DEBUG_LUT4)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT5 0x%x\n", __raw_readl(EPDC_DEBUG_LUT5)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT6 0x%x\n", __raw_readl(EPDC_DEBUG_LUT6)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT7 0x%x\n", __raw_readl(EPDC_DEBUG_LUT7)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT8 0x%x\n", __raw_readl(EPDC_DEBUG_LUT8)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT9 0x%x\n", __raw_readl(EPDC_DEBUG_LUT9)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT10 0x%x\n", __raw_readl(EPDC_DEBUG_LUT10)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT11 0x%x\n", __raw_readl(EPDC_DEBUG_LUT11)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT12 0x%x\n", __raw_readl(EPDC_DEBUG_LUT12)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT13 0x%x\n", __raw_readl(EPDC_DEBUG_LUT13)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT14 0x%x\n", __raw_readl(EPDC_DEBUG_LUT14)); + printk(KERN_DEBUG "EPDC_DEBUG_LUT15 0x%x\n", __raw_readl(EPDC_DEBUG_LUT15)); + printk(KERN_DEBUG "EPDC_GPIO 0x%x\n", __raw_readl(EPDC_GPIO)); + printk(KERN_DEBUG "EPDC_VERSION 0x%x\n", __raw_readl(EPDC_VERSION)); + printk(KERN_DEBUG "\n\n"); +} + +static void dump_update_data(struct device *dev, + struct update_data_list *upd_data_list) +{ + dev_err(dev, + "X = %d, Y = %d, Width = %d, Height = %d, WaveMode = %d, LUT = %d, Coll Mask = %d\n", + upd_data_list->upd_data.update_region.left, + upd_data_list->upd_data.update_region.top, + upd_data_list->upd_data.update_region.width, + upd_data_list->upd_data.update_region.height, + upd_data_list->upd_data.waveform_mode, upd_data_list->lut_num, + upd_data_list->collision_mask); +} + +static void dump_collision_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_err(fb_data->dev, "Collision List:\n"); + if (list_empty(&fb_data->upd_buf_collision_list->list)) + dev_err(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_collision_list->list, list) { + dev_err(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); + dump_update_data(fb_data->dev, plist); + } +} + +static void dump_free_list(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_err(fb_data->dev, "Free List:\n"); + if (list_empty(&fb_data->upd_buf_free_list->list)) + dev_err(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_free_list->list, list) { + dev_err(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); + dump_update_data(fb_data->dev, plist); + } +} + +static void dump_queue(struct mxc_epdc_fb_data *fb_data) +{ + struct update_data_list *plist; + + dev_err(fb_data->dev, "Queue:\n"); + if (list_empty(&fb_data->upd_buf_queue->list)) + dev_err(fb_data->dev, "Empty"); + list_for_each_entry(plist, &fb_data->upd_buf_queue->list, list) { + dev_err(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", + (u32)plist->virt_addr, plist->phys_addr); + dump_update_data(fb_data->dev, plist); + } +} + +static void dump_all_updates(struct mxc_epdc_fb_data *fb_data) +{ + dump_free_list(fb_data); + dump_queue(fb_data); + dump_collision_list(fb_data); + dev_err(fb_data->dev, "Current update being processed:\n"); + if (fb_data->cur_update == NULL) + dev_err(fb_data->dev, "No current update\n"); + else + dump_update_data(fb_data->dev, fb_data->cur_update); +} +#else +static inline void dump_pxp_config(struct mxc_epdc_fb_data *fb_data, + struct pxp_config_data *pxp_conf) {} +static inline void dump_epdc_reg(void) {} +static inline void dump_update_data(struct device *dev, + struct update_data_list *upd_data_list) {} +static inline void dump_collision_list(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_free_list(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_queue(struct mxc_epdc_fb_data *fb_data) {} +static inline void dump_all_updates(struct mxc_epdc_fb_data *fb_data) {} + +#endif + + +/******************************************************** + * Start Low-Level EPDC Functions + ********************************************************/ + +static inline void epdc_lut_complete_intr(u32 lut_num, bool enable) +{ + if (enable) + __raw_writel(1 << lut_num, EPDC_IRQ_MASK_SET); + else + __raw_writel(1 << lut_num, EPDC_IRQ_MASK_CLEAR); +} + +static inline void epdc_working_buf_intr(bool enable) +{ + if (enable) + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ, EPDC_IRQ_MASK_SET); + else + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ, EPDC_IRQ_MASK_CLEAR); +} + +static inline void epdc_clear_working_buf_irq(void) +{ + __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ | EPDC_IRQ_LUT_COL_IRQ, + EPDC_IRQ_CLEAR); +} + +static inline void epdc_set_temp(u32 temp) +{ + __raw_writel(temp, EPDC_TEMP); +} + +static inline void epdc_set_screen_res(u32 width, u32 height) +{ + u32 val = (height << EPDC_RES_VERTICAL_OFFSET) | width; + __raw_writel(val, EPDC_RES); +} + +static inline void epdc_set_update_addr(u32 addr) +{ + __raw_writel(addr, EPDC_UPD_ADDR); +} + +static inline void epdc_set_update_coord(u32 x, u32 y) +{ + u32 val = (y << EPDC_UPD_CORD_YCORD_OFFSET) | x; + __raw_writel(val, EPDC_UPD_CORD); +} + +static inline void epdc_set_update_dimensions(u32 width, u32 height) +{ + u32 val = (height << EPDC_UPD_SIZE_HEIGHT_OFFSET) | width; + __raw_writel(val, EPDC_UPD_SIZE); +} + +static void epdc_submit_update(u32 lut_num, u32 waveform_mode, u32 update_mode, + bool use_test_mode, u32 np_val) +{ + u32 reg_val = 0; + + if (use_test_mode) { + reg_val |= + ((np_val << EPDC_UPD_FIXED_FIXNP_OFFSET) & + EPDC_UPD_FIXED_FIXNP_MASK) | EPDC_UPD_FIXED_FIXNP_EN; + + __raw_writel(reg_val, EPDC_UPD_FIXED); + + reg_val = EPDC_UPD_CTRL_USE_FIXED; + } else { + __raw_writel(reg_val, EPDC_UPD_FIXED); + } + + reg_val |= + ((lut_num << EPDC_UPD_CTRL_LUT_SEL_OFFSET) & + EPDC_UPD_CTRL_LUT_SEL_MASK) | + ((waveform_mode << EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET) & + EPDC_UPD_CTRL_WAVEFORM_MODE_MASK) | + update_mode; + + __raw_writel(reg_val, EPDC_UPD_CTRL); +} + +static inline bool epdc_is_lut_complete(u32 lut_num) +{ + u32 val = __raw_readl(EPDC_IRQ); + bool is_compl = val & (1 << lut_num) ? true : false; + + return is_compl; +} + +static inline void epdc_clear_lut_complete_irq(u32 lut_num) +{ + __raw_writel(1 << lut_num, EPDC_IRQ_CLEAR); +} + +static inline bool epdc_is_lut_active(u32 lut_num) +{ + u32 val = __raw_readl(EPDC_STATUS_LUTS); + bool is_active = val & (1 << lut_num) ? true : false; + + return is_active; +} + +static inline bool epdc_any_luts_active(void) +{ + bool any_active = __raw_readl(EPDC_STATUS_LUTS) ? true : false; + + return any_active; +} + +static inline bool epdc_any_luts_available(void) +{ + bool luts_available = + (__raw_readl(EPDC_STATUS_NEXTLUT) & + EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID) ? true : false; + return luts_available; +} + +static inline int epdc_get_next_lut(void) +{ + u32 val = + __raw_readl(EPDC_STATUS_NEXTLUT) & + EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK; + return val; +} + +static inline bool epdc_is_working_buffer_busy(void) +{ + u32 val = __raw_readl(EPDC_STATUS); + bool is_busy = (val & EPDC_STATUS_WB_BUSY) ? true : false; + + return is_busy; +} + +static inline bool epdc_is_working_buffer_complete(void) +{ + u32 val = __raw_readl(EPDC_IRQ); + bool is_compl = (val & EPDC_IRQ_WB_CMPLT_IRQ) ? true : false; + + return is_compl; +} + +static inline bool epdc_is_collision(void) +{ + u32 val = __raw_readl(EPDC_IRQ); + return (val & EPDC_IRQ_LUT_COL_IRQ) ? true : false; +} + +static inline int epdc_get_colliding_luts(void) +{ + u32 val = __raw_readl(EPDC_STATUS_COL); + return val; +} + +static void epdc_set_horizontal_timing(u32 horiz_start, u32 horiz_end, + u32 hsync_width, u32 hsync_line_length) +{ + u32 reg_val = + ((hsync_width << EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET) & + EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK) + | ((hsync_line_length << EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET) & + EPDC_TCE_HSCAN1_LINE_SYNC_MASK); + __raw_writel(reg_val, EPDC_TCE_HSCAN1); + + reg_val = + ((horiz_start << EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET) & + EPDC_TCE_HSCAN2_LINE_BEGIN_MASK) + | ((horiz_end << EPDC_TCE_HSCAN2_LINE_END_OFFSET) & + EPDC_TCE_HSCAN2_LINE_END_MASK); + __raw_writel(reg_val, EPDC_TCE_HSCAN2); +} + +static void epdc_set_vertical_timing(u32 vert_start, u32 vert_end, + u32 vsync_width) +{ + u32 reg_val = + ((vert_start << EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET) & + EPDC_TCE_VSCAN_FRAME_BEGIN_MASK) + | ((vert_end << EPDC_TCE_VSCAN_FRAME_END_OFFSET) & + EPDC_TCE_VSCAN_FRAME_END_MASK) + | ((vsync_width << EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET) & + EPDC_TCE_VSCAN_FRAME_SYNC_MASK); + __raw_writel(reg_val, EPDC_TCE_VSCAN); +} + +void epdc_init_settings(struct mxc_epdc_fb_data *fb_data) +{ + struct mxc_epdc_fb_mode *epdc_mode = fb_data->cur_mode; + struct fb_var_screeninfo *screeninfo = &fb_data->info.var; + u32 reg_val; + int num_ce; + + /* Reset */ + __raw_writel(EPDC_CTRL_SFTRST, EPDC_CTRL_SET); + while (!(__raw_readl(EPDC_CTRL) & EPDC_CTRL_CLKGATE)) + ; + __raw_writel(EPDC_CTRL_SFTRST, EPDC_CTRL_CLEAR); + + /* Enable clock gating (clear to enable) */ + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_CLEAR); + while (__raw_readl(EPDC_CTRL) & (EPDC_CTRL_SFTRST | EPDC_CTRL_CLKGATE)) + ; + + /* EPDC_CTRL */ + reg_val = __raw_readl(EPDC_CTRL); + reg_val &= ~EPDC_CTRL_UPD_DATA_SWIZZLE_MASK; + reg_val |= EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP; + reg_val &= ~EPDC_CTRL_LUT_DATA_SWIZZLE_MASK; + reg_val |= EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP; + __raw_writel(reg_val, EPDC_CTRL_SET); + + /* EPDC_FORMAT - 2bit TFT and 4bit Buf pixel format */ + reg_val = EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT + | EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N + | ((0x0 << EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET) & + EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK); + __raw_writel(reg_val, EPDC_FORMAT); + + /* EPDC_FIFOCTRL (disabled) */ + reg_val = + ((100 << EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK) + | ((200 << EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK) + | ((100 << EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET) & + EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK); + __raw_writel(reg_val, EPDC_FIFOCTRL); + + /* EPDC_TEMP - 8 for room temperature */ + epdc_set_temp(8); + + /* EPDC_RES */ + epdc_set_screen_res(epdc_mode->vmode->xres, epdc_mode->vmode->yres); + + /* + * EPDC_TCE_CTRL + * VSCAN_HOLDOFF = 4 + * VCOM_MODE = MANUAL + * VCOM_VAL = 0 + * DDR_MODE = DISABLED + * LVDS_MODE_CE = DISABLED + * LVDS_MODE = DISABLED + * DUAL_SCAN = DISABLED + * SDDO_WIDTH = 8bit + * PIXELS_PER_SDCLK = 4 + */ + reg_val = + ((epdc_mode->vscan_holdoff << EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET) & + EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK) + | EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4; + __raw_writel(reg_val, EPDC_TCE_CTRL); + + /* EPDC_TCE_HSCAN */ + epdc_set_horizontal_timing(screeninfo->left_margin, + screeninfo->right_margin, + screeninfo->hsync_len, + screeninfo->hsync_len); + + /* EPDC_TCE_VSCAN */ + epdc_set_vertical_timing(screeninfo->upper_margin, + screeninfo->lower_margin, + screeninfo->vsync_len); + + /* EPDC_TCE_OE */ + reg_val = + ((epdc_mode->sdoed_width << EPDC_TCE_OE_SDOED_WIDTH_OFFSET) & + EPDC_TCE_OE_SDOED_WIDTH_MASK) + | ((epdc_mode->sdoed_delay << EPDC_TCE_OE_SDOED_DLY_OFFSET) & + EPDC_TCE_OE_SDOED_DLY_MASK) + | ((epdc_mode->sdoez_width << EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET) & + EPDC_TCE_OE_SDOEZ_WIDTH_MASK) + | ((epdc_mode->sdoez_delay << EPDC_TCE_OE_SDOEZ_DLY_OFFSET) & + EPDC_TCE_OE_SDOEZ_DLY_MASK); + __raw_writel(reg_val, EPDC_TCE_OE); + + /* EPDC_TCE_TIMING1 */ + __raw_writel(0x0, EPDC_TCE_TIMING1); + + /* EPDC_TCE_TIMING2 */ + reg_val = + ((epdc_mode->gdclk_hp_offs << EPDC_TCE_TIMING2_GDCLK_HP_OFFSET) & + EPDC_TCE_TIMING2_GDCLK_HP_MASK) + | ((epdc_mode->gdsp_offs << EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET) & + EPDC_TCE_TIMING2_GDSP_OFFSET_MASK); + __raw_writel(reg_val, EPDC_TCE_TIMING2); + + /* EPDC_TCE_TIMING3 */ + reg_val = + ((epdc_mode->gdoe_offs << EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET) & + EPDC_TCE_TIMING3_GDOE_OFFSET_MASK) + | ((epdc_mode->gdclk_offs << EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET) & + EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK); + __raw_writel(reg_val, EPDC_TCE_TIMING3); + + /* + * EPDC_TCE_SDCFG + * SDCLK_HOLD = 1 + * SDSHR = 1 + * NUM_CE = 1 + * SDDO_REFORMAT = FLIP_PIXELS + * SDDO_INVERT = DISABLED + * PIXELS_PER_CE = display horizontal resolution + */ + num_ce = epdc_mode->num_ce; + if (num_ce == 0) + num_ce = 1; + reg_val = EPDC_TCE_SDCFG_SDCLK_HOLD | EPDC_TCE_SDCFG_SDSHR + | ((num_ce << EPDC_TCE_SDCFG_NUM_CE_OFFSET) & + EPDC_TCE_SDCFG_NUM_CE_MASK) + | EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS + | ((epdc_mode->vmode->xres/num_ce << EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET) & + EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK); + __raw_writel(reg_val, EPDC_TCE_SDCFG); + + /* + * EPDC_TCE_GDCFG + * GDRL = 1 + * GDOE_MODE = 0; + * GDSP_MODE = 0; + */ + reg_val = EPDC_TCE_SDCFG_GDRL; + __raw_writel(reg_val, EPDC_TCE_GDCFG); + + /* + * EPDC_TCE_POLARITY + * SDCE_POL = ACTIVE LOW + * SDLE_POL = ACTIVE HIGH + * SDOE_POL = ACTIVE HIGH + * GDOE_POL = ACTIVE HIGH + * GDSP_POL = ACTIVE LOW + */ + reg_val = EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH + | EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH + | EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH; + __raw_writel(reg_val, EPDC_TCE_POLARITY); + + /* EPDC_IRQ_MASK */ + __raw_writel(EPDC_IRQ_TCE_UNDERRUN_IRQ, EPDC_IRQ_MASK); + + /* + * EPDC_GPIO + * PWRCOM = ? + * PWRCTRL = ? + * BDR = ? + */ + reg_val = ((0 << EPDC_GPIO_PWRCTRL_OFFSET) & EPDC_GPIO_PWRCTRL_MASK) + | ((0 << EPDC_GPIO_BDR_OFFSET) & EPDC_GPIO_BDR_MASK); + __raw_writel(reg_val, EPDC_GPIO); +} + +static void epdc_powerup(struct mxc_epdc_fb_data *fb_data) +{ + 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, "EPDC Powerup\n"); + + /* Enable pins used by EPDC */ + if (fb_data->pdata->enable_pins) + fb_data->pdata->enable_pins(); + + /* Enable clocks to EPDC */ + clk_enable(fb_data->epdc_clk_axi); + clk_enable(fb_data->epdc_clk_pix); + + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_CLEAR); + + /* Enable power to the EPD panel */ + regulator_enable(fb_data->display_regulator); + regulator_enable(fb_data->vcom_regulator); + + fb_data->power_state = POWER_STATE_ON; + + mutex_unlock(&fb_data->power_mutex); +} + +static void epdc_powerdown(struct mxc_epdc_fb_data *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, "EPDC Powerdown\n"); + + /* Disable power to the EPD panel */ + regulator_disable(fb_data->vcom_regulator); + regulator_disable(fb_data->display_regulator); + + /* Disable clocks to EPDC */ + __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_SET); + clk_disable(fb_data->epdc_clk_pix); + clk_disable(fb_data->epdc_clk_axi); + + /* Disable pins used by EPDC (to prevent leakage current) */ + if (fb_data->pdata->disable_pins) + fb_data->pdata->disable_pins(); + + fb_data->power_state = POWER_STATE_OFF; + fb_data->powering_down = false; + + mutex_unlock(&fb_data->power_mutex); +} + +static void epdc_init_sequence(struct mxc_epdc_fb_data *fb_data) +{ + /* Initialize EPDC, passing pointer to EPDC registers */ + epdc_init_settings(fb_data); + __raw_writel(fb_data->waveform_buffer_phys, EPDC_WVADDR); + __raw_writel(fb_data->working_buffer_phys, EPDC_WB_ADDR); + epdc_powerup(fb_data); + draw_mode0(fb_data); + epdc_powerdown(fb_data); +} + +static int mxc_epdc_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + + if (offset < info->fix.smem_len) { + /* mapping framebuffer memory */ + len = info->fix.smem_len - offset; + vma->vm_pgoff = (info->fix.smem_start + offset) >> PAGE_SHIFT; + } else + return -EINVAL; + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) + return -EINVAL; + + /* make buffers bufferable */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + vma->vm_flags |= VM_IO | VM_RESERVED; + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(info->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + } + + return 0; +} + +static int mxc_epdc_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; + /* + * Program hardware... do anything you want with transp + */ + + /* 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; +} + +static void adjust_coordinates(struct mxc_epdc_fb_data *fb_data, struct mxcfb_rect *update_region) +{ + struct fb_var_screeninfo *screeninfo = &fb_data->info.var; + u32 rotation = fb_data->info.var.rotate; + u32 temp; + + 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 = screeninfo->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 = screeninfo->yres - (update_region->top + update_region->height); + update_region->left = screeninfo->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 = screeninfo->xres - (temp + update_region->width); + temp = update_region->width; + update_region->width = update_region->height; + update_region->height = temp; + break; + } +} + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxc_epdc_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; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +/* + * This routine actually sets the video mode. It's in here where we + * the hardware state info->par and fix which can be affected by the + * change in par. For this driver it doesn't do much. + * + */ +static int mxc_epdc_fb_set_par(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; + struct pxp_proc_data *proc_data = &pxp_conf->proc_data; + struct fb_var_screeninfo *screeninfo = &fb_data->info.var; + int i; + int ret; + + /* + * 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_GREY; + if (screeninfo->grayscale == GRAYSCALE_8BIT_INVERTED) + proc_data->lut_transform = PXP_LUT_INVERT; + } 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_GREY; + + /* + * If HW not yet initialized, check to see if we are being sent + * an initialization request. + */ + if (!fb_data->hw_ready) { + for (i = 0; i < fb_data->pdata->num_modes; i++) { + struct fb_videomode *vmode = + fb_data->pdata->epdc_mode[i].vmode; + /* Check resolution for a match + with supported panel types */ + if ((screeninfo->xres != vmode->xres) || + (screeninfo->yres != vmode->yres)) + continue; + + fb_data->cur_mode = &fb_data->pdata->epdc_mode[i]; + + /* Found a match - Grab timing params */ + screeninfo->left_margin = vmode->left_margin; + screeninfo->right_margin = vmode->right_margin; + screeninfo->upper_margin = vmode->upper_margin; + screeninfo->lower_margin = vmode->lower_margin; + screeninfo->hsync_len = vmode->hsync_len; + screeninfo->vsync_len = vmode->vsync_len; + + /* Initialize EPDC settings and init panel */ + ret = + mxc_epdc_fb_init_hw((struct fb_info *)fb_data); + if (ret) { + dev_err(fb_data->dev, "Failed to load panel waveform data\n"); + return ret; + } + + break; + } + } + + mxc_epdc_fb_set_fix(info); + + return 0; +} + +static int mxc_epdc_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)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 = default_bpp; + + switch (var->bits_per_pixel) { + case 8: + if (var->grayscale != 0) { + /* + * For 8-bit grayscale, R, G, and B offset are equal. + * + */ + var->red.length = 8; + var->red.offset = 0; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 0; + 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; + } 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) * g_num_screens; + + var->height = -1; + var->width = -1; + + return 0; +} + +static int mxc_epdc_fb_get_temp_index(struct mxc_epdc_fb_data *fb_data, int temp) +{ + int i; + int index = -1; + + if (fb_data->trt_entries == 0) { + dev_err(fb_data->dev, + "No TRT exists...using default temp index\n"); + return TEMP_USE_DEFAULT; + } + + /* Search temperature ranges for a match */ + for (i = 0; i < fb_data->trt_entries - 1; i++) { + if ((temp >= fb_data->temp_range_bounds[i]) + && (temp < fb_data->temp_range_bounds[i+1])) { + index = i; + break; + } + } + + if (index < 0) { + dev_err(fb_data->dev, + "No TRT index match...using default temp index\n"); + return TEMP_USE_DEFAULT; + } + + dev_dbg(fb_data->dev, "Using temperature index %d\n", index); + + return index; +} + +static int mxc_epdc_fb_set_temperature(int temperature, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + int temp_index; + + if (temperature != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, temperature); + epdc_set_temp(temp_index); + } + + return 0; +} + +static int mxc_epdc_fb_set_auto_update(u32 auto_mode, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + + 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; +} + +static int mxc_epdc_fb_send_update(struct mxcfb_update_data *upd_data, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + struct update_data_list *upd_data_list = NULL; + struct mxcfb_rect *screen_upd_region; /* Region on screen to update */ + struct mxcfb_rect *src_upd_region; /* Region of src buffer for update */ + struct mxcfb_rect pxp_upd_region; + u32 src_width; + unsigned long flags; + int i; + u32 offset_from_8, bytes_per_pixel; + u32 post_rotation_xcoord, post_rotation_ycoord, width_pxp_blocks; + u32 pxp_input_offs, pxp_output_offs, pxp_output_shift; + int x_start_offs = 0; + u32 hist_stat = 0; + int temp_index; + bool wait_for_power = false; + + int ret; + + /* Has EPDC HW been initialized? */ + if (!fb_data->hw_ready) { + dev_err(fb_data->dev, "Display HW not properly initialized. Aborting update.\n"); + return -EPERM; + } + + /* Check validity of update params */ + if ((upd_data->update_mode != UPDATE_MODE_PARTIAL) && + (upd_data->update_mode != UPDATE_MODE_FULL)) { + dev_err(fb_data->dev, + "Update mode 0x%x is invalid. Aborting update.\n", + upd_data->update_mode); + return -EINVAL; + } + if ((upd_data->waveform_mode > 255) && + (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->info.var.xres) || + (upd_data->update_region.top + upd_data->update_region.height > fb_data->info.var.yres)) { + dev_err(fb_data->dev, + "Update region is outside bounds of framebuffer. Aborting update.\n"); + return -EINVAL; + } + if (upd_data->use_alt_buffer && + ((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; + } + + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* + * 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) || (fb_data->blank != FB_BLANK_UNBLANK)) { + dev_dbg(fb_data->dev, "EPDC not active. Update request abort.\n"); + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return -EPERM; + } + + /* + * Get available intermediate (PxP output) buffer to hold + * processed update region + */ + if (list_empty(&fb_data->upd_buf_free_list->list)) { + dev_err(fb_data->dev, "No free intermediate buffers available.\n"); + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return -ENOMEM; + } + + /* Grab first available buffer and delete it from the free list */ + upd_data_list = + list_entry(fb_data->upd_buf_free_list->list.next, + struct update_data_list, list); + + list_del_init(&upd_data_list->list); + + /* + * We can release lock on queues now + * that we have grabbed the one we need + */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + /* copy update parameters to the current update data object */ + memcpy(&upd_data_list->upd_data, upd_data, + sizeof(struct mxcfb_update_data)); + memcpy(&upd_data_list->upd_data.update_region, &upd_data->update_region, + sizeof(struct mxcfb_rect)); + + /* + * Hold on to original screen update region, which we + * will ultimately use when telling EPDC where to update on panel + */ + screen_upd_region = &upd_data_list->upd_data.update_region; + + /* + * Gotta do a whole bunch of buffer ptr manipulation to + * work around HW restrictions for PxP & EPDC + */ + + /* + * Are we using FB or an alternate (overlay) + * buffer for source of update? + */ + if (upd_data->use_alt_buffer) { + src_width = upd_data->alt_buffer_data.width; + src_upd_region = &upd_data->alt_buffer_data.alt_update_region; + } else { + src_width = fb_data->info.var.xres_virtual; + src_upd_region = screen_upd_region; + } + + /* + * Compute buffer offset to account for + * PxP limitation (must read 8x8 pixel blocks) + */ + offset_from_8 = src_upd_region->left & 0x7; + bytes_per_pixel = fb_data->info.var.bits_per_pixel/8; + if ((offset_from_8 * fb_data->info.var.bits_per_pixel/8 % 4) != 0) { + /* 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 should change to reflect relative position to input ptr */ + pxp_upd_region.top = 0; + pxp_upd_region.left = (offset_from_8 & 0x3) % bytes_per_pixel; + } else { + pxp_input_offs = + (src_upd_region->top * src_width + src_upd_region->left) + * bytes_per_pixel; + /* Update region should change to reflect relative position to input ptr */ + pxp_upd_region.top = 0; + pxp_upd_region.left = 0; + } + + /* + * We want PxP processing region to start at the first pixel + * that we have to process in each row, but PxP alignment + * restricts the input mem address to be 32-bit aligned + * + * We work around this by using x_start_offs to offset + * to offset from our starting pixel location to the + * first pixel that is in the relevant update region + * for each row. + */ + x_start_offs = pxp_upd_region.left & 0x7; + + /* Update region to meet 8x8 pixel requirement */ + pxp_upd_region.width = ALIGN(src_upd_region->width, 8); + pxp_upd_region.height = ALIGN(src_upd_region->height, 8); + pxp_upd_region.top &= ~0x7; + pxp_upd_region.left &= ~0x7; + + switch (fb_data->info.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; + } + + pxp_output_offs = post_rotation_ycoord * width_pxp_blocks + + post_rotation_xcoord; + + pxp_output_shift = ALIGN(pxp_output_offs, 8) - pxp_output_offs; + + upd_data_list->epdc_offs = pxp_output_offs + pxp_output_shift; + + /* Source address either comes from alternate buffer + provided in update data, or from the framebuffer. */ + if (upd_data->use_alt_buffer) + sg_dma_address(&fb_data->sg[0]) = + 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 */ + sg_dma_address(&fb_data->sg[1]) = upd_data_list->phys_addr + pxp_output_offs; + sg_set_page(&fb_data->sg[1], virt_to_page(upd_data_list->virt_addr), + upd_data_list->size, + offset_in_page(upd_data_list->virt_addr)); + + mutex_lock(&fb_data->pxp_mutex); + + /* This is a blocking call, so upon return PxP tx should be done */ + ret = pxp_process_update(fb_data, &pxp_upd_region, + x_start_offs); + if (ret) { + dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); + mutex_unlock(&fb_data->pxp_mutex); + return ret; + } + + mutex_unlock(&fb_data->pxp_mutex); + + /* If needed, enable EPDC HW while ePxP is processing */ + if ((fb_data->power_state == POWER_STATE_OFF) + || fb_data->powering_down) { + wait_for_power = true; + epdc_powerup(fb_data); + } + + mutex_lock(&fb_data->pxp_mutex); + + /* 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; + } + + mutex_unlock(&fb_data->pxp_mutex); + + /* Grab lock for queue manipulation and update submission */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* Update coordinates for rotation */ + adjust_coordinates(fb_data, &upd_data_list->upd_data.update_region); + + /* Update waveform mode from PxP histogram results */ + if (upd_data_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO) { + if (hist_stat & 0x1) + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_du; + else if (hist_stat & 0x2) + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc4; + else if (hist_stat & 0x4) + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc8; + else if (hist_stat & 0x8) + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc16; + else + upd_data_list->upd_data.waveform_mode = + fb_data->wv_modes.mode_gc32; + + /* Pass selected waveform mode back to user */ + upd_data->waveform_mode = upd_data_list->upd_data.waveform_mode; + + dev_dbg(fb_data->dev, "hist_stat = 0x%x, new waveform = 0x%x\n", + hist_stat, upd_data_list->upd_data.waveform_mode); + } + + /* If marker specified, associate it with a completion */ + if (upd_data->update_marker != 0) { + /* Find available update marker and set it up */ + for (i = 0; i < EPDC_MAX_NUM_UPDATES; i++) { + /* Marker value set to 0 signifies it is not currently in use */ + if (fb_data->update_marker_array[i].update_marker == 0) { + fb_data->update_marker_array[i].update_marker = upd_data->update_marker; + init_completion(&fb_data->update_marker_array[i].update_completion); + upd_data_list->upd_marker_data = &fb_data->update_marker_array[i]; + break; + } + } + } else { + if (upd_data_list->upd_marker_data) + upd_data_list->upd_marker_data->update_marker = 0; + } + + upd_data_list->is_collision = false; + + /* + * 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) || !epdc_any_luts_available()) { + /* Add processed Y buffer to update list */ + list_add_tail(&upd_data_list->list, + &fb_data->upd_buf_queue->list); + + /* Return and allow the udpate to be submitted by the ISR. */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return 0; + } + + /* Save current update */ + fb_data->cur_update = upd_data_list; + + /* LUTs are available, so we get one here */ + upd_data_list->lut_num = epdc_get_next_lut(); + + /* Associate LUT with update marker */ + if (upd_data_list->upd_marker_data) + if (upd_data_list->upd_marker_data->update_marker != 0) + upd_data_list->upd_marker_data->lut_num = upd_data_list->lut_num; + + /* Mark LUT as containing new update */ + fb_data->lut_update_type[upd_data_list->lut_num] = LUT_UPDATE_NEW; + + /* Clear status and Enable LUT complete and WB complete IRQs */ + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->cur_update->lut_num, true); + + /* Program EPDC update to process buffer */ + epdc_set_update_addr(upd_data_list->phys_addr + upd_data_list->epdc_offs); + epdc_set_update_coord(screen_upd_region->left, screen_upd_region->top); + epdc_set_update_dimensions(screen_upd_region->width, screen_upd_region->height); + if (upd_data_list->upd_data.temp != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, upd_data_list->upd_data.temp); + epdc_set_temp(temp_index); + } + epdc_submit_update(upd_data_list->lut_num, + upd_data_list->upd_data.waveform_mode, + upd_data_list->upd_data.update_mode, false, 0); + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + return 0; +} + +static int mxc_epdc_fb_wait_update_complete(u32 update_marker, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + int ret; + int i; + + /* 0 is an invalid update_marker value */ + if (update_marker == 0) + return -EINVAL; + + /* Wait for completion associated with update_marker requested */ + for (i = 0; i < EPDC_MAX_NUM_UPDATES; i++) { + if (fb_data->update_marker_array[i].update_marker == update_marker) { + dev_dbg(fb_data->dev, "Waiting for marker %d\n", update_marker); + ret = wait_for_completion_timeout(&fb_data->update_marker_array[i].update_completion, msecs_to_jiffies(5000)); + if (!ret) + dev_err(fb_data->dev, "Timed out waiting for update completion\n"); + + dev_dbg(fb_data->dev, "marker %d signalled!\n", update_marker); + + /* Reset marker so it can be reused */ + fb_data->update_marker_array[i].update_marker = 0; + + break; + } + } + + return 0; +} + +static int mxc_epdc_fb_set_pwrdown_delay(u32 pwrdown_delay, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + + fb_data->pwrdown_delay = pwrdown_delay; + + return 0; +} + +static int mxc_epdc_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int ret = -EINVAL; + + switch (cmd) { + case MXCFB_SET_WAVEFORM_MODES: + { + struct mxcfb_waveform_modes modes; + struct mxc_epdc_fb_data *fb_data = + (struct mxc_epdc_fb_data *)info; + if (!copy_from_user(&modes, argp, sizeof(modes))) { + memcpy(&fb_data->wv_modes, &modes, + sizeof(modes)); + ret = 0; + } + break; + } + case MXCFB_SET_TEMPERATURE: + { + int temperature; + if (!get_user(temperature, (int32_t __user *) arg)) + ret = + mxc_epdc_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_epdc_fb_set_auto_update(auto_mode, + info); + break; + } + case MXCFB_SEND_UPDATE: + { + struct mxcfb_update_data upd_data; + if (!copy_from_user(&upd_data, argp, + sizeof(upd_data))) { + ret = mxc_epdc_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: + { + u32 update_marker = 0; + if (!get_user(update_marker, (__u32 __user *) arg)) + ret = + mxc_epdc_fb_wait_update_complete(update_marker, + info); + break; + } + + case MXCFB_SET_PWRDOWN_DELAY: + { + int delay = 0; + if (!get_user(delay, (__u32 __user *) arg)) + ret = + mxc_epdc_fb_set_pwrdown_delay(delay, info); + break; + } + + case MXCFB_GET_PWRDOWN_DELAY: + { + struct mxc_epdc_fb_data *fb_data = + (struct mxc_epdc_fb_data *)info; + if (put_user(fb_data->pwrdown_delay, + (int __user *)argp)) + ret = -EFAULT; + ret = 0; + break; + } + default: + break; + } + return ret; +} + +static void mxc_epdc_fb_update_pages(struct mxc_epdc_fb_data *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->info.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 = TEMP_USE_AMBIENT; + update.use_alt_buffer = false; + + mxc_epdc_fb_send_update(&update, &fb_data->info); +} + +/* this is called back from the deferred io workqueue */ +static void mxc_epdc_fb_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)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 >= info->var.yres) + y2 = info->var.yres - 1; + if (miny > y1) + miny = y1; + if (maxy < y2) + maxy = y2; + } + + mxc_epdc_fb_update_pages(fb_data, miny, maxy); +} + +static void mxc_epdc_fb_disable(struct mxc_epdc_fb_data *fb_data) +{ + unsigned long flags; + /* Grab queue lock to prevent any new updates from being submitted */ + + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* If any updates in flight, we must wait for them to complete */ + if (!(list_empty(&fb_data->upd_buf_collision_list->list) && + list_empty(&fb_data->upd_buf_queue->list) && + (fb_data->cur_update == NULL))) { + /* Initialize event signalling updates are done */ + init_completion(&fb_data->updates_done); + fb_data->waiting_for_idle = true; + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + /* Wait for any currently active updates to complete */ + wait_for_completion_timeout(&fb_data->updates_done, msecs_to_jiffies(2000)); + spin_lock_irqsave(&fb_data->queue_lock, flags); + fb_data->waiting_for_idle = false; + } + + spin_unlock_irqrestore(&fb_data->queue_lock, flags); +} + +static int mxc_epdc_fb_blank(int blank, struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + + 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: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + mxc_epdc_fb_disable(fb_data); + break; + } + return 0; +} + +static int mxc_epdc_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + u_int y_bottom; + + dev_dbg(info->device, "%s: var->xoffset %d, info->var.xoffset %d\n", + __func__, var->xoffset, info->var.xoffset); + /* 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 ((info->var.xoffset == var->xoffset) && + (info->var.yoffset == var->yoffset)) + return 0; /* No change, do nothing */ + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) + y_bottom += var->yres; + + if (y_bottom > info->var.yres_virtual) + return -EINVAL; + + fb_data->fb_offset = (var->yoffset * var->xres_virtual + var->xoffset) + * (var->bits_per_pixel) / 8; + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + + return 0; +} + +static struct fb_ops mxc_epdc_fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mxc_epdc_fb_check_var, + .fb_set_par = mxc_epdc_fb_set_par, + .fb_setcolreg = mxc_epdc_fb_setcolreg, + .fb_pan_display = mxc_epdc_fb_pan_display, + .fb_ioctl = mxc_epdc_fb_ioctl, + .fb_mmap = mxc_epdc_fb_mmap, + .fb_blank = mxc_epdc_fb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static struct fb_deferred_io mxc_epdc_fb_defio = { + .delay = HZ, + .deferred_io = mxc_epdc_fb_deferred_io, +}; + +static void epdc_done_work_func(struct work_struct *work) +{ + struct mxc_epdc_fb_data *fb_data = + container_of(work, struct mxc_epdc_fb_data, + epdc_done_work.work); + epdc_powerdown(fb_data); +} + +static bool is_free_list_full(struct mxc_epdc_fb_data *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, list) + count++; + + /* Check to see if all buffers are in this list */ + if (count == EPDC_MAX_NUM_UPDATES) + return true; + else + return false; +} + +static irqreturn_t mxc_epdc_irq_handler(int irq, void *dev_id) +{ + struct mxc_epdc_fb_data *fb_data = dev_id; + struct update_data_list *collision_update; + struct mxcfb_rect *next_upd_region; + unsigned long flags; + int temp_index; + u32 luts_completed_mask; + u32 temp_mask; + u32 lut; + bool ignore_collision = false; + int i, j; + + /* + * If we just completed one-time panel init, bypass + * queue handling, clear interrupt and return + */ + if (fb_data->in_init) { + if (epdc_is_working_buffer_complete()) { + epdc_working_buf_intr(false); + epdc_clear_working_buf_irq(); + dev_dbg(fb_data->dev, "Cleared WB for init update\n"); + } + + if (epdc_is_lut_complete(0)) { + epdc_lut_complete_intr(0, false); + epdc_clear_lut_complete_irq(0); + fb_data->in_init = false; + dev_dbg(fb_data->dev, "Cleared LUT complete for init update\n"); + } + + return IRQ_HANDLED; + } + + if (!(__raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ))) + return IRQ_HANDLED; + + if (__raw_readl(EPDC_IRQ) & EPDC_IRQ_TCE_UNDERRUN_IRQ) { + dev_err(fb_data->dev, "TCE underrun! Panel may lock up.\n"); + return IRQ_HANDLED; + } + + /* Protect access to buffer queues and to update HW */ + spin_lock_irqsave(&fb_data->queue_lock, flags); + + /* Free any LUTs that have completed */ + luts_completed_mask = 0; + for (i = 0; i < EPDC_NUM_LUTS; i++) { + if (!epdc_is_lut_complete(i)) + continue; + + dev_dbg(fb_data->dev, "\nLUT %d completed\n", i); + + /* Disable IRQ for completed LUT */ + epdc_lut_complete_intr(i, false); + + /* + * Go through all updates in the collision list and + * unmask any updates that were colliding with + * the completed LUT. + */ + list_for_each_entry(collision_update, + &fb_data->upd_buf_collision_list-> + list, list) { + collision_update->collision_mask = + collision_update->collision_mask & ~(1 << i); + } + + epdc_clear_lut_complete_irq(i); + + luts_completed_mask |= 1 << i; + + fb_data->lut_update_type[i] = LUT_UPDATE_NONE; + + /* Signal completion if anyone waiting on this LUT */ + for (j = 0; j < EPDC_MAX_NUM_UPDATES; j++) { + if (fb_data->update_marker_array[j].lut_num != i) + continue; + + /* Signal completion of update */ + dev_dbg(fb_data->dev, + "Signaling marker %d\n", + fb_data->update_marker_array[j].update_marker); + complete(&fb_data->update_marker_array[j].update_completion); + /* Ensure this doesn't get signaled again inadvertently */ + fb_data->update_marker_array[j].lut_num = INVALID_LUT; + } + } + + /* Check to see if all updates have completed */ + if (is_free_list_full(fb_data) && + (fb_data->cur_update == NULL) && + !epdc_any_luts_active()) { + + 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 EPDC HW until next update */ + schedule_delayed_work(&fb_data->epdc_done_work, + msecs_to_jiffies(fb_data->pwrdown_delay)); + } + + if (fb_data->waiting_for_idle) + complete(&fb_data->updates_done); + } + + /* Is Working Buffer busy? */ + if (epdc_is_working_buffer_busy()) { + /* Can't submit another update until WB is done */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return IRQ_HANDLED; + } + + /* + * Were we waiting on working buffer? + * If so, update queues and check for collisions + */ + if (fb_data->cur_update != NULL) { + dev_dbg(fb_data->dev, "\nWorking buffer completed\n"); + + /* Was there a collision? */ + if (epdc_is_collision()) { + /* Check list of colliding LUTs, and add to our collision mask */ + fb_data->cur_update->collision_mask = + epdc_get_colliding_luts(); + + dev_dbg(fb_data->dev, "\nCollision mask = 0x%x\n", + epdc_get_colliding_luts()); + + /* Clear collisions that just completed */ + fb_data->cur_update->collision_mask &= ~luts_completed_mask; + + /* + * If this is a re-collision, AND we re-collide + * with only new updates, then we don't want + * to re-submit it again. + */ + if (fb_data->cur_update->is_collision) { + /* + * Check whether collided LUTs are + * new updates or resubmitted collisions + */ + temp_mask = fb_data->cur_update->collision_mask; + lut = 0; + while (temp_mask != 0) { + if ((temp_mask & 0x1) && + (fb_data->lut_update_type[lut] == LUT_UPDATE_NEW)) { + dev_dbg(fb_data->dev, "Ignoring collision with new update.\n"); + ignore_collision = true; + break; + } + lut++; + temp_mask = temp_mask >> 1; + } + } + + if (ignore_collision) { + /* Add to free buffer list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_free_list->list); + } else { + /* + * If update has a marker, clear the LUT, since we + * don't want to signal that it is complete. + */ + if (fb_data->cur_update->upd_marker_data) + if (fb_data->cur_update->upd_marker_data->update_marker != 0) + fb_data->cur_update->upd_marker_data->lut_num = INVALID_LUT; + + fb_data->cur_update->is_collision = true; + + /* Move to collision list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_collision_list->list); + } + } else { + /* Add to free buffer list */ + list_add_tail(&fb_data->cur_update->list, + &fb_data->upd_buf_free_list->list); + } + /* Clear current update */ + fb_data->cur_update = NULL; + + /* Clear IRQ for working buffer */ + epdc_working_buf_intr(false); + epdc_clear_working_buf_irq(); + } + + /* Check to see if any LUTs are free */ + if (!epdc_any_luts_available()) { + dev_dbg(fb_data->dev, "No luts available.\n"); + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return IRQ_HANDLED; + } + + /* + * Are any of our collision updates able to go now? + * Go through all updates in the collision list and check to see + * if the collision mask has been fully cleared + */ + list_for_each_entry(collision_update, + &fb_data->upd_buf_collision_list->list, list) { + + if (collision_update->collision_mask != 0) + continue; + + dev_dbg(fb_data->dev, "A collision update is ready to go!\n"); + /* + * We have a collision cleared, so select it + * and we will retry the update + */ + fb_data->cur_update = collision_update; + list_del_init(&fb_data->cur_update->list); + break; + } + + /* + * If we didn't find a collision update ready to go, + * we try to grab one from the update queue + */ + if (fb_data->cur_update == NULL) { + /* Is update list empty? */ + if (list_empty(&fb_data->upd_buf_queue->list)) { + dev_dbg(fb_data->dev, "No pending updates.\n"); + + /* No updates pending, so we are done */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + return IRQ_HANDLED; + } 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->list.next, + struct update_data_list, list); + list_del_init(&fb_data->cur_update->list); + } + } + + /* LUTs are available, so we get one here */ + fb_data->cur_update->lut_num = epdc_get_next_lut(); + + /* Associate LUT with update marker */ + if ((fb_data->cur_update->upd_marker_data) + && (fb_data->cur_update->upd_marker_data->update_marker != 0)) + fb_data->cur_update->upd_marker_data->lut_num = + fb_data->cur_update->lut_num; + + /* Mark LUT as containing new update */ + if (fb_data->cur_update->is_collision) + fb_data->lut_update_type[fb_data->cur_update->lut_num] = LUT_UPDATE_COLLISION; + else + fb_data->lut_update_type[fb_data->cur_update->lut_num] = LUT_UPDATE_NEW; + + /* Enable Collision and WB complete IRQs */ + epdc_working_buf_intr(true); + epdc_lut_complete_intr(fb_data->cur_update->lut_num, true); + + /* Program EPDC update to process buffer */ + next_upd_region = &fb_data->cur_update->upd_data.update_region; + if (fb_data->cur_update->upd_data.temp != TEMP_USE_AMBIENT) { + temp_index = mxc_epdc_fb_get_temp_index(fb_data, fb_data->cur_update->upd_data.temp); + epdc_set_temp(temp_index); + } + epdc_set_update_addr(fb_data->cur_update->phys_addr + fb_data->cur_update->epdc_offs); + epdc_set_update_coord(next_upd_region->left, next_upd_region->top); + epdc_set_update_dimensions(next_upd_region->width, + next_upd_region->height); + epdc_submit_update(fb_data->cur_update->lut_num, + fb_data->cur_update->upd_data.waveform_mode, + fb_data->cur_update->upd_data.update_mode, false, 0); + + /* Release buffer queues */ + spin_unlock_irqrestore(&fb_data->queue_lock, flags); + + return IRQ_HANDLED; +} + +static void draw_mode0(struct mxc_epdc_fb_data *fb_data) +{ + u32 *upd_buf_ptr; + int i; + + upd_buf_ptr = (u32 *)fb_data->info.screen_base; + + epdc_working_buf_intr(true); + epdc_lut_complete_intr(0, true); + fb_data->in_init = true; + + /* Program EPDC update to process buffer */ + epdc_set_update_addr(fb_data->phys_start); + epdc_set_update_coord(0, 0); + epdc_set_update_dimensions(fb_data->info.var.xres, + fb_data->info.var.yres); + epdc_submit_update(0, fb_data->wv_modes.mode_init, UPDATE_MODE_FULL, true, 0xFF); + + dev_dbg(fb_data->dev, "Mode0 update - Waiting for LUT to complete...\n"); + + /* Will timeout after ~4-5 seconds */ + + for (i = 0; i < 40; i++) { + if (!epdc_is_lut_active(0)) { + dev_dbg(fb_data->dev, "Mode0 init complete\n"); + return; + } + msleep(100); + } + + dev_err(fb_data->dev, "Mode0 init failed!\n"); + + return; +} + +static int mxc_epdc_fb_init_hw(struct fb_info *info) +{ + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; + const struct firmware *fw; + char *fw_str = "imx/epdc"; + struct mxcfb_update_data update; + struct mxcfb_waveform_data_file *wv_file; + int wv_data_offs; + int ret; + int i; + + /* + * Create fw search string based on ID string in selected videomode. + * Format is "imx/epdc_[panel string].fw" + */ + if (fb_data->cur_mode) { + strcat(fw_str, "_"); + strcat(fw_str, fb_data->cur_mode->vmode->name); + strcat(fw_str, ".fw"); + } + + ret = request_firmware(&fw, fw_str, fb_data->dev); + if (ret) { + printk(KERN_ERR "Failed to load image imx/epdc.ihex err %d\n", + ret); + return ret; + } + + wv_file = (struct mxcfb_waveform_data_file *)fw->data; + + /* Get size and allocate temperature range table */ + fb_data->trt_entries = wv_file->wdh.trc + 1; + fb_data->temp_range_bounds = kzalloc(fb_data->trt_entries, GFP_KERNEL); + + for (i = 0; i < fb_data->trt_entries; i++) + dev_dbg(fb_data->dev, "trt entry #%d = 0x%x\n", i, *((u8 *)&wv_file->data + i)); + + /* Copy TRT data */ + memcpy(fb_data->temp_range_bounds, &wv_file->data, fb_data->trt_entries); + + /* Get offset and size for waveform data */ + wv_data_offs = sizeof(wv_file->wdh) + fb_data->trt_entries + 1; + fb_data->waveform_buffer_size = fw->size - wv_data_offs; + + /* Allocate memory for waveform data */ + fb_data->waveform_buffer_virt = dma_alloc_coherent(fb_data->dev, + fb_data->waveform_buffer_size, + &fb_data->waveform_buffer_phys, + GFP_DMA); + if (fb_data->waveform_buffer_virt == NULL) { + dev_err(fb_data->dev, "Can't allocate mem for waveform!\n"); + ret = -ENOMEM; + } + + memcpy(fb_data->waveform_buffer_virt, (u8 *)(fw->data) + wv_data_offs, + fb_data->waveform_buffer_size); + + release_firmware(fw); + + /* Enable clocks to access EPDC regs */ + clk_enable(fb_data->epdc_clk_axi); + + /* Enable pix clk for EPDC */ + clk_enable(fb_data->epdc_clk_pix); + clk_set_rate(fb_data->epdc_clk_pix, fb_data->cur_mode->vmode->pixclock); + + epdc_init_sequence(fb_data); + + /* Enable clocks to access EPDC regs */ + clk_disable(fb_data->epdc_clk_axi); + clk_disable(fb_data->epdc_clk_pix); + + fb_data->hw_ready = true; + + update.update_region.left = 0; + update.update_region.width = info->var.xres; + update.update_region.top = 0; + update.update_region.height = info->var.yres; + update.update_mode = UPDATE_MODE_FULL; + update.waveform_mode = WAVEFORM_MODE_AUTO; + update.update_marker = INIT_UPDATE_MARKER; + update.temp = TEMP_USE_AMBIENT; + update.use_alt_buffer = false; + + mxc_epdc_fb_send_update(&update, info); + + /* Block on initial update */ + ret = mxc_epdc_fb_wait_update_complete(update.update_marker, info); + if (ret < 0) + dev_err(fb_data->dev, + "Wait for update complete failed. Error = 0x%x", ret); + + return 0; +} + +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); + struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)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; + + /* Now, request full screen update */ + update.update_region.left = 0; + update.update_region.width = info->var.xres; + update.update_region.top = 0; + update.update_region.height = info->var.yres; + update.update_mode = UPDATE_MODE_FULL; + update.temp = TEMP_USE_AMBIENT; + update.update_marker = 0; + update.use_alt_buffer = false; + + mxc_epdc_fb_send_update(&update, info); + + return count; +} + +static struct device_attribute fb_attrs[] = { + __ATTR(update, S_IRUGO|S_IWUSR, NULL, store_update), +}; + +int __devinit mxc_epdc_fb_probe(struct platform_device *pdev) +{ + int ret = 0; + struct mxc_epdc_fb_data *fb_data; + struct resource *res; + struct fb_info *info; + char *options, *opt; + char *panel_str = NULL; + char name[] = "mxcepdcfb"; + struct fb_videomode *vmode; + int xres_virt, yres_virt, buf_size; + 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; + int i; + unsigned long x_mem_size = 0; +#ifdef CONFIG_FRAMEBUFFER_CONSOLE + struct mxcfb_update_data update; +#endif + + fb_data = (struct mxc_epdc_fb_data *)framebuffer_alloc( + sizeof(struct mxc_epdc_fb_data), &pdev->dev); + if (fb_data == NULL) { + ret = -ENOMEM; + goto out; + } + + /* Get platform data and check validity */ + fb_data->pdata = pdev->dev.platform_data; + if ((fb_data->pdata == NULL) || (fb_data->pdata->num_modes < 1) + || (fb_data->pdata->epdc_mode == NULL) + || (fb_data->pdata->epdc_mode->vmode == NULL)) { + ret = -EINVAL; + goto out_fbdata; + } + + if (fb_get_options(name, &options)) { + ret = -ENODEV; + goto out_fbdata; + } + + if (options) + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "bpp=", 4)) + fb_data->default_bpp = + simple_strtoul(opt + 4, NULL, 0); + else if (!strncmp(opt, "x_mem=", 6)) + x_mem_size = + simple_strtoul(opt + 6, NULL, 0); + else + panel_str = opt; + } + + fb_data->dev = &pdev->dev; + + if (!fb_data->default_bpp) + fb_data->default_bpp = 16; + + /* Set default (first defined mode) before searching for a match */ + fb_data->cur_mode = &fb_data->pdata->epdc_mode[0]; + + if (panel_str) + for (i = 0; i < fb_data->pdata->num_modes; i++) + if (!strcmp(fb_data->pdata->epdc_mode[i].vmode->name, + panel_str)) { + fb_data->cur_mode = + &fb_data->pdata->epdc_mode[i]; + break; + } + + vmode = fb_data->cur_mode->vmode; + + platform_set_drvdata(pdev, fb_data); + info = &fb_data->info; + + /* Allocate color map for the FB */ + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) + goto out_fbdata; + + dev_dbg(&pdev->dev, "resolution %dx%d, bpp %d\n", + fb_data->cur_mode->vmode->xres, + fb_data->cur_mode->vmode->yres, fb_data->default_bpp); + + /* + * 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); + buf_size = xres_virt * yres_virt * fb_data->default_bpp/8; + + if (x_mem_size > 0) + g_num_screens = NUM_SCREENS_X; + + fb_data->map_size = PAGE_ALIGN(buf_size) * g_num_screens; + dev_dbg(&pdev->dev, "memory to allocate: %d\n", fb_data->map_size); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + ret = -ENODEV; + goto out_cmap; + } + + epdc_base = ioremap(res->start, SZ_4K); + if (epdc_base == NULL) { + ret = -ENOMEM; + goto out_cmap; + } + + /* Allocate FB memory */ + info->screen_base = dma_alloc_writecombine(&pdev->dev, + fb_data->map_size, + &fb_data->phys_start, + GFP_KERNEL); + + if (info->screen_base == NULL) { + ret = -ENOMEM; + goto out_mapregs; + } + dev_dbg(&pdev->dev, "allocated at %p:0x%x\n", info->screen_base, + fb_data->phys_start); + + 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; + /* Additional screens allow for panning and buffer flipping */ + var_info->yres_virtual = yres_virt * g_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; + + default: + dev_err(&pdev->dev, "unsupported bitwidth %d\n", + fb_data->default_bpp); + ret = -EINVAL; + goto out_dma_fb; + } + + fix_info = &info->fix; + + strcpy(fix_info->id, "mxc_epdc_fb"); + 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; + + fb_data->native_width = vmode->xres; + fb_data->native_height = vmode->yres; + + info->fbops = &mxc_epdc_fb_ops; + info->var.activate = FB_ACTIVATE_NOW; + info->pseudo_palette = fb_data->pseudo_palette; + info->screen_size = info->fix.smem_len; + fb_data->par = NULL; + info->flags = FBINFO_FLAG_DEFAULT; + + mxc_epdc_fb_set_fix(info); + + fb_data->auto_mode = AUTO_UPDATE_MODE_REGION_MODE; + + init_waitqueue_head(&fb_data->vsync_wait_q); + fb_data->vsync_count = 0; + + fb_data->fb_offset = 0; + + /* Allocate head objects for our lists */ + fb_data->upd_buf_queue = + kzalloc(sizeof(struct update_data_list), GFP_KERNEL); + fb_data->upd_buf_collision_list = + kzalloc(sizeof(struct update_data_list), GFP_KERNEL); + fb_data->upd_buf_free_list = + kzalloc(sizeof(struct update_data_list), GFP_KERNEL); + if ((fb_data->upd_buf_queue == NULL) || (fb_data->upd_buf_free_list == NULL) + || (fb_data->upd_buf_collision_list == NULL)) { + ret = -ENOMEM; + goto out_dma_fb; + } + + /* + * Initialize lists for update requests, update collisions, + * and available update (PxP output) buffers + */ + INIT_LIST_HEAD(&fb_data->upd_buf_queue->list); + INIT_LIST_HEAD(&fb_data->upd_buf_free_list->list); + INIT_LIST_HEAD(&fb_data->upd_buf_collision_list->list); + + /* Allocate update buffers and add them to the list */ + for (i = 0; i < EPDC_MAX_NUM_UPDATES; i++) { + upd_list = kzalloc(sizeof(*upd_list), GFP_KERNEL); + if (upd_list == NULL) { + ret = -ENOMEM; + goto out_upd_buffers; + } + + /* Clear update data structure */ + memset(&upd_list->upd_data, 0, + sizeof(struct mxcfb_update_data)); + + /* + * Each update buffer is 1 byte per pixel, and can + * be as big as the full-screen frame buffer + */ + upd_list->size = info->var.xres * info->var.yres; + + /* Allocate memory for PxP output buffer */ + upd_list->virt_addr = + dma_alloc_coherent(fb_data->info.device, upd_list->size, + &upd_list->phys_addr, GFP_DMA); + if (upd_list->virt_addr == NULL) { + kfree(upd_list); + ret = -ENOMEM; + goto out_upd_buffers; + } + + /* Add newly allocated buffer to free list */ + list_add(&upd_list->list, &fb_data->upd_buf_free_list->list); + + dev_dbg(fb_data->info.device, "allocated %d bytes @ 0x%08X\n", + upd_list->size, upd_list->phys_addr); + } + + fb_data->working_buffer_size = vmode->yres * vmode->xres * 2; + /* Allocate memory for EPDC working buffer */ + fb_data->working_buffer_virt = + dma_alloc_coherent(&pdev->dev, fb_data->working_buffer_size, + &fb_data->working_buffer_phys, GFP_DMA); + if (fb_data->working_buffer_virt == NULL) { + dev_err(&pdev->dev, "Can't allocate mem for working buf!\n"); + ret = -ENOMEM; + goto out_upd_buffers; + } + + /* Initialize EPDC pins */ + if (fb_data->pdata->get_pins) + fb_data->pdata->get_pins(); + + fb_data->epdc_clk_axi = clk_get(fb_data->dev, "epdc_axi"); + fb_data->epdc_clk_pix = clk_get(fb_data->dev, "epdc_pix"); + + fb_data->in_init = false; + + fb_data->hw_ready = false; + + /* + * Set default waveform mode values. + * Should be overwritten via ioctl. + */ + fb_data->wv_modes.mode_init = 0; + fb_data->wv_modes.mode_du = 1; + fb_data->wv_modes.mode_gc4 = 3; + fb_data->wv_modes.mode_gc8 = 2; + fb_data->wv_modes.mode_gc16 = 2; + fb_data->wv_modes.mode_gc32 = 2; + + /* Initialize markers */ + for (i = 0; i < EPDC_MAX_NUM_UPDATES; i++) { + fb_data->update_marker_array[i].update_marker = 0; + fb_data->update_marker_array[i].lut_num = INVALID_LUT; + } + + /* Initialize all LUTs to inactive */ + for (i = 0; i < EPDC_NUM_LUTS; i++) + fb_data->lut_update_type[i] = LUT_UPDATE_NONE; + + /* Retrieve EPDC 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 out_dma_work_buf; + } + fb_data->epdc_irq = res->start; + + /* Register IRQ handler */ + ret = request_irq(fb_data->epdc_irq, mxc_epdc_irq_handler, 0, + "fb_dma", fb_data); + if (ret) { + dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", + fb_data->epdc_irq, ret); + ret = -ENODEV; + goto out_dma_work_buf; + } + + INIT_DELAYED_WORK(&fb_data->epdc_done_work, epdc_done_work_func); + + info->fbdefio = &mxc_epdc_fb_defio; +#ifdef CONFIG_FB_MXC_EINK_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 out_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 out_irq; + } + + if (device_create_file(info->dev, &fb_attrs[0])) + dev_err(&pdev->dev, "Unable to create file from fb_attrs\n"); + + fb_data->cur_update = NULL; + + spin_lock_init(&fb_data->queue_lock); + + mutex_init(&fb_data->pxp_mutex); + + mutex_init(&fb_data->power_mutex); + + /* PxP DMA interface */ + dmaengine_get(); + + /* + * 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 = proc_data->srect.width = fb_data->info.var.xres; + proc_data->drect.height = 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; + + /* + * 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_GREY; + + /* + * 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)); + + fb_data->waiting_for_idle = false; + fb_data->blank = FB_BLANK_UNBLANK; + fb_data->power_state = POWER_STATE_OFF; + fb_data->powering_down = false; + fb_data->pwrdown_delay = 0; + + /* Register FB */ + ret = register_framebuffer(info); + if (ret) { + dev_err(&pdev->dev, + "register_framebuffer failed with error %d\n", ret); + goto out_dmaengine; + } + +#ifdef DEFAULT_PANEL_HW_INIT + ret = mxc_epdc_fb_init_hw((struct fb_info *)fb_data); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize HW!\n"); + } +#endif + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE + /* If FB console included, update display to show logo */ + update.update_region.left = 0; + update.update_region.width = info->var.xres; + update.update_region.top = 0; + update.update_region.height = info->var.yres; + update.update_mode = UPDATE_MODE_PARTIAL; + update.waveform_mode = WAVEFORM_MODE_AUTO; + update.update_marker = INIT_UPDATE_MARKER; + update.temp = TEMP_USE_AMBIENT; + update.use_alt_buffer = false; + + mxc_epdc_fb_send_update(&update, info); + + ret = mxc_epdc_fb_wait_update_complete(update.update_marker, info); + if (ret < 0) + dev_err(fb_data->dev, + "Wait for update complete failed. Error = 0x%x", ret); +#endif + + goto out; + +out_dmaengine: + dmaengine_put(); +out_irq: + free_irq(fb_data->epdc_irq, fb_data); +out_dma_work_buf: + dma_free_writecombine(&pdev->dev, fb_data->working_buffer_size, + fb_data->working_buffer_virt, fb_data->working_buffer_phys); + if (fb_data->pdata->put_pins) + fb_data->pdata->put_pins(); +out_upd_buffers: + list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list->list, list) { + list_del(&plist->list); + dma_free_writecombine(&pdev->dev, plist->size, plist->virt_addr, + plist->phys_addr); + kfree(plist); + } +out_dma_fb: + dma_free_writecombine(&pdev->dev, fb_data->map_size, info->screen_base, + fb_data->phys_start); + +out_mapregs: + iounmap(epdc_base); +out_cmap: + fb_dealloc_cmap(&info->cmap); +out_fbdata: + kfree(fb_data); +out: + return ret; +} + +static int mxc_epdc_fb_remove(struct platform_device *pdev) +{ + struct update_data_list *plist, *temp_list; + struct mxc_epdc_fb_data *fb_data = platform_get_drvdata(pdev); + + mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &fb_data->info); + + regulator_put(fb_data->display_regulator); + regulator_put(fb_data->vcom_regulator); + + unregister_framebuffer(&fb_data->info); + free_irq(fb_data->epdc_irq, fb_data); + + dma_free_writecombine(&pdev->dev, fb_data->working_buffer_size, + fb_data->working_buffer_virt, + fb_data->working_buffer_phys); + if (fb_data->waveform_buffer_virt != NULL) + dma_free_writecombine(&pdev->dev, fb_data->waveform_buffer_size, + fb_data->waveform_buffer_virt, + fb_data->waveform_buffer_phys); + list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list->list, list) { + list_del(&plist->list); + dma_free_writecombine(&pdev->dev, plist->size, plist->virt_addr, + plist->phys_addr); + kfree(plist); + } +#ifdef CONFIG_FB_MXC_EINK_AUTO_UPDATE_MODE + fb_deferred_io_cleanup(&fb_data->info); +#endif + + dma_free_writecombine(&pdev->dev, fb_data->map_size, fb_data->info.screen_base, + fb_data->phys_start); + + if (fb_data->pdata->put_pins) + fb_data->pdata->put_pins(); + + /* Release PxP-related resources */ + if (fb_data->pxp_chan != NULL) + dma_release_channel(&fb_data->pxp_chan->dma_chan); + + dmaengine_put(); + + iounmap(epdc_base); + +#ifdef CONFIG_FB_MXC_EINK_AUTO_UPDATE_MODE + fb_deferred_io_cleanup(&fb_data->info); +#endif + fb_dealloc_cmap(&fb_data->info.cmap); + + framebuffer_release(&fb_data->info); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int mxc_epdc_fb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxc_epdc_fb_data *data = platform_get_drvdata(pdev); + int ret; + + ret = mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &data->info); + if (ret) + goto out; + +out: + return ret; +} + +static int mxc_epdc_fb_resume(struct platform_device *pdev) +{ + struct mxc_epdc_fb_data *data = platform_get_drvdata(pdev); + + mxc_epdc_fb_blank(FB_BLANK_UNBLANK, &data->info); + return 0; +} +#else +#define mxc_epdc_fb_suspend NULL +#define mxc_epdc_fb_resume NULL +#endif + +static struct platform_driver mxc_epdc_fb_driver = { + .probe = mxc_epdc_fb_probe, + .remove = mxc_epdc_fb_remove, + .suspend = mxc_epdc_fb_suspend, + .resume = mxc_epdc_fb_resume, + .driver = { + .name = "mxc_epdc_fb", + .owner = THIS_MODULE, + }, +}; + +/* 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); + struct mxc_epdc_fb_data *fb_data = pxp_chan->client; + + /* This call will signal wait_for_completion_timeout() in send_buffer_to_pxp */ + complete(&fb_data->pxp_tx_cmpl); +} + +/* Function to request PXP DMA channel */ +static int pxp_chan_init(struct mxc_epdc_fb_data *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, NULL, NULL); + if (!chan) { + dev_err(fb_data->dev, "Unsuccessfully received channel!!!!\n"); + return -EBUSY; + } + + dev_dbg(fb_data->dev, "Successfully received channel.\n"); + + 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(struct mxc_epdc_fb_data *fb_data, + struct mxcfb_rect *update_region, + int x_start_offs) +{ + 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; + + dev_dbg(fb_data->dev, "Starting PxP Send Buffer\n"); + + /* 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); + + dev_dbg(fb_data->dev, "sg[0] = 0x%x, sg[1] = 0x%x\n", + sg_dma_address(&sg[0]), sg_dma_address(&sg[1])); + + 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. + */ + proc_data->srect.top = update_region->top; + proc_data->srect.left = update_region->left + x_start_offs; + 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->info.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; + + 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); + dev_dbg(fb_data->info.device, "%d: Submit %p #%d\n", __LINE__, txd, + cookie); + 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(struct mxc_epdc_fb_data *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; + } + + *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 int __init mxc_epdc_fb_init(void) +{ + return platform_driver_register(&mxc_epdc_fb_driver); +} +late_initcall(mxc_epdc_fb_init); + + +static void __exit mxc_epdc_fb_exit(void) +{ + platform_driver_unregister(&mxc_epdc_fb_driver); +} +module_exit(mxc_epdc_fb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC EPDC framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); |