| /* |
| * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. |
| * Copyright 2017 NXP |
| * |
| * Copyright 2017 NXP |
| * |
| * 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/busfreq-imx.h> |
| #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/pm_runtime.h> |
| #include <linux/mxcfb.h> |
| #include <linux/mxcfb_epdc.h> |
| #include <linux/gpio.h> |
| #include <linux/of_gpio.h> |
| #include <linux/mfd/syscon.h> |
| #include <linux/regmap.h> |
| #include <linux/regulator/driver.h> |
| #include <linux/mfd/max17135.h> |
| #include <linux/fsl_devices.h> |
| #include <linux/bitops.h> |
| #include <linux/pinctrl/consumer.h> |
| #include <linux/platform_data/dma-imx.h> |
| #include <asm/cacheflush.h> |
| |
| #include "epdc_v2_regs.h" |
| |
| #define EPDC_STANDARD_MODE |
| |
| #define USE_PS_AS_OUTPUT |
| |
| /* |
| * Enable this define to have a default panel |
| * loaded during driver initialization |
| */ |
| /*#define DEFAULT_PANEL_HW_INIT*/ |
| |
| #define SG_NUM 14 /* 2+4+4+4 */ |
| #define NUM_SCREENS_MIN 2 |
| |
| #define EPDC_V1_NUM_LUTS 16 |
| #define EPDC_V1_MAX_NUM_UPDATES 20 |
| #define EPDC_V2_NUM_LUTS 64 |
| #define EPDC_V2_MAX_NUM_UPDATES 64 |
| #define EPDC_MAX_NUM_BUFFERS 2 |
| #define INVALID_LUT (-1) |
| #define DRY_RUN_NO_LUT 100 |
| |
| /* Maximum update buffer image width due to v2.0 and v2.1 errata ERR005313. */ |
| #define EPDC_V2_MAX_UPDATE_WIDTH 2047 |
| #define EPDC_V2_ROTATION_ALIGNMENT 8 |
| |
| #define DEFAULT_TEMP_INDEX 0 |
| #define DEFAULT_TEMP 20 /* room temp in deg Celsius */ |
| |
| #define INIT_UPDATE_MARKER 0x12345678 |
| #define PAN_UPDATE_MARKER 0x12345679 |
| |
| #define POWER_STATE_OFF 0 |
| #define POWER_STATE_ON 1 |
| |
| #define MERGE_OK 0 |
| #define MERGE_FAIL 1 |
| #define MERGE_BLOCK 2 |
| |
| static u64 used_luts = 0x1; /* do not use LUT0 */ |
| static unsigned long default_bpp = 16; |
| |
| struct update_marker_data { |
| struct list_head full_list; |
| struct list_head upd_list; |
| u32 update_marker; |
| struct completion update_completion; |
| int lut_num; |
| bool collision_test; |
| bool waiting; |
| }; |
| |
| struct update_desc_list { |
| struct list_head list; |
| struct mxcfb_update_data upd_data;/* Update parameters */ |
| u32 epdc_offs; /* Added to buffer ptr to resolve alignment */ |
| u32 epdc_stride; /* Depends on rotation & whether we skip PxP */ |
| struct list_head upd_marker_list; /* List of markers for this update */ |
| u32 update_order; /* Numeric ordering value for update */ |
| }; |
| |
| /* This structure represents a list node containing both |
| * a memory region allocated as an output buffer for the PxP |
| * update processing task, and the update description (mode, region, etc.) */ |
| struct update_data_list { |
| struct list_head list; |
| dma_addr_t phys_addr; /* Pointer to phys address of processed Y buf */ |
| void *virt_addr; |
| struct update_desc_list *update_desc; |
| int lut_num; /* Assigned before update is processed into working buffer */ |
| u64 collision_mask; /* Set when update creates collision */ |
| /* Mask of the LUTs the update collides with */ |
| }; |
| |
| struct mxc_epdc_fb_data { |
| struct fb_info info; |
| struct fb_var_screeninfo epdc_fb_var; /* Internal copy of screeninfo |
| so we can sync changes to it */ |
| u32 pseudo_palette[16]; |
| char fw_str[24]; |
| struct list_head list; |
| struct imx_epdc_fb_mode *cur_mode; |
| struct imx_epdc_fb_platform_data *pdata; |
| int blank; |
| u32 max_pix_size; |
| ssize_t map_size; |
| dma_addr_t phys_start; |
| void *virt_start; |
| u32 fb_offset; |
| int default_bpp; |
| int native_width; |
| int native_height; |
| int num_screens; |
| int epdc_irq; |
| struct device *dev; |
| int power_state; |
| int wait_for_powerdown; |
| struct completion powerdown_compl; |
| struct clk *epdc_clk_axi; |
| struct clk *epdc_clk_pix; |
| struct regulator *display_regulator; |
| struct regulator *vcom_regulator; |
| struct regulator *v3p3_regulator; |
| bool fw_default_load; |
| int rev; |
| |
| /* FB elements related to EPDC updates */ |
| int num_luts; |
| int max_num_updates; |
| bool in_init; |
| bool hw_ready; |
| bool hw_initializing; |
| bool waiting_for_idle; |
| u32 auto_mode; |
| u32 upd_scheme; |
| struct list_head upd_pending_list; |
| struct list_head upd_buf_queue; |
| struct list_head upd_buf_free_list; |
| struct list_head upd_buf_collision_list; |
| struct update_data_list *cur_update; |
| struct mutex queue_mutex; |
| int trt_entries; |
| int temp_index; |
| u8 *temp_range_bounds; |
| struct mxcfb_waveform_modes wv_modes; |
| bool wv_modes_update; |
| bool waveform_is_advanced; |
| u32 *waveform_buffer_virt; |
| u32 waveform_buffer_phys; |
| u32 waveform_buffer_size; |
| u32 *working_buffer_virt; |
| u32 working_buffer_phys; |
| u32 working_buffer_size; |
| u32 *tmp_working_buffer_virt; |
| u32 tmp_working_buffer_phys; |
| dma_addr_t *phys_addr_updbuf; |
| void **virt_addr_updbuf; |
| u32 upd_buffer_num; |
| u32 max_num_buffers; |
| dma_addr_t phys_addr_copybuf; /* Phys address of copied update data */ |
| void *virt_addr_copybuf; /* Used for PxP SW workaround */ |
| dma_addr_t phys_addr_y4; |
| void *virt_addr_y4; |
| dma_addr_t phys_addr_y4c; |
| void *virt_addr_y4c; |
| dma_addr_t phys_addr_black; |
| void *virt_addr_black; |
| u32 order_cnt; |
| struct list_head full_marker_list; |
| u32 *lut_update_order; /* Array size = number of luts */ |
| u64 epdc_colliding_luts; |
| u64 luts_complete_wb; |
| u64 luts_complete; |
| struct completion updates_done; |
| struct delayed_work epdc_done_work; |
| struct workqueue_struct *epdc_submit_workqueue; |
| struct work_struct epdc_submit_work; |
| struct workqueue_struct *epdc_intr_workqueue; |
| struct work_struct epdc_intr_work; |
| bool waiting_for_wb; |
| bool waiting_for_lut; |
| bool waiting_for_lut15; |
| struct completion update_res_free; |
| struct completion lut15_free; |
| struct completion eof_event; |
| int eof_sync_period; |
| struct mutex power_mutex; |
| bool powering_down; |
| bool updates_active; |
| int pwrdown_delay; |
| unsigned long tce_prevent; |
| bool restrict_width; /* work around rev >=2.0 width and |
| stride restriction */ |
| |
| /* 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[SG_NUM]; |
| struct mutex pxp_mutex; /* protects access to PxP */ |
| |
| /* external mode or internal mode */ |
| int epdc_wb_mode; |
| struct pxp_collision_info col_info; |
| u32 hist_status; |
| |
| struct regmap *gpr; |
| u8 req_gpr; |
| u8 req_bit; |
| |
| /* qos */ |
| struct regmap *qos_regmap; |
| }; |
| |
| 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 */ |
| }; |
| |
| #define WAVEFORM_HDR_LUT_ADVANCED_ALGO_MASK 0xc |
| |
| static struct fb_videomode ed060xh2c1mode = { |
| .name = "ED060XH2C1", |
| .refresh = 85, |
| .xres = 1024, |
| .yres = 758, |
| .pixclock = 40000000, |
| .left_margin = 12, |
| .right_margin = 76, |
| .upper_margin = 4, |
| .lower_margin = 5, |
| .hsync_len = 12, |
| .vsync_len = 2, |
| .sync = 0, |
| .vmode = FB_VMODE_NONINTERLACED, |
| .flag = 0, |
| }; |
| |
| static struct fb_videomode e60_v110_mode = { |
| .name = "E60_V110", |
| .refresh = 50, |
| .xres = 800, |
| .yres = 600, |
| .pixclock = 18604700, |
| .left_margin = 8, |
| .right_margin = 178, |
| .upper_margin = 4, |
| .lower_margin = 10, |
| .hsync_len = 20, |
| .vsync_len = 4, |
| .sync = 0, |
| .vmode = FB_VMODE_NONINTERLACED, |
| .flag = 0, |
| }; |
| |
| static struct fb_videomode e60_v220_mode = { |
| .name = "E60_V220", |
| .refresh = 85, |
| .xres = 800, |
| .yres = 600, |
| .pixclock = 30000000, |
| .left_margin = 8, |
| .right_margin = 164, |
| .upper_margin = 4, |
| .lower_margin = 8, |
| .hsync_len = 4, |
| .vsync_len = 1, |
| .sync = 0, |
| .vmode = FB_VMODE_NONINTERLACED, |
| .flag = 0, |
| }; |
| |
| static struct fb_videomode e060scm_mode = { |
| .name = "E060SCM", |
| .refresh = 85, |
| .xres = 800, |
| .yres = 600, |
| .pixclock = 26666667, |
| .left_margin = 8, |
| .right_margin = 100, |
| .upper_margin = 4, |
| .lower_margin = 8, |
| .hsync_len = 4, |
| .vsync_len = 1, |
| .sync = 0, |
| .vmode = FB_VMODE_NONINTERLACED, |
| .flag = 0, |
| }; |
| |
| static struct fb_videomode e97_v110_mode = { |
| .name = "E97_V110", |
| .refresh = 50, |
| .xres = 1200, |
| .yres = 825, |
| .pixclock = 32000000, |
| .left_margin = 12, |
| .right_margin = 128, |
| .upper_margin = 4, |
| .lower_margin = 10, |
| .hsync_len = 20, |
| .vsync_len = 4, |
| .sync = 0, |
| .vmode = FB_VMODE_NONINTERLACED, |
| .flag = 0, |
| }; |
| |
| static struct imx_epdc_fb_mode panel_modes[] = { |
| { |
| &ed060xh2c1mode, /* struct fb_videomode *mode */ |
| 4, /* vscan_holdoff */ |
| 10, /* sdoed_width */ |
| 20, /* sdoed_delay */ |
| 10, /* sdoez_width */ |
| 20, /* sdoez_delay */ |
| 524, /* GDCLK_HP */ |
| 327, /* GDSP_OFF */ |
| 0, /* GDOE_OFF */ |
| 19, /* gdclk_offs */ |
| 1, /* num_ce */ |
| }, |
| { |
| &e60_v110_mode, |
| 4, /* vscan_holdoff */ |
| 10, /* sdoed_width */ |
| 20, /* sdoed_delay */ |
| 10, /* sdoez_width */ |
| 20, /* sdoez_delay */ |
| 428, /* gdclk_hp_offs */ |
| 20, /* gdsp_offs */ |
| 0, /* gdoe_offs */ |
| 1, /* gdclk_offs */ |
| 1, /* num_ce */ |
| }, |
| { |
| &e60_v220_mode, |
| 4, /* vscan_holdoff */ |
| 10, /* sdoed_width */ |
| 20, /* sdoed_delay */ |
| 10, /* sdoez_width */ |
| 20, /* sdoez_delay */ |
| 465, /* gdclk_hp_offs */ |
| 20, /* gdsp_offs */ |
| 0, /* gdoe_offs */ |
| 9, /* gdclk_offs */ |
| 1, /* num_ce */ |
| }, |
| { |
| &e060scm_mode, |
| 4, /* vscan_holdoff */ |
| 10, /* sdoed_width */ |
| 20, /* sdoed_delay */ |
| 10, /* sdoez_width */ |
| 20, /* sdoez_delay */ |
| 419, /* gdclk_hp_offs */ |
| 263, /* gdsp_offs */ |
| 0, /* gdoe_offs */ |
| 5, /* gdclk_offs */ |
| 1, /* num_ce */ |
| }, |
| { |
| &e97_v110_mode, |
| 8, /* vscan_holdoff */ |
| 10, /* sdoed_width */ |
| 20, /* sdoed_delay */ |
| 10, /* sdoez_width */ |
| 20, /* sdoez_delay */ |
| 632, /* gdclk_hp_offs */ |
| 20, /* gdsp_offs */ |
| 0, /* gdoe_offs */ |
| 1, /* gdclk_offs */ |
| 3, /* num_ce */ |
| } |
| }; |
| |
| static struct imx_epdc_fb_platform_data epdc_data = { |
| .epdc_mode = panel_modes, |
| .num_modes = ARRAY_SIZE(panel_modes), |
| }; |
| |
| void __iomem *epdc_v2_base; |
| |
| static struct mxc_epdc_fb_data *g_fb_data; |
| |
| /* forward declaration */ |
| static int mxc_epdc_fb_get_temp_index(struct mxc_epdc_fb_data *fb_data, |
| int temp); |
| static void mxc_epdc_fb_flush_updates(struct mxc_epdc_fb_data *fb_data); |
| 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_legacy_process(struct mxc_epdc_fb_data *fb_data, |
| u32 src_width, u32 src_height, |
| struct mxcfb_rect *update_region); |
| static int pxp_process_dithering(struct mxc_epdc_fb_data *fb_data, |
| struct mxcfb_rect *update_region); |
| static int pxp_wfe_a_process(struct mxc_epdc_fb_data *fb_data, |
| struct mxcfb_rect *update_region, |
| struct update_data_list *upd_data_list); |
| static int pxp_wfe_b_process_update(struct mxc_epdc_fb_data *fb_data, |
| struct mxcfb_rect *update_region); |
| static int pxp_wfe_a_process_clear_workingbuffer(struct mxc_epdc_fb_data *fb_data, |
| u32 src_width, u32 src_height); |
| 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); |
| static bool is_free_list_full(struct mxc_epdc_fb_data *fb_data); |
| |
| static void do_dithering_processing_Y1_v1_0( |
| unsigned char *update_region_virt_ptr, |
| dma_addr_t update_region_phys_ptr, |
| struct mxcfb_rect *update_region, |
| unsigned long update_region_stride, |
| int *err_dist); |
| static void do_dithering_processing_Y4_v1_0( |
| unsigned char *update_region_virt_ptr, |
| dma_addr_t update_region_phys_ptr, |
| struct mxcfb_rect *update_region, |
| unsigned long update_region_stride, |
| int *err_dist); |
| static inline void epdc_set_used_lut(u64 used_bit); |
| static inline void epdc_reset_used_lut(void); |
| static int pxp_clear_wb_work_func(struct mxc_epdc_fb_data *fb_data); |
| static int epdc_working_buffer_update(struct mxc_epdc_fb_data *fb_data, |
| struct update_data_list *upd_data_list, |
| struct mxcfb_rect *update_region); |
| extern void pxp_get_collision_info(struct pxp_collision_info *info); |
| |
| #ifdef DEBUG |
| static void dump_pxp_config(struct mxc_epdc_fb_data *fb_data, |
| struct pxp_config_data *pxp_conf) |
| { |
| dev_info(fb_data->dev, "S0 fmt 0x%x", |
| pxp_conf->s0_param.pixel_fmt); |
| dev_info(fb_data->dev, "S0 width 0x%x", |
| pxp_conf->s0_param.width); |
| dev_info(fb_data->dev, "S0 height 0x%x", |
| pxp_conf->s0_param.height); |
| dev_info(fb_data->dev, "S0 ckey 0x%x", |
| pxp_conf->s0_param.color_key); |
| dev_info(fb_data->dev, "S0 ckey en 0x%x", |
| pxp_conf->s0_param.color_key_enable); |
| |
| dev_info(fb_data->dev, "OL0 combine en 0x%x", |
| pxp_conf->ol_param[0].combine_enable); |
| dev_info(fb_data->dev, "OL0 fmt 0x%x", |
| pxp_conf->ol_param[0].pixel_fmt); |
| dev_info(fb_data->dev, "OL0 width 0x%x", |
| pxp_conf->ol_param[0].width); |
| dev_info(fb_data->dev, "OL0 height 0x%x", |
| pxp_conf->ol_param[0].height); |
| dev_info(fb_data->dev, "OL0 ckey 0x%x", |
| pxp_conf->ol_param[0].color_key); |
| dev_info(fb_data->dev, "OL0 ckey en 0x%x", |
| pxp_conf->ol_param[0].color_key_enable); |
| dev_info(fb_data->dev, "OL0 alpha 0x%x", |
| pxp_conf->ol_param[0].global_alpha); |
| dev_info(fb_data->dev, "OL0 alpha en 0x%x", |
| pxp_conf->ol_param[0].global_alpha_enable); |
| dev_info(fb_data->dev, "OL0 local alpha en 0x%x", |
| pxp_conf->ol_param[0].local_alpha_enable); |
| |
| dev_info(fb_data->dev, "Out fmt 0x%x", |
| pxp_conf->out_param.pixel_fmt); |
| dev_info(fb_data->dev, "Out width 0x%x", |
| pxp_conf->out_param.width); |
| dev_info(fb_data->dev, "Out height 0x%x", |
| pxp_conf->out_param.height); |
| |
| dev_info(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_info(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_info(fb_data->dev, "Scaling en 0x%x", pxp_conf->proc_data.scaling); |
| dev_info(fb_data->dev, "HFlip en 0x%x", pxp_conf->proc_data.hflip); |
| dev_info(fb_data->dev, "VFlip en 0x%x", pxp_conf->proc_data.vflip); |
| dev_info(fb_data->dev, "Rotation 0x%x", pxp_conf->proc_data.rotate); |
| dev_info(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_STRIDE 0x%x\n", __raw_readl(EPDC_UPD_STRIDE)); |
| 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_AUTOWV_LUT 0x%x\n", __raw_readl(EPDC_AUTOWV_LUT)); |
| 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_PIGEON_CTRL0 0x%x\n", __raw_readl(EPDC_PIGEON_CTRL0)); |
| printk(KERN_DEBUG "EPDC_PIGEON_CTRL1 0x%x\n", __raw_readl(EPDC_PIGEON_CTRL1)); |
| printk(KERN_DEBUG "EPDC_IRQ_MASK1 0x%x\n", __raw_readl(EPDC_IRQ_MASK1)); |
| printk(KERN_DEBUG "EPDC_IRQ_MASK2 0x%x\n", __raw_readl(EPDC_IRQ_MASK2)); |
| printk(KERN_DEBUG "EPDC_IRQ1 0x%x\n", __raw_readl(EPDC_IRQ1)); |
| printk(KERN_DEBUG "EPDC_IRQ2 0x%x\n", __raw_readl(EPDC_IRQ2)); |
| 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_LUTS2 0x%x\n", __raw_readl(EPDC_STATUS_LUTS2)); |
| printk(KERN_DEBUG "EPDC_STATUS_NEXTLUT 0x%x\n", __raw_readl(EPDC_STATUS_NEXTLUT)); |
| printk(KERN_DEBUG "EPDC_STATUS_COL1 0x%x\n", __raw_readl(EPDC_STATUS_COL)); |
| printk(KERN_DEBUG "EPDC_STATUS_COL2 0x%x\n", __raw_readl(EPDC_STATUS_COL2)); |
| printk(KERN_DEBUG "EPDC_STATUS 0x%x\n", __raw_readl(EPDC_STATUS)); |
| printk(KERN_DEBUG "EPDC_UPD_COL_CORD 0x%x\n", __raw_readl(EPDC_UPD_COL_CORD)); |
| printk(KERN_DEBUG "EPDC_UPD_COL_SIZE 0x%x\n", __raw_readl(EPDC_UPD_COL_SIZE)); |
| printk(KERN_DEBUG "EPDC_DEBUG 0x%x\n", __raw_readl(EPDC_DEBUG)); |
| printk(KERN_DEBUG "EPDC_DEBUG_LUT 0x%x\n", __raw_readl(EPDC_DEBUG_LUT)); |
| printk(KERN_DEBUG "EPDC_HIST1_PARAM 0x%x\n", __raw_readl(EPDC_HIST1_PARAM)); |
| printk(KERN_DEBUG "EPDC_HIST2_PARAM 0x%x\n", __raw_readl(EPDC_HIST2_PARAM)); |
| printk(KERN_DEBUG "EPDC_HIST4_PARAM 0x%x\n", __raw_readl(EPDC_HIST4_PARAM)); |
| printk(KERN_DEBUG "EPDC_HIST8_PARAM0 0x%x\n", __raw_readl(EPDC_HIST8_PARAM0)); |
| printk(KERN_DEBUG "EPDC_HIST8_PARAM1 0x%x\n", __raw_readl(EPDC_HIST8_PARAM1)); |
| printk(KERN_DEBUG "EPDC_HIST16_PARAM0 0x%x\n", __raw_readl(EPDC_HIST16_PARAM0)); |
| printk(KERN_DEBUG "EPDC_HIST16_PARAM1 0x%x\n", __raw_readl(EPDC_HIST16_PARAM1)); |
| printk(KERN_DEBUG "EPDC_HIST16_PARAM2 0x%x\n", __raw_readl(EPDC_HIST16_PARAM2)); |
| printk(KERN_DEBUG "EPDC_HIST16_PARAM3 0x%x\n", __raw_readl(EPDC_HIST16_PARAM3)); |
| 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_info(dev, |
| "X = %d, Y = %d, Width = %d, Height = %d, WaveMode = %d, " |
| "LUT = %d, Coll Mask = 0x%llx, order = %d\n", |
| upd_data_list->update_desc->upd_data.update_region.left, |
| upd_data_list->update_desc->upd_data.update_region.top, |
| upd_data_list->update_desc->upd_data.update_region.width, |
| upd_data_list->update_desc->upd_data.update_region.height, |
| upd_data_list->update_desc->upd_data.waveform_mode, |
| upd_data_list->lut_num, |
| upd_data_list->collision_mask, |
| upd_data_list->update_desc->update_order); |
| } |
| |
| static void dump_collision_list(struct mxc_epdc_fb_data *fb_data) |
| { |
| struct update_data_list *plist; |
| |
| dev_info(fb_data->dev, "Collision List:\n"); |
| if (list_empty(&fb_data->upd_buf_collision_list)) |
| dev_info(fb_data->dev, "Empty"); |
| list_for_each_entry(plist, &fb_data->upd_buf_collision_list, list) { |
| dev_info(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_info(fb_data->dev, "Free List:\n"); |
| if (list_empty(&fb_data->upd_buf_free_list)) |
| dev_info(fb_data->dev, "Empty"); |
| list_for_each_entry(plist, &fb_data->upd_buf_free_list, list) |
| dev_info(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ", |
| (u32)plist->virt_addr, plist->phys_addr); |
| } |
| |
| static void dump_queue(struct mxc_epdc_fb_data *fb_data) |
| { |
| struct update_data_list *plist; |
| |
| dev_info(fb_data->dev, "Queue:\n"); |
| if (list_empty(&fb_data->upd_buf_queue)) |
| dev_info(fb_data->dev, "Empty"); |
| list_for_each_entry(plist, &fb_data->upd_buf_queue, list) { |
| dev_info(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_desc_data(struct device *dev, |
| struct update_desc_list *upd_desc_list) |
| { |
| dev_info(dev, |
| "X = %d, Y = %d, Width = %d, Height = %d, WaveMode = %d, " |
| "order = %d\n", |
| upd_desc_list->upd_data.update_region.left, |
| upd_desc_list->upd_data.update_region.top, |
| upd_desc_list->upd_data.update_region.width, |
| upd_desc_list->upd_data.update_region.height, |
| upd_desc_list->upd_data.waveform_mode, |
| upd_desc_list->update_order); |
| } |
| |
| static void dump_pending_list(struct mxc_epdc_fb_data *fb_data) |
| { |
| struct update_desc_list *plist; |
| |
| dev_info(fb_data->dev, "Queue:\n"); |
| if (list_empty(&fb_data->upd_pending_list)) |
| dev_info(fb_data->dev, "Empty"); |
| list_for_each_entry(plist, &fb_data->upd_pending_list, list) |
| dump_desc_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_info(fb_data->dev, "Current update being processed:\n"); |
| if (fb_data->cur_update == NULL) |
| dev_info(fb_data->dev, "No current update\n"); |
| else |
| dump_update_data(fb_data->dev, fb_data->cur_update); |
| } |
| |
| static void dump_fw_header(struct device *dev, |
| struct mxcfb_waveform_data_file *fw) |
| { |
| dev_dbg(dev, "Firmware Header:\n"); |
| dev_dbg(dev, "wi0 0x%08x\n", fw->wdh.wi0); |
| dev_dbg(dev, "wi1 0x%08x\n", fw->wdh.wi1); |
| dev_dbg(dev, "wi2 0x%08x\n", fw->wdh.wi2); |
| dev_dbg(dev, "wi3 0x%08x\n", fw->wdh.wi3); |
| dev_dbg(dev, "wi4 0x%08x\n", fw->wdh.wi4); |
| dev_dbg(dev, "wi5 0x%08x\n", fw->wdh.wi5); |
| dev_dbg(dev, "wi6 0x%08x\n", fw->wdh.wi6); |
| dev_dbg(dev, "xwia:24 0x%06x\n", fw->wdh.xwia); |
| dev_dbg(dev, "cs1:8 0x%02x\n", fw->wdh.cs1); |
| dev_dbg(dev, "wmta:24 0x%06x\n", fw->wdh.wmta); |
| dev_dbg(dev, "fvsn:8 0x%02x\n", fw->wdh.fvsn); |
| dev_dbg(dev, "luts:8 0x%02x\n", fw->wdh.luts); |
| dev_dbg(dev, "mc:8 0x%02x\n", fw->wdh.mc); |
| dev_dbg(dev, "trc:8 0x%02x\n", fw->wdh.trc); |
| dev_dbg(dev, "reserved0_0 0x%02x\n", fw->wdh.reserved0_0); |
| dev_dbg(dev, "eb:8 0x%02x\n", fw->wdh.eb); |
| dev_dbg(dev, "sb:8 0x%02x\n", fw->wdh.sb); |
| dev_dbg(dev, "reserved0_1 0x%02x\n", fw->wdh.reserved0_1); |
| dev_dbg(dev, "reserved0_2 0x%02x\n", fw->wdh.reserved0_2); |
| dev_dbg(dev, "reserved0_3 0x%02x\n", fw->wdh.reserved0_3); |
| dev_dbg(dev, "reserved0_4 0x%02x\n", fw->wdh.reserved0_4); |
| dev_dbg(dev, "reserved0_5 0x%02x\n", fw->wdh.reserved0_5); |
| dev_dbg(dev, "cs2:8 0x%02x\n", fw->wdh.cs2); |
| } |
| |
| #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) {} |
| static void dump_fw_header(struct device *dev, |
| struct mxcfb_waveform_data_file *fw) {} |
| |
| #endif |
| |
| |
| /******************************************************** |
| * Start Low-Level EPDC Functions |
| ********************************************************/ |
| |
| static inline void epdc_lut_complete_intr(int rev, u32 lut_num, bool enable) |
| { |
| if (rev < 20) { |
| if (enable) |
| __raw_writel(1 << lut_num, EPDC_IRQ_MASK_SET); |
| else |
| __raw_writel(1 << lut_num, EPDC_IRQ_MASK_CLEAR); |
| } else { |
| if (enable) { |
| if (lut_num < 32) |
| __raw_writel(1 << lut_num, EPDC_IRQ_MASK1_SET); |
| else |
| __raw_writel(1 << (lut_num - 32), |
| EPDC_IRQ_MASK2_SET); |
| } else { |
| if (lut_num < 32) |
| __raw_writel(1 << lut_num, |
| EPDC_IRQ_MASK1_CLEAR); |
| else |
| __raw_writel(1 << (lut_num - 32), |
| EPDC_IRQ_MASK2_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_eof_intr(bool enable) |
| { |
| if (enable) |
| __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_MASK_SET); |
| else |
| __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_MASK_CLEAR); |
| } |
| |
| static inline void epdc_clear_eof_irq(void) |
| { |
| __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_CLEAR); |
| } |
| |
| static inline bool epdc_signal_eof(void) |
| { |
| return (__raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ) |
| & EPDC_IRQ_FRAME_END_IRQ) ? true : false; |
| } |
| |
| static inline void epdc_set_temp(u32 temp) |
| { |
| int ret = 0; |
| /* used to store external panel temperature value */ |
| unsigned int ext_temp, ext_temp_index = temp; |
| |
| if (temp == DEFAULT_TEMP_INDEX) { |
| ret = max17135_reg_read(REG_MAX17135_EXT_TEMP, &ext_temp); |
| if (ret == 0) { |
| ext_temp = ext_temp >> 8; |
| dev_dbg(g_fb_data->dev, "the current external temperature is %d\n", |
| ext_temp); |
| ext_temp_index = mxc_epdc_fb_get_temp_index(g_fb_data, ext_temp); |
| } |
| } |
| |
| __raw_writel(ext_temp_index, 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) |
| { |
| #ifdef EPDC_STANDARD_MODE |
| __raw_writel(0, EPDC_UPD_ADDR); |
| #else |
| __raw_writel(addr, EPDC_UPD_ADDR); |
| #endif |
| } |
| |
| 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_set_update_waveform(struct mxcfb_waveform_modes *wv_modes) |
| { |
| u32 val; |
| |
| #ifdef EPDC_STANDARD_MODE |
| return; |
| #endif |
| |
| /* Configure the auto-waveform look-up table based on waveform modes */ |
| |
| /* Entry 1 = DU, 2 = GC4, 3 = GC8, etc. */ |
| val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) | |
| (0 << EPDC_AUTOWV_LUT_ADDR_OFFSET); |
| __raw_writel(val, EPDC_AUTOWV_LUT); |
| val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) | |
| (1 << EPDC_AUTOWV_LUT_ADDR_OFFSET); |
| __raw_writel(val, EPDC_AUTOWV_LUT); |
| val = (wv_modes->mode_gc4 << EPDC_AUTOWV_LUT_DATA_OFFSET) | |
| (2 << EPDC_AUTOWV_LUT_ADDR_OFFSET); |
| __raw_writel(val, EPDC_AUTOWV_LUT); |
| val = (wv_modes->mode_gc8 << EPDC_AUTOWV_LUT_DATA_OFFSET) | |
| (3 << EPDC_AUTOWV_LUT_ADDR_OFFSET); |
| __raw_writel(val, EPDC_AUTOWV_LUT); |
| val = (wv_modes->mode_gc16 << EPDC_AUTOWV_LUT_DATA_OFFSET) | |
| (4 << EPDC_AUTOWV_LUT_ADDR_OFFSET); |
| __raw_writel(val, EPDC_AUTOWV_LUT); |
| val = (wv_modes->mode_gc32 << EPDC_AUTOWV_LUT_DATA_OFFSET) | |
| (5 << EPDC_AUTOWV_LUT_ADDR_OFFSET); |
| __raw_writel(val, EPDC_AUTOWV_LUT); |
| } |
| |
| static void epdc_set_update_stride(u32 stride) |
| { |
| #ifdef EPDC_STANDARD_MODE |
| __raw_writel(0, EPDC_UPD_STRIDE); |
| #else |
| __raw_writel(stride, EPDC_UPD_STRIDE); |
| #endif |
| } |
| |
| static void epdc_submit_update(u32 lut_num, u32 waveform_mode, u32 update_mode, |
| bool use_dry_run, 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; |
| reg_val |= |
| ((np_val << EPDC_UPD_FIXED_FIXCP_OFFSET) & |
| EPDC_UPD_FIXED_FIXCP_MASK) | EPDC_UPD_FIXED_FIXCP_EN; |
| |
| __raw_writel(reg_val, EPDC_UPD_FIXED); |
| |
| reg_val = EPDC_UPD_CTRL_USE_FIXED; |
| } else { |
| __raw_writel(reg_val, EPDC_UPD_FIXED); |
| } |
| |
| if (waveform_mode == WAVEFORM_MODE_AUTO) |
| reg_val |= EPDC_UPD_CTRL_AUTOWV; |
| else |
| reg_val |= ((waveform_mode << |
| EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET) & |
| EPDC_UPD_CTRL_WAVEFORM_MODE_MASK); |
| |
| reg_val |= (use_dry_run ? EPDC_UPD_CTRL_DRY_RUN : 0) | |
| ((lut_num << EPDC_UPD_CTRL_LUT_SEL_OFFSET) & |
| EPDC_UPD_CTRL_LUT_SEL_MASK) | |
| update_mode; |
| |
| #ifdef EPDC_STANDARD_MODE |
| reg_val |= 0x80000000; |
| |
| epdc_set_used_lut(lut_num); |
| #endif |
| dump_epdc_reg(); |
| __raw_writel(reg_val, EPDC_UPD_CTRL); |
| } |
| |
| static inline bool epdc_is_lut_complete(int rev, u32 lut_num) |
| { |
| u32 val; |
| bool is_compl; |
| if (rev < 20) { |
| val = __raw_readl(EPDC_IRQ); |
| is_compl = val & (1 << lut_num) ? true : false; |
| } else if (lut_num < 32) { |
| val = __raw_readl(EPDC_IRQ1); |
| is_compl = val & (1 << lut_num) ? true : false; |
| } else { |
| val = __raw_readl(EPDC_IRQ2); |
| is_compl = val & (1 << (lut_num - 32)) ? true : false; |
| } |
| |
| return is_compl; |
| } |
| |
| static inline void epdc_clear_lut_complete_irq(int rev, u32 lut_num) |
| { |
| if (rev < 20) |
| __raw_writel(1 << lut_num, EPDC_IRQ_CLEAR); |
| else if (lut_num < 32) |
| __raw_writel(1 << lut_num, EPDC_IRQ1_CLEAR); |
| else |
| __raw_writel(1 << (lut_num - 32), EPDC_IRQ2_CLEAR); |
| } |
| |
| static inline bool epdc_is_lut_active(u32 lut_num) |
| { |
| u32 val; |
| bool is_active; |
| |
| if (lut_num < 32) { |
| val = __raw_readl(EPDC_STATUS_LUTS); |
| is_active = val & (1 << lut_num) ? true : false; |
| } else { |
| val = __raw_readl(EPDC_STATUS_LUTS2); |
| is_active = val & (1 << (lut_num - 32)) ? true : false; |
| } |
| |
| return is_active; |
| } |
| |
| static inline bool epdc_any_luts_active(int rev) |
| { |
| bool any_active; |
| |
| if (rev < 20) |
| any_active = __raw_readl(EPDC_STATUS_LUTS) ? true : false; |
| else |
| any_active = (__raw_readl(EPDC_STATUS_LUTS) | |
| __raw_readl(EPDC_STATUS_LUTS2)) ? true : false; |
| |
| return any_active; |
| } |
| |
| static inline bool epdc_any_luts_real_available(void) |
| { |
| if ((__raw_readl(EPDC_STATUS_LUTS) != 0xfffffffe) || |
| (__raw_readl(EPDC_STATUS_LUTS2) != ~0UL)) |
| return true; |
| else |
| return false; |
| } |
| |
| static inline bool epdc_any_luts_available(void) |
| { |
| #ifdef EPDC_STANDARD_MODE |
| if (((u32)used_luts != ~0UL) || ((u32)(used_luts >> 32) != ~0UL)) |
| return 1; |
| else |
| return 0; |
| #else |
| bool luts_available = |
| (__raw_readl(EPDC_STATUS_NEXTLUT) & |
| EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID) ? true : false; |
| return luts_available; |
| #endif |
| } |
| |
| 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 void epdc_set_used_lut(u64 used_bit) |
| { |
| used_luts |= (u64)1 << used_bit; |
| } |
| |
| static inline void epdc_reset_used_lut(void) |
| { |
| used_luts = 0x1; |
| } |
| |
| #ifdef EPDC_STANDARD_MODE |
| /* |
| * in previous flow, when all LUTs are used, the LUT cleanup operation |
| * need to wait for all the LUT to finish, it will not happen util last LUT |
| * is done. while in new flow, the cleanup operation does not need to wait |
| * for all LUTs to finish, instead it can start when there's LUT's done. |
| * The saved time is multiple LUT operation time. |
| */ |
| static int epdc_choose_next_lut(struct mxc_epdc_fb_data *fb_data, int *next_lut) |
| { |
| while (!epdc_any_luts_available()) { |
| u64 luts_complete = fb_data->luts_complete; |
| pxp_clear_wb_work_func(fb_data); |
| used_luts &= ~luts_complete; |
| fb_data->luts_complete &= ~luts_complete; |
| mutex_unlock(&fb_data->queue_mutex); |
| msleep(10); |
| mutex_lock(&fb_data->queue_mutex); |
| } |
| |
| used_luts |= 0x1; |
| |
| if ((u32)used_luts != ~0UL) |
| *next_lut = ffz((u32)used_luts); |
| else if ((u32)(used_luts >> 32) != ~0UL) |
| *next_lut = ffz((u32)(used_luts >> 32)) + 32; |
| |
| return 0; |
| } |
| #else |
| static int epdc_choose_next_lut(struct mxc_epdc_fb_data *fb_data, int *next_lut) |
| { |
| u64 luts_status, unprocessed_luts, used_luts; |
| /* Available LUTs are reduced to 16 in 5-bit waveform mode */ |
| bool format_p5n = ((__raw_readl(EPDC_FORMAT) & |
| EPDC_FORMAT_BUF_PIXEL_FORMAT_MASK) == |
| EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N); |
| |
| luts_status = __raw_readl(EPDC_STATUS_LUTS); |
| if ((fb_data->rev < 20) || format_p5n) |
| luts_status &= 0xFFFF; |
| else |
| luts_status |= ((u64)__raw_readl(EPDC_STATUS_LUTS2) << 32); |
| |
| if (fb_data->rev < 20) { |
| unprocessed_luts = __raw_readl(EPDC_IRQ) & 0xFFFF; |
| } else { |
| unprocessed_luts = __raw_readl(EPDC_IRQ1) | |
| ((u64)__raw_readl(EPDC_IRQ2) << 32); |
| if (format_p5n) |
| unprocessed_luts &= 0xFFFF; |
| } |
| |
| /* |
| * Note on unprocessed_luts: There is a race condition |
| * where a LUT completes, but has not been processed by |
| * IRQ handler workqueue, and then a new update request |
| * attempts to use that LUT. We prevent that here by |
| * ensuring that the LUT we choose doesn't have its IRQ |
| * bit set (indicating it has completed but not yet been |
| * processed). |
| */ |
| used_luts = luts_status | unprocessed_luts; |
| |
| /* |
| * Selecting a LUT to minimize incidence of TCE Underrun Error |
| * -------------------------------------------------------- |
| * We want to find the lowest order LUT that is of greater |
| * order than all other active LUTs. If highest order LUT |
| * is active, then we want to choose the lowest order |
| * available LUT. |
| * |
| * NOTE: For EPDC version 2.0 and later, TCE Underrun error |
| * bug is fixed, so it doesn't matter which LUT is used. |
| */ |
| |
| if ((fb_data->rev < 20) || format_p5n) { |
| *next_lut = fls64(used_luts); |
| if (*next_lut > 15) |
| *next_lut = ffz(used_luts); |
| } else { |
| if ((u32)used_luts != ~0UL) |
| *next_lut = ffz((u32)used_luts); |
| else if ((u32)(used_luts >> 32) != ~0UL) |
| *next_lut = ffz((u32)(used_luts >> 32)) + 32; |
| else |
| *next_lut = INVALID_LUT; |
| } |
| |
| if (used_luts & 0x8000) |
| return 1; |
| else |
| return 0; |
| } |
| #endif |
| |
| 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_lut_cancelled(void) |
| { |
| u32 val = __raw_readl(EPDC_STATUS); |
| bool is_void = (val & EPDC_STATUS_UPD_VOID) ? true : false; |
| |
| return is_void; |
| } |
| |
| static inline bool epdc_is_collision(void) |
| { |
| u32 val = __raw_readl(EPDC_IRQ); |
| return (val & EPDC_IRQ_LUT_COL_IRQ) ? true : false; |
| } |
| |
| static inline u64 epdc_get_colliding_luts(int rev) |
| { |
| u32 val = __raw_readl(EPDC_STATUS_COL); |
| if (rev >= 20) |
| val |= (u64)__raw_readl(EPDC_STATUS_COL2) << 32; |
| 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); |
| } |
| |
| static void epdc_init_settings(struct mxc_epdc_fb_data *fb_data) |
| { |
| struct imx_epdc_fb_mode *epdc_mode = fb_data->cur_mode; |
| struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; |
| u32 reg_val; |
| int num_ce; |
| #ifndef EPDC_STANDARD_MODE |
| int i; |
| #endif |
| int j; |
| unsigned char *bb_p; |
| |
| /* Enable clocks to access EPDC regs */ |
| clk_prepare_enable(fb_data->epdc_clk_axi); |
| clk_prepare_enable(fb_data->epdc_clk_pix); |
| |
| /* 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; |
| #ifdef EPDC_STANDARD_MODE |
| reg_val |= EPDC_CTRL_UPD_DATA_SWIZZLE_ALL_BYTES_SWAP; |
| #else |
| reg_val |= EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP; |
| #endif |
| 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 |
| #ifdef EPDC_STANDARD_MODE |
| | EPDC_FORMAT_WB_TYPE_WB_EXTERNAL16 |
| #endif |
| | 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); |
| |
| #ifdef EPDC_STANDARD_MODE |
| reg_val = 0; |
| if (fb_data->waveform_is_advanced) { |
| reg_val = |
| ((EPDC_WB_FIELD_USAGE_PTS << EPDC_WB_FIELD_USAGE_OFFSET) & |
| EPDC_WB_FIELD_USAGE_MASK) |
| | ((0x8 << EPDC_WB_FIELD_FROM_OFFSET) & |
| EPDC_WB_FIELD_FROM_MASK) |
| | ((0x8 << EPDC_WB_FIELD_TO_OFFSET) & |
| EPDC_WB_FIELD_TO_MASK) |
| | ((0x1 << EPDC_WB_FIELD_LEN_OFFSET) & |
| EPDC_WB_FIELD_LEN_MASK); |
| } |
| __raw_writel(reg_val, EPDC_WB_FIELD3); |
| #endif |
| |
| /* 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 - Use default temp to get index */ |
| epdc_set_temp(mxc_epdc_fb_get_temp_index(fb_data, DEFAULT_TEMP)); |
| |
| /* EPDC_RES */ |
| epdc_set_screen_res(epdc_mode->vmode->xres, epdc_mode->vmode->yres); |
| |
| #ifndef EPDC_STANDARD_MODE |
| /* EPDC_AUTOWV_LUT */ |
| /* Initialize all auto-wavefrom look-up values to 2 - GC16 */ |
| for (i = 0; i < 8; i++) |
| __raw_writel((2 << EPDC_AUTOWV_LUT_DATA_OFFSET) | |
| (i << EPDC_AUTOWV_LUT_ADDR_OFFSET), EPDC_AUTOWV_LUT); |
| #endif |
| |
| /* |
| * 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); |
| |
| __raw_writel(fb_data->waveform_buffer_phys, EPDC_WVADDR); |
| __raw_writel(fb_data->working_buffer_phys, EPDC_WB_ADDR); |
| __raw_writel(fb_data->working_buffer_phys, EPDC_WB_ADDR_TCE); |
| |
| bb_p = (unsigned char *)fb_data->virt_addr_black; |
| for (j = 0; j < fb_data->cur_mode->vmode->xres * fb_data->cur_mode->vmode->yres; j++) { |
| *bb_p = 0x0; |
| bb_p++; |
| } |
| |
| /* Disable clock */ |
| clk_disable_unprepare(fb_data->epdc_clk_axi); |
| clk_disable_unprepare(fb_data->epdc_clk_pix); |
| } |
| |
| static void epdc_powerup(struct mxc_epdc_fb_data *fb_data) |
| { |
| int ret = 0; |
| mutex_lock(&fb_data->power_mutex); |
| |
| /* |
| * If power down request is pending, clear |
| * powering_down to cancel the request. |
| */ |
| if (fb_data->powering_down) |
| fb_data->powering_down = false; |
| |
| if (fb_data->power_state == POWER_STATE_ON) { |
| mutex_unlock(&fb_data->power_mutex); |
| return; |
| } |
| |
| dev_dbg(fb_data->dev, "EPDC Powerup\n"); |
| |
| fb_data->updates_active = true; |
| |
| /* Enable the v3p3 regulator */ |
| ret = regulator_enable(fb_data->v3p3_regulator); |
| if (IS_ERR((void *)ret)) { |
| dev_err(fb_data->dev, "Unable to enable V3P3 regulator." |
| "err = 0x%x\n", ret); |
| mutex_unlock(&fb_data->power_mutex); |
| return; |
| } |
| |
| msleep(1); |
| |
| pm_runtime_get_sync(fb_data->dev); |
| |
| /* Enable clocks to EPDC */ |
| clk_prepare_enable(fb_data->epdc_clk_axi); |
| clk_prepare_enable(fb_data->epdc_clk_pix); |
| |
| __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_CLEAR); |
| |
| /* Enable power to the EPD panel */ |
| ret = regulator_enable(fb_data->display_regulator); |
| if (IS_ERR((void *)ret)) { |
| dev_err(fb_data->dev, "Unable to enable DISPLAY regulator." |
| "err = 0x%x\n", ret); |
| mutex_unlock(&fb_data->power_mutex); |
| return; |
| } |
| ret = regulator_enable(fb_data->vcom_regulator); |
| if (IS_ERR((void *)ret)) { |
| dev_err(fb_data->dev, "Unable to enable VCOM regulator." |
| "err = 0x%x\n", ret); |
| mutex_unlock(&fb_data->power_mutex); |
| return; |
| } |
| |
| fb_data->power_state = POWER_STATE_ON; |
| |
| 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_unprepare(fb_data->epdc_clk_pix); |
| clk_disable_unprepare(fb_data->epdc_clk_axi); |
| |
| pm_runtime_put_sync_suspend(fb_data->dev); |
| |
| /* turn off the V3p3 */ |
| regulator_disable(fb_data->v3p3_regulator); |
| |
| fb_data->power_state = POWER_STATE_OFF; |
| fb_data->powering_down = false; |
| |
| if (fb_data->wait_for_powerdown) { |
| fb_data->wait_for_powerdown = false; |
| complete(&fb_data->powerdown_compl); |
| } |
| |
| mutex_unlock(&fb_data->power_mutex); |
| } |
| |
| static void epdc_init_sequence(struct mxc_epdc_fb_data *fb_data) |
| { |
| /* Initialize EPDC, passing pointer to EPDC registers */ |
| epdc_init_settings(fb_data); |
| |
| fb_data->in_init = true; |
| epdc_powerup(fb_data); |
| draw_mode0(fb_data); |
| /* Force power down event */ |
| fb_data->powering_down = true; |
| epdc_powerdown(fb_data); |
| fb_data->updates_active = false; |
| } |
| |
| 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); |
| |
| 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 inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) |
| { |
| chan &= 0xffff; |
| chan >>= 16 - bf->length; |
| return chan << bf->offset; |
| } |
| |
| static int mxc_epdc_fb_setcolreg(u_int regno, u_int red, u_int green, |
| u_int blue, u_int transp, struct fb_info *info) |
| { |
| unsigned int val; |
| int ret = 1; |
| |
| /* |
| * If greyscale is true, then we convert the RGB value |
| * to greyscale no matter what visual we are using. |
| */ |
| if (info->var.grayscale) |
| red = green = blue = (19595 * red + 38470 * green + |
| 7471 * blue) >> 16; |
| switch (info->fix.visual) { |
| case FB_VISUAL_TRUECOLOR: |
| /* |
| * 16-bit True Colour. We encode the RGB value |
| * according to the RGB bitfield information. |
| */ |
| if (regno < 16) { |
| u32 *pal = info->pseudo_palette; |
| |
| val = _chan_to_field(red, &info->var.red); |
| val |= _chan_to_field(green, &info->var.green); |
| val |= _chan_to_field(blue, &info->var.blue); |
| |
| pal[regno] = val; |
| ret = 0; |
| } |
| break; |
| |
| case FB_VISUAL_STATIC_PSEUDOCOLOR: |
| case FB_VISUAL_PSEUDOCOLOR: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int mxc_epdc_fb_setcmap(struct fb_cmap *cmap, struct fb_info *info) |
| { |
| int count, index, r; |
| u16 *red, *green, *blue, *transp; |
| u16 trans = 0xffff; |
| struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; |
| int i; |
| |
| dev_dbg(fb_data->dev, "setcmap\n"); |
| |
| if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) { |
| /* Only support an 8-bit, 256 entry lookup */ |
| if (cmap->len != 256) |
| return 1; |
| |
| mxc_epdc_fb_flush_updates(fb_data); |
| |
| mutex_lock(&fb_data->pxp_mutex); |
| /* |
| * Store colormap in pxp_conf structure for later transmit |
| * to PxP during update process to convert gray pixels. |
| * |
| * Since red=blue=green for pseudocolor visuals, we can |
| * just use red values. |
| */ |
| for (i = 0; i < 256; i++) |
| fb_data->pxp_conf.proc_data.lut_map[i] = cmap->red[i] & 0xFF; |
| |
| fb_data->pxp_conf.proc_data.lut_map_updated = true; |
| |
| mutex_unlock(&fb_data->pxp_mutex); |
| } else { |
| red = cmap->red; |
| green = cmap->green; |
| blue = cmap->blue; |
| transp = cmap->transp; |
| index = cmap->start; |
| |
| for (count = 0; count < cmap->len; count++) { |
| if (transp) |
| trans = *transp++; |
| r = mxc_epdc_fb_setcolreg(index++, *red++, *green++, *blue++, |
| trans, info); |
| if (r != 0) |
| return r; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void adjust_coordinates(u32 xres, u32 yres, u32 rotation, |
| struct mxcfb_rect *update_region, struct mxcfb_rect *adj_update_region) |
| { |
| u32 temp; |
| |
| /* If adj_update_region == NULL, pass result back in update_region */ |
| /* If adj_update_region == valid, use it to pass back result */ |
| if (adj_update_region) |
| switch (rotation) { |
| case FB_ROTATE_UR: |
| adj_update_region->top = update_region->top; |
| adj_update_region->left = update_region->left; |
| adj_update_region->width = update_region->width; |
| adj_update_region->height = update_region->height; |
| break; |
| case FB_ROTATE_CW: |
| adj_update_region->top = update_region->left; |
| adj_update_region->left = yres - |
| (update_region->top + update_region->height); |
| adj_update_region->width = update_region->height; |
| adj_update_region->height = update_region->width; |
| break; |
| case FB_ROTATE_UD: |
| adj_update_region->width = update_region->width; |
| adj_update_region->height = update_region->height; |
| adj_update_region->top = yres - |
| (update_region->top + update_region->height); |
| adj_update_region->left = xres - |
| (update_region->left + update_region->width); |
| break; |
| case FB_ROTATE_CCW: |
| adj_update_region->left = update_region->top; |
| adj_update_region->top = xres - |
| (update_region->left + update_region->width); |
| adj_update_region->width = update_region->height; |
| adj_update_region->height = update_region->width; |
| break; |
| } |
| else |
| switch (rotation) { |
| case FB_ROTATE_UR: |
| /* No adjustment needed */ |
| break; |
| case FB_ROTATE_CW: |
| temp = update_region->top; |
| update_region->top = update_region->left; |
| update_region->left = yres - |
| (temp + update_region->height); |
| temp = update_region->width; |
| update_region->width = update_region->height; |
| update_region->height = temp; |
| break; |
| case FB_ROTATE_UD: |
| update_region->top = yres - |
| (update_region->top + update_region->height); |
| update_region->left = xres - |
| (update_region->left + update_region->width); |
| break; |
| case FB_ROTATE_CCW: |
| temp = update_region->left; |
| update_region->left = update_region->top; |
| update_region->top = xres - |
| (temp + update_region->width); |
| temp = update_region->width; |
| update_region->width = update_region->height; |
| update_region->height = temp; |
| break; |
| } |
| } |
| |
| /* |
| * 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; |
| if (var->grayscale) |
| fix->visual = FB_VISUAL_STATIC_PSEUDOCOLOR; |
| else |
| 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; |
| struct imx_epdc_fb_mode *epdc_modes = fb_data->pdata->epdc_mode; |
| int i; |
| int ret; |
| __u32 xoffset_old, yoffset_old; |
| |
| /* |
| * Can't change the FB parameters until current updates have completed. |
| * This function returns when all active updates are done. |
| */ |
| mxc_epdc_fb_flush_updates(fb_data); |
| |
| mutex_lock(&fb_data->queue_mutex); |
| /* |
| * Set all screeninfo except for xoffset/yoffset |
| * Subsequent call to pan_display will handle those. |
| */ |
| xoffset_old = fb_data->epdc_fb_var.xoffset; |
| yoffset_old = fb_data->epdc_fb_var.yoffset; |
| fb_data->epdc_fb_var = *screeninfo; |
| fb_data->epdc_fb_var.xoffset = xoffset_old; |
| fb_data->epdc_fb_var.yoffset = yoffset_old; |
| mutex_unlock(&fb_data->queue_mutex); |
| |
| mutex_lock(&fb_data->pxp_mutex); |
| |
| /* |
| * Update PxP config data (used to process FB regions for updates) |
| * based on FB info and processing tasks required |
| */ |
| |
| /* Initialize non-channel-specific PxP parameters */ |
| proc_data->drect.left = proc_data->srect.left = 0; |
| proc_data->drect.top = proc_data->srect.top = 0; |
| proc_data->drect.width = proc_data->srect.width = screeninfo->xres; |
| proc_data->drect.height = proc_data->srect.height = screeninfo->yres; |
| proc_data->scaling = 0; |
| proc_data->hflip = 0; |
| proc_data->vflip = 0; |
| proc_data->rotate = screeninfo->rotate; |
| proc_data->bgcolor = 0; |
| proc_data->overlay_state = 0; |
| proc_data->lut_transform = PXP_LUT_NONE; |
| |
| /* |
| * configure S0 channel parameters |
| * Parameters should match FB format/width/height |
| */ |
| if (screeninfo->grayscale) |
| pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_GREY; |
| 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_XRGB32; |
| 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.stride = (screeninfo->bits_per_pixel * pxp_conf->s0_param.width) >> 3; |
| 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; |
| |
| mutex_unlock(&fb_data->pxp_mutex); |
| |
| /* |
| * If HW not yet initialized, check to see if we are being sent |
| * an initialization request. |
| */ |
| if (!fb_data->hw_ready) { |
| struct fb_videomode mode; |
| u32 xres_temp; |
| |
| fb_var_to_videomode(&mode, screeninfo); |
| |
| /* When comparing requested fb mode, |
| we need to use unrotated dimensions */ |
| if ((screeninfo->rotate == FB_ROTATE_CW) || |
| (screeninfo->rotate == FB_ROTATE_CCW)) { |
| xres_temp = mode.xres; |
| mode.xres = mode.yres; |
| mode.yres = xres_temp; |
| } |
| |
| /* |
| * If requested video mode does not match current video |
| * mode, search for a matching panel. |
| */ |
| if (fb_data->cur_mode && |
| !fb_mode_is_equal(fb_data->cur_mode->vmode, |
| &mode)) { |
| bool found_match = false; |
| |
| /* Match videomode against epdc modes */ |
| for (i = 0; i < fb_data->pdata->num_modes; i++) { |
| if (!fb_mode_is_equal(epdc_modes[i].vmode, |
| &mode)) |
| continue; |
| fb_data->cur_mode = &epdc_modes[i]; |
| found_match = true; |
| break; |
| } |
| |
| if (!found_match) { |
| dev_err(fb_data->dev, |
| "Failed to match requested " |
| "video mode\n"); |
| return EINVAL; |
| } |
| } |
| |
| /* Found a match - Grab timing params */ |
| screeninfo->left_margin = mode.left_margin; |
| screeninfo->right_margin = mode.right_margin; |
| screeninfo->upper_margin = mode.upper_margin; |
| screeninfo->lower_margin = mode.lower_margin; |
| screeninfo->hsync_len = mode.hsync_len; |
| screeninfo->vsync_len = mode.vsync_len; |
| |
| fb_data->hw_initializing = true; |
| |
| /* 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; |
| } |
| } |
| |
| /* |
| * EOF sync delay (in us) should be equal to the vscan holdoff time |
| * VSCAN_HOLDOFF time = (VSCAN_HOLDOFF value + 1) * Vertical lines |
| * Add 25us for additional margin |
| */ |
| fb_data->eof_sync_period = (fb_data->cur_mode->vscan_holdoff + 1) * |
| 1000000/(fb_data->cur_mode->vmode->refresh * |
| (fb_data->cur_mode->vmode->upper_margin + |
| fb_data->cur_mode->vmode->yres + |
| fb_data->cur_mode->vmode->lower_margin + |
| fb_data->cur_mode->vmode->vsync_len)) + 25; |
| |
| 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) * fb_data->num_screens; |
| |
| var->height = -1; |
| var->width = -1; |
| |
| return 0; |
| } |
| |
| static void mxc_epdc_fb_set_waveform_modes(struct mxcfb_waveform_modes *modes, |
| struct fb_info *info) |
| { |
| struct mxc_epdc_fb_data *fb_data = info ? |
| (struct mxc_epdc_fb_data *)info:g_fb_data; |
| |
| mutex_lock(&fb_data->queue_mutex); |
| |
| memcpy(&fb_data->wv_modes, modes, sizeof(struct mxcfb_waveform_modes)); |
| |
| /* Set flag to ensure that new waveform modes |
| * are programmed into EPDC before next update */ |
| fb_data->wv_modes_update = true; |
| |
| mutex_unlock(&fb_data->queue_mutex); |
| } |
| |
| 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 DEFAULT_TEMP_INDEX; |
| } |
| |
| /* 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 DEFAULT_TEMP_INDEX; |
| } |
| |
| 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 = info ? |
| (struct mxc_epdc_fb_data *)info:g_fb_data; |
| |
| /* Store temp index. Used later when configuring updates. */ |
| mutex_lock(&fb_data->queue_mutex); |
| fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, temperature); |
| mutex_unlock(&fb_data->queue_mutex); |
| |
| return 0; |
| } |
| |
| static int mxc_epdc_fb_set_auto_update(u32 auto_mode, struct fb_info *info) |
| { |
| struct mxc_epdc_fb_data *fb_data = info ? |
| (struct mxc_epdc_fb_data *)info:g_fb_data; |
| |
| dev_dbg(fb_data->dev, "Setting auto update mode to %d\n", auto_mode); |
| |
| if ((auto_mode == AUTO_UPDATE_MODE_AUTOMATIC_MODE) |
| || (auto_mode == AUTO_UPDATE_MODE_REGION_MODE)) |
| fb_data->auto_mode = auto_mode; |
| else { |
| dev_err(fb_data->dev, "Invalid auto update mode parameter.\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int mxc_epdc_fb_set_upd_scheme(u32 upd_scheme, struct fb_info *info) |
| { |
| struct mxc_epdc_fb_data *fb_data = info ? |
| (struct mxc_epdc_fb_data *)info:g_fb_data; |
| |
| dev_dbg(fb_data->dev, "Setting optimization level to %d\n", upd_scheme); |
| |
| /* |
| * Can't change the scheme until current updates have completed. |
| * This function returns when all active updates are done. |
| */ |
| mxc_epdc_fb_flush_updates(fb_data); |
| |
| if ((upd_scheme == UPDATE_SCHEME_SNAPSHOT) |
| || (upd_scheme == UPDATE_SCHEME_QUEUE) |
| || (upd_scheme == UPDATE_SCHEME_QUEUE_AND_MERGE)) |
| fb_data->upd_scheme = upd_scheme; |
| else { |
| dev_err(fb_data->dev, "Invalid update scheme specified.\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static void copy_before_process(struct mxc_epdc_fb_data *fb_data, |
| struct update_data_list *upd_data_list) |
| { |
| struct mxcfb_update_data *upd_data = |
| &upd_data_list->update_desc->upd_data; |
| int i; |
| unsigned char *temp_buf_ptr = fb_data->virt_addr_copybuf; |
| unsigned char *src_ptr; |
| struct mxcfb_rect *src_upd_region; |
| int temp_buf_stride; |
| int src_stride; |
| int bpp = fb_data->epdc_fb_var.bits_per_pixel; |
| int left_offs, right_offs; |
| int x_trailing_bytes, y_trailing_bytes; |
| int alt_buf_offset; |
| |
| /* Set source buf pointer based on input source, panning, etc. */ |
| if (upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) { |
| src_upd_region = &upd_data->alt_buffer_data.alt_update_region; |
| src_stride = |
| upd_data->alt_buffer_data.width * bpp/8; |
| alt_buf_offset = upd_data->alt_buffer_data.phys_addr - |
| fb_data->info.fix.smem_start; |
| src_ptr = fb_data->info.screen_base + alt_buf_offset |
| + src_upd_region->top * src_stride; |
| } else { |
| src_upd_region = &upd_data->update_region; |
| src_stride = fb_data->epdc_fb_var.xres_virtual * bpp/8; |
| src_ptr = fb_data->info.screen_base + fb_data->fb_offset |
| + src_upd_region->top * src_stride; |
| } |
| |
| temp_buf_stride = ALIGN(src_upd_region->width, 8) * bpp/8; |
| left_offs = src_upd_region->left * bpp/8; |
| right_offs = src_upd_region->width * bpp/8; |
| x_trailing_bytes = (ALIGN(src_upd_region->width, 8) |
| - src_upd_region->width) * bpp/8; |
| |
| for (i = 0; i < src_upd_region->height; i++) { |
| /* Copy the full line */ |
| memcpy(temp_buf_ptr, src_ptr + left_offs, |
| src_upd_region->width * bpp/8); |
| |
| /* Clear any unwanted pixels at the end of each line */ |
| if (src_upd_region->width & 0x7) { |
| memset(temp_buf_ptr + right_offs, 0x0, |
| x_trailing_bytes); |
| } |
| |
| temp_buf_ptr += temp_buf_stride; |
| src_ptr += src_stride; |
| } |
| |
| /* Clear any unwanted pixels at the bottom of the end of each line */ |
| if (src_upd_region->height & 0x7) { |
| y_trailing_bytes = (ALIGN(src_upd_region->height, 8) |
| - src_upd_region->height) * |
| ALIGN(src_upd_region->width, 8) * bpp/8; |
| memset(temp_buf_ptr, 0x0, y_trailing_bytes); |
| } |
| } |
| |
| /* Before every update to panel, we should call this |
| * function to update the working buffer first. |
| */ |
| static int epdc_working_buffer_update(struct mxc_epdc_fb_data *fb_data, |
| struct update_data_list *upd_data_list, |
| struct mxcfb_rect *update_region) |
| { |
| struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; |
| u32 wv_mode = upd_data_list->update_desc->upd_data.waveform_mode; |
| int ret = 0; |
| u32 hist_stat; |
| struct update_desc_list *upd_desc_list; |
| |
| ret = pxp_wfe_a_process(fb_data, update_region, upd_data_list); |
| if (ret) { |
| dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); |
| mutex_unlock(&fb_data->pxp_mutex); |
| return ret; |
| } |
| |
| /* If needed, enable EPDC HW while ePxP is processing */ |
| if ((fb_data->power_state == POWER_STATE_OFF) |
| || fb_data->powering_down) { |
| epdc_powerup(fb_data); |
| } |
| |
| /* This is a blocking call, so upon return PxP tx should be done */ |
| ret = pxp_complete_update(fb_data, &fb_data->hist_status); |
| if (ret) { |
| dev_err(fb_data->dev, "Unable to complete PxP update task: main process\n"); |
| return ret; |
| } |
| |
| upd_desc_list = upd_data_list->update_desc; |
| if (fb_data->epdc_wb_mode && |
| (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) { |
| hist_stat = fb_data->hist_status; |
| |
| if (hist_stat & 0x1) |
| upd_desc_list->upd_data.waveform_mode = |
| fb_data->wv_modes.mode_du; |
| else if (hist_stat & 0x2) |
| upd_desc_list->upd_data.waveform_mode = |
| fb_data->wv_modes.mode_gc4; |
| else if (hist_stat & 0x4) |
| upd_desc_list->upd_data.waveform_mode = |
| fb_data->wv_modes.mode_gc8; |
| else if (hist_stat & 0x8) |
| upd_desc_list->upd_data.waveform_mode = |
| fb_data->wv_modes.mode_gc16; |
| else |
| upd_desc_list->upd_data.waveform_mode = |
| fb_data->wv_modes.mode_gc32; |
| |
| dev_dbg(fb_data->dev, "hist_stat = 0x%x, new waveform = 0x%x\n", |
| hist_stat, upd_desc_list->upd_data.waveform_mode); |
| } |
| |
| if (proc_data->detection_only == 1) { |
| dev_dbg(fb_data->dev, "collision detection only, no real update\n"); |
| return 0; |
| } |
| |
| if (fb_data->col_info.pixel_cnt) { |
| dev_dbg(fb_data->dev, "collision detected, can not do REAGl/-D\n"); |
| return 0; |
| } |
| |
| /* for REAGL/-D Processing */ |
| if (wv_mode == WAVEFORM_MODE_GLR16 |
| || wv_mode == WAVEFORM_MODE_GLD16) { |
| /* This is a blocking call, so upon return PxP tx should be done */ |
| ret = pxp_wfe_b_process_update(fb_data, update_region); |
| if (ret) { |
| dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); |
| mutex_unlock(&fb_data->pxp_mutex); |
| return ret; |
| } |
| |
| /* If needed, enable EPDC HW while ePxP is processing */ |
| if ((fb_data->power_state == POWER_STATE_OFF) |
| || fb_data->powering_down) { |
| epdc_powerup(fb_data); |
| } |
| |
| /* This is a blocking call, so upon return PxP tx should be done */ |
| ret = pxp_complete_update(fb_data, &hist_stat); |
| if (ret) { |
| dev_err(fb_data->dev, "Unable to complete PxP update task: reagl/-d process\n"); |
| mutex_unlock(&fb_data->pxp_mutex); |
| return ret; |
| } |
| |
| } |
| |
| return 0; |
| } |
| |
| static int epdc_process_update(struct update_data_list *upd_data_list, |
| struct mxc_epdc_fb_data *fb_data) |
| { |
| struct mxcfb_rect *src_upd_region; /* Region of src buffer for update */ |
| struct mxcfb_rect pxp_upd_region; |
| u32 src_width, src_height; |
| u32 offset_from_4, bytes_per_pixel; |
| u32 post_rotation_xcoord, post_rotation_ycoord, width_pxp_blocks; |
| u32 pxp_input_offs, pxp_output_offs, pxp_output_shift; |
| u32 hist_stat = 0; |
| int width_unaligned, height_unaligned; |
| bool input_unaligned = false; |
| bool line_overflow = false; |
| int pix_per_line_added; |
| bool use_temp_buf = false; |
| struct mxcfb_rect temp_buf_upd_region; |
| struct update_desc_list *upd_desc_list = upd_data_list->update_desc; |
| |
| int ret; |
| |
| /* |
| * Gotta do a whole bunch of buffer ptr manipulation to |
| * work around HW restrictions for PxP & EPDC |
| * Note: Applies to pre-2.0 versions of EPDC/PxP |
| */ |
| |
| /* |
| * Are we using FB or an alternate (overlay) |
| * buffer for source of update? |
| */ |
| if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) { |
| src_width = upd_desc_list->upd_data.alt_buffer_data.width; |
| src_height = upd_desc_list->upd_data.alt_buffer_data.height; |
| src_upd_region = &upd_desc_list->upd_data.alt_buffer_data.alt_update_region; |
| } else { |
| src_width = fb_data->epdc_fb_var.xres_virtual; |
| src_height = fb_data->epdc_fb_var.yres; |
| src_upd_region = &upd_desc_list->upd_data.update_region; |
| } |
| |
| bytes_per_pixel = fb_data->epdc_fb_var.bits_per_pixel/8; |
| |
| /* |
| * SW workaround for PxP limitation (for pre-v2.0 HW) |
| * |
| * There are 3 cases where we cannot process the update data |
| * directly from the input buffer: |
| * |
| * 1) PxP must process 8x8 pixel blocks, and all pixels in each block |
| * are considered for auto-waveform mode selection. If the |
| * update region is not 8x8 aligned, additional unwanted pixels |
| * will be considered in auto-waveform mode selection. |
| * |
| * 2) PxP input must be 32-bit aligned, so any update |
| * address not 32-bit aligned must be shifted to meet the |
| * 32-bit alignment. The PxP will thus end up processing pixels |
| * outside of the update region to satisfy this alignment restriction, |
| * which can affect auto-waveform mode selection. |
| * |
| * 3) If input fails 32-bit alignment, and the resulting expansion |
| * of the processed region would add at least 8 pixels more per |
| * line than the original update line width, the EPDC would |
| * cause screen artifacts by incorrectly handling the 8+ pixels |
| * at the end of each line. |
| * |
| * Workaround is to copy from source buffer into a temporary |
| * buffer, which we pad with zeros to match the 8x8 alignment |
| * requirement. This temp buffer becomes the input to the PxP. |
| */ |
| width_unaligned = src_upd_region->width & 0x7; |
| height_unaligned = src_upd_region->height & 0x7; |
| |
| offset_from_4 = src_upd_region->left & 0x3; |
| input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ? |
| true : false; |
| |
| pix_per_line_added = (offset_from_4 * bytes_per_pixel % 4) |
| / bytes_per_pixel; |
| if ((((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) || |
| fb_data->epdc_fb_var.rotate == FB_ROTATE_UD)) && |
| (ALIGN(src_upd_region->width, 8) < |
| ALIGN(src_upd_region->width + pix_per_line_added, 8))) |
| line_overflow = true; |
| |
| /* Grab pxp_mutex here so that we protect access |
| * to copybuf in addition to the PxP structures */ |
| mutex_lock(&fb_data->pxp_mutex); |
| |
| if (((((width_unaligned || height_unaligned || input_unaligned) && |
| (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) |
| || line_overflow) && (fb_data->rev < 20)) || |
| fb_data->restrict_width) { |
| dev_dbg(fb_data->dev, "Copying update before processing.\n"); |
| |
| /* Update to reflect what the new source buffer will be */ |
| src_width = ALIGN(src_upd_region->width, 8); |
| src_height = ALIGN(src_upd_region->height, 8); |
| |
| copy_before_process(fb_data, upd_data_list); |
| |
| /* |
| * src_upd_region should now describe |
| * the new update buffer attributes. |
| */ |
| temp_buf_upd_region.left = 0; |
| temp_buf_upd_region.top = 0; |
| temp_buf_upd_region.width = src_upd_region->width; |
| temp_buf_upd_region.height = src_upd_region->height; |
| src_upd_region = &temp_buf_upd_region; |
| |
| use_temp_buf = true; |
| } |
| |
| /* |
| * For pre-2.0 HW, input address must be 32-bit aligned |
| * Compute buffer offset to account for this PxP limitation |
| */ |
| offset_from_4 = src_upd_region->left & 0x3; |
| input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ? |
| true : false; |
| if ((fb_data->rev < 20) && input_unaligned) { |
| /* Leave a gap between PxP input addr and update region pixels */ |
| pxp_input_offs = |
| (src_upd_region->top * src_width + src_upd_region->left) |
| * bytes_per_pixel & 0xFFFFFFFC; |
| /* Update region left changes to reflect relative position to input ptr */ |
| pxp_upd_region.left = (offset_from_4 * bytes_per_pixel % 4) |
| / bytes_per_pixel; |
| } else { |
| pxp_input_offs = |
| (src_upd_region->top * src_width + src_upd_region->left) |
| * bytes_per_pixel; |
| pxp_upd_region.left = 0; |
| } |
| |
| pxp_upd_region.top = 0; |
| |
| /* |
| * For version 2.0 and later of EPDC & PxP, if no rotation, we don't |
| * need to align width & height (rotation always requires 8-pixel |
| * width & height alignment, per PxP limitations) |
| */ |
| if ((fb_data->epdc_fb_var.rotate == 0) && (fb_data->rev >= 20)) { |
| pxp_upd_region.width = src_upd_region->width; |
| pxp_upd_region.height = src_upd_region->height; |
| } else { |
| /* Update region dimensions to meet 8x8 pixel requirement */ |
| pxp_upd_region.width = ALIGN(src_upd_region->width + pxp_upd_region.left, 8); |
| pxp_upd_region.height = ALIGN(src_upd_region->height, 8); |
| } |
| |
| switch (fb_data->epdc_fb_var.rotate) { |
| case FB_ROTATE_UR: |
| default: |
| post_rotation_xcoord = pxp_upd_region.left; |
| post_rotation_ycoord = pxp_upd_region.top; |
| width_pxp_blocks = pxp_upd_region.width; |
| break; |
| case FB_ROTATE_CW: |
| width_pxp_blocks = pxp_upd_region.height; |
| post_rotation_xcoord = width_pxp_blocks - src_upd_region->height; |
| post_rotation_ycoord = pxp_upd_region.left; |
| break; |
| case FB_ROTATE_UD: |
| width_pxp_blocks = pxp_upd_region.width; |
| post_rotation_xcoord = width_pxp_blocks - src_upd_region->width - pxp_upd_region.left; |
| post_rotation_ycoord = pxp_upd_region.height - src_upd_region->height - pxp_upd_region.top; |
| break; |
| case FB_ROTATE_CCW: |
| width_pxp_blocks = pxp_upd_region.height; |
| post_rotation_xcoord = pxp_upd_region.top; |
| post_rotation_ycoord = pxp_upd_region.width - src_upd_region->width - pxp_upd_region.left; |
| break; |
| } |
| |
| /* Update region start coord to force PxP to process full 8x8 regions */ |
| pxp_upd_region.top &= ~0x7; |
| pxp_upd_region.left &= ~0x7; |
| |
| if (fb_data->rev < 20) { |
| pxp_output_shift = ALIGN(post_rotation_xcoord, 8) |
| - post_rotation_xcoord; |
| |
| pxp_output_offs = post_rotation_ycoord * width_pxp_blocks |
| + pxp_output_shift; |
| |
| upd_desc_list->epdc_offs = ALIGN(pxp_output_offs, 8); |
| } else { |
| pxp_output_shift = 0; |
| pxp_output_offs = post_rotation_ycoord * width_pxp_blocks |
| + post_rotation_xcoord; |
| |
| upd_desc_list->epdc_offs = pxp_output_offs; |
| } |
| |
| upd_desc_list->epdc_stride = width_pxp_blocks; |
| |
| /* Source address either comes from alternate buffer |
| provided in update data, or from the framebuffer. */ |
| if (use_temp_buf) |
| sg_dma_address(&fb_data->sg[0]) = |
| fb_data->phys_addr_copybuf; |
| else if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) |
| sg_dma_address(&fb_data->sg[0]) = |
| upd_desc_list->upd_data.alt_buffer_data.phys_addr |
| + pxp_input_offs; |
| else { |
| sg_dma_address(&fb_data->sg[0]) = |
| fb_data->info.fix.smem_start + fb_data->fb_offset |
| + pxp_input_offs; |
| sg_set_page(&fb_data->sg[0], |
| virt_to_page(fb_data->info.screen_base), |
| fb_data->info.fix.smem_len, |
| offset_in_page(fb_data->info.screen_base)); |
| } |
| |
| /* Update sg[1] to point to output of PxP proc task */ |
| sg_dma_address(&fb_data->sg[1]) = upd_data_list->phys_addr |
| + pxp_output_shift; |
| sg_set_page(&fb_data->sg[1], virt_to_page(upd_data_list->virt_addr), |
| fb_data->max_pix_size, |
| offset_in_page(upd_data_list->virt_addr)); |
| |
| /* |
| * Set PxP LUT transform type based on update flags. |
| */ |
| fb_data->pxp_conf.proc_data.lut_transform = 0; |
| if (upd_desc_list->upd_data.flags & EPDC_FLAG_ENABLE_INVERSION) |
| fb_data->pxp_conf.proc_data.lut_transform |= PXP_LUT_INVERT; |
| if (upd_desc_list->upd_data.flags & EPDC_FLAG_FORCE_MONOCHROME) |
| fb_data->pxp_conf.proc_data.lut_transform |= |
| PXP_LUT_BLACK_WHITE; |
| if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_CMAP) |
| fb_data->pxp_conf.proc_data.lut_transform |= |
| PXP_LUT_USE_CMAP; |
| |
| /* |
| * Toggle inversion processing if 8-bit |
| * inverted is the current pixel format. |
| */ |
| if (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT_INVERTED) |
| fb_data->pxp_conf.proc_data.lut_transform ^= PXP_LUT_INVERT; |
| |
| #ifdef USE_PS_AS_OUTPUT |
| /* This is a blocking call, so upon return PxP tx should be done */ |
| ret = pxp_legacy_process(fb_data, src_width, src_height, |
| &pxp_upd_region); |
| if (ret) { |
| dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); |
| mutex_unlock(&fb_data->pxp_mutex); |
| return ret; |
| } |
| |
| /* If needed, enable EPDC HW while ePxP is processing */ |
| if ((fb_data->power_state == POWER_STATE_OFF) |
| || fb_data->powering_down) { |
| epdc_powerup(fb_data); |
| } |
| |
| /* This is a blocking call, so upon return PxP tx should be done */ |
| ret = pxp_complete_update(fb_data, &hist_stat); |
| if (ret) { |
| dev_err(fb_data->dev, "Unable to complete PxP update task: pre_prcoess.\n"); |
| mutex_unlock(&fb_data->pxp_mutex); |
| return ret; |
| } |
| #endif |
| pr_debug(" upd_data.dither_mode %d \n", upd_desc_list->upd_data.dither_mode); |
| fb_data->pxp_conf.proc_data.dither_mode = 0; |
| |
| /* Dithering */ |
| if ((EPDC_FLAG_USE_DITHERING_PASSTHROUGH < upd_desc_list->upd_data.dither_mode) && |
| (upd_desc_list->upd_data.dither_mode < EPDC_FLAG_USE_DITHERING_MAX)) { |
| |
| fb_data->pxp_conf.proc_data.dither_mode = upd_desc_list->upd_data.dither_mode; |
| fb_data->pxp_conf.proc_data.quant_bit = upd_desc_list->upd_data.quant_bit; |
| |
| /* This is a blocking call, so upon return PxP tx should be done */ |
| ret = pxp_process_dithering(fb_data, &pxp_upd_region); |
| if (ret) { |
| dev_err(fb_data->dev, "Unable to submit PxP update task.\n"); |
| mutex_unlock(&fb_data->pxp_mutex); |
| return ret; |
| } |
| |
| /* If needed, enable EPDC HW while ePxP is processing */ |
| if ((fb_data->power_state == POWER_STATE_OFF) |
| || fb_data->powering_down) { |
| epdc_powerup(fb_data); |
| } |
| |
| /* This is a blocking call, so upon return PxP tx should be done */ |
| ret = pxp_complete_update(fb_data, &hist_stat); |
| if (ret) { |
| dev_err(fb_data->dev, "Unable to complete PxP update task: dithering process\n"); |
| mutex_unlock(&fb_data->pxp_mutex); |
| return ret; |
| } |
| |
| } |
| |
| /* Regal D Processing */ |
| fb_data->pxp_conf.proc_data.reagl_d_en = |
| (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_GLD16); |
| |
| mutex_unlock(&fb_data->pxp_mutex); |
| |
| /* Update waveform mode from PxP histogram results */ |
| if ((fb_data->rev <= 20) && |
| (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) { |
| if (hist_stat & 0x1) |
| upd_desc_list->upd_data.waveform_mode = |
| fb_data->wv_modes.mode_du; |
| else if (hist_stat & 0x2) |
| upd_desc_list->upd_data.waveform_mode = |
| fb_data->wv_modes.mode_gc4; |
| else if (hist_stat & 0x4) |
| upd_desc_list->upd_data.waveform_mode = |
| fb_data->wv_modes.mode_gc8; |
| else if (hist_stat & 0x8) |
| upd_desc_list->upd_data.waveform_mode = |
| fb_data->wv_modes.mode_gc16; |
| else |
| upd_desc_list->upd_data.waveform_mode = |
| fb_data->wv_modes.mode_gc32; |
| |
| dev_dbg(fb_data->dev, "hist_stat = 0x%x, new waveform = 0x%x\n", |
| hist_stat, upd_desc_list->upd_data.waveform_mode); |
| } |
| |
| return 0; |
| } |
| |
| static int epdc_submit_merge(struct update_desc_list *upd_desc_list, |
| struct update_desc_list *update_to_merge, |
| struct mxc_epdc_fb_data *fb_data) |
| { |
| struct mxcfb_update_data *a, *b; |
| struct mxcfb_rect *arect, *brect; |
| struct mxcfb_rect combine; |
| bool use_flags = false; |
| |
| a = &upd_desc_list->upd_data; |
| b = &update_to_merge->upd_data; |
| arect = &upd_desc_list->upd_data.update_region; |
| brect = &update_to_merge->upd_data.update_region; |
| |
| /* Do not merge a dry-run collision test update */ |
| if ((a->flags & EPDC_FLAG_TEST_COLLISION) || |
| (b->flags & EPDC_FLAG_TEST_COLLISION)) |
| return MERGE_BLOCK; |
| |
| /* |
| * Updates with different flags must be executed sequentially. |
| * Halt the merge process to ensure this. |
| */ |
| if (a->flags != b->flags) { |
| /* |
| * Special exception: if update regions are identical, |
| * we may be able to merge them. |
| */ |
| if ((arect->left != brect->left) || |
| (arect->top != brect->top) || |
| (arect->width != brect->width) || |
| (arect->height != brect->height)) |
| return MERGE_BLOCK; |
| |
| use_flags = true; |
| } |
| |
| if (a->update_mode != b->update_mode) |
| a->update_mode = UPDATE_MODE_FULL; |
| |
| if (a->waveform_mode != b->waveform_mode) |
| a->waveform_mode = WAVEFORM_MODE_AUTO; |
| |
| if (arect->left > (brect->left + brect->width) || |
| brect->left > (arect->left + arect->width) || |
| arect->top > (brect->top + brect->height) || |
| brect->top > (arect->top + arect->height)) |
| return MERGE_FAIL; |
| |
| combine.left = arect->left < brect->left ? arect->left : brect->left; |
| combine.top = arect->top < brect->top ? arect->top : brect->top; |
| combine.width = (arect->left + arect->width) > |
| (brect->left + brect->width) ? |
| (arect->left + arect->width - combine.left) : |
| (brect->left + brect->width - combine.left); |
| combine.height = (arect->top + arect->height) > |
| (brect->top + brect->height) ? |
| (arect->top + arect->height - combine.top) : |
| (brect->top + brect->height - combine.top); |
| |
| /* Don't merge if combined width exceeds max width */ |
| if (fb_data->restrict_width) { |
| u32 max_width = EPDC_V2_MAX_UPDATE_WIDTH; |
| u32 combined_width = combine.width; |
| if (fb_data->epdc_fb_var.rotate != FB_ROTATE_UR) |
| max_width -= EPDC_V2_ROTATION_ALIGNMENT; |
| if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_CW) || |
| (fb_data->epdc_fb_var.rotate == FB_ROTATE_CCW)) |
| combined_width = combine.height; |
| if (combined_width > max_width) |
| return MERGE_FAIL; |
| } |
| |
| *arect = combine; |
| |
| /* Use flags of the later update */ |
| if (use_flags) |
| a->flags = b->flags; |
| |
| /* Merge markers */ |
| list_splice_tail(&update_to_merge->upd_marker_list, |
| &upd_desc_list->upd_marker_list); |
| |
| /* Merged update should take on the earliest order */ |
| upd_desc_list->update_order = |
| (upd_desc_list->update_order > update_to_merge->update_order) ? |
| upd_desc_list->update_order : update_to_merge->update_order; |
| |
| return MERGE_OK; |
| } |
| |
| static void epdc_submit_work_func(struct work_struct *work) |
| { |
| int temp_index; |
| struct update_data_list *next_update, *temp_update; |
| struct update_desc_list *next_desc, *temp_desc; |
| struct update_marker_data *next_marker, *temp_marker; |
| struct mxc_epdc_fb_data *fb_data = |
| container_of(work, struct mxc_epdc_fb_data, epdc_submit_work); |
| struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; |
| struct pxp_proc_data *proc_data = &pxp_conf->proc_data; |
| struct update_data_list *upd_data_list = NULL; |
| struct mxcfb_rect adj_update_region, *upd_region; |
| bool end_merge = false; |
| bool is_transform; |
| u32 update_addr; |
| int *err_dist; |
| int ret; |
| |
| /* Protect access to buffer queues and to update HW */ |
| mutex_lock(&fb_data->queue_mutex); |
| |
| /* |
| * 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_safe(next_update, temp_update, |
| &fb_data->upd_buf_collision_list, list) { |
| |
| if (next_update->collision_mask != 0) |
| continue; |
| |
| dev_dbg(fb_data->dev, "A collision update is ready to go!\n"); |
| |
| /* Force waveform mode to auto for resubmitted collisions */ |
| next_update->update_desc->upd_data.waveform_mode = |
| WAVEFORM_MODE_AUTO; |
| |
| /* |
| * We have a collision cleared, so select it for resubmission. |
| * If an update is already selected, attempt to merge. |
| */ |
| if (!upd_data_list) { |
| upd_data_list = next_update; |
| list_del_init(&next_update->list); |
| if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) |
| /* If not merging, we have our update */ |
| break; |
| } else { |
| switch (epdc_submit_merge(upd_data_list->update_desc, |
| next_update->update_desc, |
| fb_data)) { |
| case MERGE_OK: |
| dev_dbg(fb_data->dev, |
| "Update merged [collision]\n"); |
| list_del_init(&next_update->update_desc->list); |
| kfree(next_update->update_desc); |
| next_update->update_desc = NULL; |
| list_del_init(&next_update->list); |
| /* Add to free buffer list */ |
| list_add_tail(&next_update->list, |
| &fb_data->upd_buf_free_list); |
| break; |
| case MERGE_FAIL: |
| dev_dbg(fb_data->dev, |
| "Update not merged [collision]\n"); |
| break; |
| case MERGE_BLOCK: |
| dev_dbg(fb_data->dev, |
| "Merge blocked [collision]\n"); |
| end_merge = true; |
| break; |
| } |
| |
| if (end_merge) { |
| end_merge = false; |
| break; |
| } |
| } |
| } |
| |
| /* |
| * Skip pending update list only if we found a collision |
| * update and we are not merging |
| */ |
| if (!((fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) && |
| upd_data_list)) { |
| /* |
| * If we didn't find a collision update ready to go, we |
| * need to get a free buffer and match it to a pending update. |
| */ |
| |
| /* |
| * Can't proceed if there are no free buffers (and we don't |
| * already have a collision update selected) |
| */ |
| if (!upd_data_list && |
| list_empty(&fb_data->upd_buf_free_list)) { |
| mutex_unlock(&fb_data->queue_mutex); |
| return; |
| } |
| |
| list_for_each_entry_safe(next_desc, temp_desc, |
| &fb_data->upd_pending_list, list) { |
| |
| dev_dbg(fb_data->dev, "Found a pending update!\n"); |
| |
| if (!upd_data_list) { |
| if (list_empty(&fb_data->upd_buf_free_list)) |
| break; |
| upd_data_list = |
| list_entry(fb_data->upd_buf_free_list.next, |
| struct update_data_list, list); |
| list_del_init(&upd_data_list->list); |
| upd_data_list->update_desc = next_desc; |
| list_del_init(&next_desc->list); |
| if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) |
| /* If not merging, we have an update */ |
| break; |
| } else { |
| switch (epdc_submit_merge(upd_data_list->update_desc, |
| next_desc, fb_data)) { |
| case MERGE_OK: |
| dev_dbg(fb_data->dev, |
| "Update merged [queue]\n"); |
| list_del_init(&next_desc->list); |
| kfree(next_desc); |
| break; |
| case MERGE_FAIL: |
| dev_dbg(fb_data->dev, |
| "Update not merged [queue]\n"); |
| break; |
| case MERGE_BLOCK: |
| dev_dbg(fb_data->dev, |
| "Merge blocked [collision]\n"); |
| end_merge = true; |
| break; |
| } |
| |
| if (end_merge) |
| break; |
| } |
| } |
| } |
| |
| /* Is update list empty? */ |
| if (!upd_data_list) { |
| mutex_unlock(&fb_data->queue_mutex); |
| return; |
| } |
| |
| /* |
| * If no processing required, skip update processing |
| * No processing means: |
| * - FB unrotated |
| * - FB pixel format = 8-bit grayscale |
| * - No look-up transformations (inversion, posterization, etc.) |
| * - No scaling/flip |
| */ |
| is_transform = ((upd_data_list->update_desc->upd_data.flags & |
| (EPDC_FLAG_ENABLE_INVERSION | EPDC_FLAG_USE_DITHERING_Y1 | |
| EPDC_FLAG_USE_DITHERING_Y4 | EPDC_FLAG_FORCE_MONOCHROME | |
| EPDC_FLAG_USE_CMAP)) && (proc_data->scaling == 0) && |
| (proc_data->hflip == 0) && (proc_data->vflip == 0)) ? |
| true : false; |
| |
| /*XXX if we use external mode, we should first use pxp |
| * to update upd buffer data to working buffer first. |
| */ |
| if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) && |
| (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT) && |
| !is_transform && (proc_data->dither_mode == 0) && |
| !fb_data->restrict_width) { |
| |
| /* If needed, enable EPDC HW while ePxP is processing */ |
| if ((fb_data->power_state == POWER_STATE_OFF) |
| || fb_data->powering_down) |
| epdc_powerup(fb_data); |
| |
| /* |
| * Set update buffer pointer to the start of |
| * the update region in the frame buffer. |
| */ |
| upd_region = &upd_data_list->update_desc->upd_data.update_region; |
| update_addr = fb_data->info.fix.smem_start + |
| ((upd_region->top * fb_data->info.var.xres_virtual) + |
| upd_region->left) * fb_data->info.var.bits_per_pixel/8; |
| upd_data_list->update_desc->epdc_stride = |
| fb_data->info.var.xres_virtual * |
| fb_data->info.var.bits_per_pixel/8; |
| } else { |
| /* Select from PxP output buffers */ |
| upd_data_list->phys_addr = |
| fb_data->phys_addr_updbuf[fb_data->upd_buffer_num]; |
| upd_data_list->virt_addr = |
| fb_data->virt_addr_updbuf[fb_data->upd_buffer_num]; |
| fb_data->upd_buffer_num++; |
| if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1) |
| fb_data->upd_buffer_num = 0; |
| |
| /* Release buffer queues */ |
| mutex_unlock(&fb_data->queue_mutex); |
| |
| /* Perform PXP processing - EPDC power will also be enabled */ |
| if (epdc_process_update(upd_data_list, fb_data)) { |
| dev_dbg(fb_data->dev, "PXP processing error.\n"); |
| /* Protect access to buffer queues and to update HW */ |
| mutex_lock(&fb_data->queue_mutex); |
| list_del_init(&upd_data_list->update_desc->list); |
| kfree(upd_data_list->update_desc); |
| upd_data_list->update_desc = NULL; |
| /* Add to free buffer list */ |
| list_add_tail(&upd_data_list->list, |
| &fb_data->upd_buf_free_list); |
| /* Release buffer queues */ |
| mutex_unlock(&fb_data->queue_mutex); |
| return; |
| } |
| |
| /* Protect access to buffer queues and to update HW */ |
| mutex_lock(&fb_data->queue_mutex); |
| |
| update_addr = upd_data_list->phys_addr |
| + upd_data_list->update_desc->epdc_offs; |
| } |
| |
| /* Get rotation-adjusted coordinates */ |
| adjust_coordinates(fb_data->epdc_fb_var.xres, |
| fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate, |
| &upd_data_list->update_desc->upd_data.update_region, |
| &adj_update_region); |
| |
| /* |
| * Is the working buffer idle? |
| * If the working buffer is busy, we must wait for the resource |
| * to become free. The IST will signal this event. |
| */ |
| if (fb_data->cur_update != NULL) { |
| dev_dbg(fb_data->dev, "working buf busy!\n"); |
| |
| /* Initialize event signalling an update resource is free */ |
| init_completion(&fb_data->update_res_free); |
| |
| fb_data->waiting_for_wb = true; |
| |
| /* Leave spinlock while waiting for WB to complete */ |
| mutex_unlock(&fb_data->queue_mutex); |
| wait_for_completion(&fb_data->update_res_free); |
| mutex_lock(&fb_data->queue_mutex); |
| } |
| |
| /* |
| * Dithering Processing (Version 1.0 - for i.MX508 and i.MX6SL) |
| */ |
| if (upd_data_list->update_desc->upd_data.flags & |
| EPDC_FLAG_USE_DITHERING_Y1) { |
| |
| err_dist = kzalloc((fb_data->info.var.xres_virtual + 3) * 3 |
| * sizeof(int), GFP_KERNEL); |
| |
| /* Dithering Y8 -> Y1 */ |
| do_dithering_processing_Y1_v1_0( |
| (uint8_t *)(upd_data_list->virt_addr + |
| upd_data_list->update_desc->epdc_offs), |
| upd_data_list->phys_addr + |
| upd_data_list->update_desc->epdc_offs, |
| &adj_update_region, |
| (fb_data->rev < 20) ? |
| ALIGN(adj_update_region.width, 8) : |
| adj_update_region.width, |
| err_dist); |
| |
| kfree(err_dist); |
| } else if (upd_data_list->update_desc->upd_data.flags & |
| EPDC_FLAG_USE_DITHERING_Y4) { |
| |
| err_dist = kzalloc((fb_data->info.var.xres_virtual + 3) * 3 |
| * sizeof(int), GFP_KERNEL); |
| |
| /* Dithering Y8 -> Y1 */ |
| do_dithering_processing_Y4_v1_0( |
| (uint8_t *)(upd_data_list->virt_addr + |
| upd_data_list->update_desc->epdc_offs), |
| upd_data_list->phys_addr + |
| upd_data_list->update_desc->epdc_offs, |
| &adj_update_region, |
| (fb_data->rev < 20) ? |
| ALIGN(adj_update_region.width, 8) : |
| adj_update_region.width, |
| err_dist); |
| |
| kfree(err_dist); |
| } |
| |
| /* |
| * If there are no LUTs available, |
| * then we must wait for the resource to become free. |
| * The IST will signal this event. |
| */ |
| { |
| bool luts_available; |
| |
| luts_available = fb_data->epdc_wb_mode ? epdc_any_luts_real_available() : |
| epdc_any_luts_available(); |
| if (!luts_available) { |
| dev_dbg(fb_data->dev, "no luts available!\n"); |
| |
| /* Initialize event signalling an update resource is free */ |
| init_completion(&fb_data->update_res_free); |
| |
| fb_data->waiting_for_lut = true; |
| |
| /* Leave spinlock while waiting for LUT to free up */ |
| mutex_unlock(&fb_data->queue_mutex); |
| wait_for_completion(&fb_data->update_res_free); |
| mutex_lock(&fb_data->queue_mutex); |
| } |
| } |
| |
| ret = epdc_choose_next_lut(fb_data, &upd_data_list->lut_num); |
| /* |
| * If LUT15 is in use (for pre-EPDC v2.0 hardware): |
| * - Wait for LUT15 to complete is if TCE underrun prevent is enabled |
| * - If we go ahead with update, sync update submission with EOF |
| */ |
| if (ret && fb_data->tce_prevent && (fb_data->rev < 20)) { |
| dev_dbg(fb_data->dev, "Waiting for LUT15\n"); |
| |
| /* Initialize event signalling that lut15 is free */ |
| init_completion(&fb_data->lut15_free); |
| |
| fb_data->waiting_for_lut15 = true; |
| |
| /* Leave spinlock while waiting for LUT to free up */ |
| mutex_unlock(&fb_data->queue_mutex); |
| wait_for_completion(&fb_data->lut15_free); |
| mutex_lock(&fb_data->queue_mutex); |
| |
| epdc_choose_next_lut(fb_data, &upd_data_list->lut_num); |
| } else if (ret && (fb_data->rev < 20)) { |
| /* Synchronize update submission time to reduce |
| chances of TCE underrun */ |
| init_completion(&fb_data->eof_event); |
| |
| epdc_eof_intr(true); |
| |
| /* Leave spinlock while waiting for EOF event */ |
| mutex_unlock(&fb_data->queue_mutex); |
| ret = wait_for_completion_timeout(&fb_data->eof_event, |
| msecs_to_jiffies(1000)); |
| if (!ret) { |
| dev_err(fb_data->dev, "Missed EOF event!\n"); |
| epdc_eof_intr(false); |
| } |
| udelay(fb_data->eof_sync_period); |
| mutex_lock(&fb_data->queue_mutex); |
| |
| } |
| |
| /* LUTs are available, so we get one here */ |
| fb_data->cur_update = upd_data_list; |
| |
| /* Reset mask for LUTS that have completed during WB processing */ |
| fb_data->luts_complete_wb = 0; |
| |
| /* If we are just testing for collision, we don't assign a LUT, |
| * so we don't need to update LUT-related resources. */ |
| if (!(upd_data_list->update_desc->upd_data.flags |
| & EPDC_FLAG_TEST_COLLISION)) { |
| /* Associate LUT with update marker */ |
| list_for_each_entry_safe(next_marker, temp_marker, |
| &upd_data_list->update_desc->upd_marker_list, upd_list) |
| next_marker->lut_num = fb_data->cur_update->lut_num; |
| |
| /* Mark LUT with order */ |
| fb_data->lut_update_order[upd_data_list->lut_num] = |
| upd_data_list->update_desc->update_order; |
| |
| epdc_lut_complete_intr(fb_data->rev, upd_data_list->lut_num, |
| true); |
| } |
| |
| /* Enable Collision and WB complete IRQs */ |
| epdc_working_buf_intr(true); |
| |
| /* add working buffer update here for external mode */ |
| if (fb_data->epdc_wb_mode) |
| ret = epdc_working_buffer_update(fb_data, upd_data_list, |
| &adj_update_region); |
| |
| /* Program EPDC update to process buffer */ |
| if (upd_data_list->update_desc->upd_data.temp != TEMP_USE_AMBIENT) { |
| temp_index = mxc_epdc_fb_get_temp_index(fb_data, |
| upd_data_list->update_desc->upd_data.temp); |
| epdc_set_temp(temp_index); |
| } else |
| epdc_set_temp(fb_data->temp_index); |
| |
| epdc_set_update_addr(update_addr); |
| epdc_set_update_coord(adj_update_region.left, adj_update_region.top); |
| epdc_set_update_dimensions(adj_update_region.width, |
| adj_update_region.height); |
| if (fb_data->rev > 20) |
| epdc_set_update_stride(upd_data_list->update_desc->epdc_stride); |
| if (fb_data->wv_modes_update && |
| (upd_data_list->update_desc->upd_data.waveform_mode |
| == WAVEFORM_MODE_AUTO)) { |
| epdc_set_update_waveform(&fb_data->wv_modes); |
| fb_data->wv_modes_update = false; |
| } |
| |
| epdc_submit_update(upd_data_list->lut_num, |
| upd_data_list->update_desc->upd_data.waveform_mode, |
| upd_data_list->update_desc->upd_data.update_mode, |
| (upd_data_list->update_desc->upd_data.flags |
| & EPDC_FLAG_TEST_COLLISION) ? true : false, |
| false, 0); |
| |
| /* Release buffer queues */ |
| mutex_unlock(&fb_data->queue_mutex); |
| } |
| |
| static int mxc_epdc_fb_send_single_update(struct mxcfb_update_data *upd_data, |
| struct fb_info *info) |
| { |
| struct mxc_epdc_fb_data *fb_data = info ? |
| (struct mxc_epdc_fb_data *)info:g_fb_data; |
| struct update_data_list *upd_data_list = NULL; |
| struct mxcfb_rect *screen_upd_region; /* Region on screen to update */ |
| int temp_index; |
| int ret; |
| struct update_desc_list *upd_desc; |
| struct update_marker_data *marker_data, *next_marker, *temp_marker; |
| |
| /* Has EPDC HW been initialized? */ |
| if (!fb_data->hw_ready) { |
| /* Throw message if we are not mid-initialization */ |
| if (!fb_data->hw_initializing) |
| dev_err(fb_data->dev, "Display HW not properly" |
| "initialized. Aborting update.\n"); |
| return -EPERM; |
| } |
| |
| /* 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 >= fb_data->epdc_fb_var.xres) || |
| (upd_data->update_region.top >= fb_data->epdc_fb_var.yres) || |
| (upd_data->update_region.width > fb_data->epdc_fb_var.xres) || |
| (upd_data->update_region.height > fb_data->epdc_fb_var.yres) || |
| (upd_data->update_region.left + upd_data->update_region.width > fb_data->epdc_fb_var.xres) || |
| (upd_data->update_region.top + upd_data->update_region.height > fb_data->epdc_fb_var.yres)) { |
| dev_err(fb_data->dev, |
| "Update region is outside bounds of framebuffer." |
| "Aborting update.\n"); |
| return -EINVAL; |
| } |
| if (upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) { |
| if ((upd_data->update_region.width != |
| upd_data->alt_buffer_data.alt_update_region.width) || |
| (upd_data->update_region.height != |
| upd_data->alt_buffer_data.alt_update_region.height)) { |
| dev_err(fb_data->dev, |
| "Alternate update region dimensions must " |
| "match screen update region dimensions.\n"); |
| return -EINVAL; |
| } |
| /* Validate physical address parameter */ |
| if ((upd_data->alt_buffer_data.phys_addr < |
| fb_data->info.fix.smem_start) || |
| (upd_data->alt_buffer_data.phys_addr > |
| fb_data->info.fix.smem_start + fb_data->map_size)) { |
| dev_err(fb_data->dev, |
| "Invalid physical address for alternate " |
| "buffer. Aborting update...\n"); |
| return -EINVAL; |
| } |
| } |
| |
| mutex_lock(&fb_data->queue_mutex); |
| |
| /* |
| * If we are waiting to go into suspend, or the FB is blanked, |
| * we do not accept new updates |
| */ |
| if ((fb_data->waiting_for_idle) || |
| (fb_data->blank != FB_BLANK_UNBLANK)) { |
| dev_dbg(fb_data->dev, "EPDC not active." |
| "Update request abort.\n"); |
| mutex_unlock(&fb_data->queue_mutex); |
| return -EPERM; |
| } |
| |
| if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) { |
| int count = 0; |
| struct update_data_list *plist; |
| |
| /* |
| * If next update is a FULL mode update, then we must |
| * ensure that all pending & active updates are complete |
| * before submitting the update. Otherwise, the FULL |
| * mode update may cause an endless collision loop with |
| * other updates. Block here until updates are flushed. |
| */ |
| if (upd_data->update_mode == UPDATE_MODE_FULL) { |
| mutex_unlock(&fb_data->queue_mutex); |
| mxc_epdc_fb_flush_updates(fb_data); |
| mutex_lock(&fb_data->queue_mutex); |
| } |
| |
| /* Count buffers in free buffer list */ |
| list_for_each_entry(plist, &fb_data->upd_buf_free_list, list) |
| count++; |
| |
| /* Use count to determine if we have enough |
| * free buffers to handle this update request */ |
| if (count + fb_data->max_num_buffers |
| <= fb_data->max_num_updates) { |
| dev_err(fb_data->dev, |
| "No free intermediate buffers available.\n"); |
| mutex_unlock(&fb_data->queue_mutex); |
| return -ENOMEM; |
| } |
| |
| /* Grab first available buffer and delete from the free list */ |
| upd_data_list = |
| list_entry(fb_data->upd_buf_free_list.next, |
| struct update_data_list, list); |
| |
| list_del_init(&upd_data_list->list); |
| } |
| |
| /* |
| * Create new update data structure, fill it with new update |
| * data and add it to the list of pending updates |
| */ |
| upd_desc = kzalloc(sizeof(struct update_desc_list), GFP_KERNEL); |
| if (!upd_desc) { |
| dev_err(fb_data->dev, |
| "Insufficient system memory for update! Aborting.\n"); |
| if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) { |
| list_add(&upd_data_list->list, |
| &fb_data->upd_buf_free_list); |
| } |
| mutex_unlock(&fb_data->queue_mutex); |
| return -EPERM; |
| } |
| /* Initialize per-update marker list */ |
| INIT_LIST_HEAD(&upd_desc->upd_marker_list); |
| upd_desc->upd_data = *upd_data; |
| upd_desc->update_order = fb_data->order_cnt++; |
| list_add_tail(&upd_desc->list, &fb_data->upd_pending_list); |
| |
| /* If marker specified, associate it with a completion */ |
| if (upd_data->update_marker != 0) { |
| /* Allocate new update marker and set it up */ |
| marker_data = kzalloc(sizeof(struct update_marker_data), |
| GFP_KERNEL); |
| if (!marker_data) { |
| dev_err(fb_data->dev, "No memory for marker!\n"); |
| mutex_unlock(&fb_data->queue_mutex); |
| return -ENOMEM; |
| } |
| list_add_tail(&marker_data->upd_list, |
| &upd_desc->upd_marker_list); |
| marker_data->update_marker = upd_data->update_marker; |
| if (upd_desc->upd_data.flags & EPDC_FLAG_TEST_COLLISION) |
| marker_data->lut_num = DRY_RUN_NO_LUT; |
| else |
| marker_data->lut_num = INVALID_LUT; |
| init_completion(&marker_data->update_completion); |
| /* Add marker to master marker list */ |
| list_add_tail(&marker_data->full_list, |
| &fb_data->full_marker_list); |
| } |
| |
| if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) { |
| /* Queued update scheme processing */ |
| |
| mutex_unlock(&fb_data->queue_mutex); |
| |
| /* Signal workqueue to handle new update */ |
| queue_work(fb_data->epdc_submit_workqueue, |
| &fb_data->epdc_submit_work); |
| |
| return 0; |
| } |
| |
| /* Snapshot update scheme processing */ |
| |
| /* Set descriptor for current update, delete from pending list */ |
| upd_data_list->update_desc = upd_desc; |
| list_del_init(&upd_desc->list); |
| |
| mutex_unlock(&fb_data->queue_mutex); |
| |
| /* |
| * Hold on to original screen update region, which we |
| * will ultimately use when telling EPDC where to update on panel |
| */ |
| screen_upd_region = &upd_desc->upd_data.update_region; |
| |
| /* Select from PxP output buffers */ |
| upd_data_list->phys_addr = |
| fb_data->phys_addr_updbuf[fb_data->upd_buffer_num]; |
| upd_data_list->virt_addr = |
| fb_data->virt_addr_updbuf[fb_data->upd_buffer_num]; |
| fb_data->upd_buffer_num++; |
| if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1) |
| fb_data->upd_buffer_num = 0; |
| |
| ret = epdc_process_update(upd_data_list, fb_data); |
| if (ret) { |
| mutex_unlock(&fb_data->pxp_mutex); |
| return ret; |
| } |
| |
| /* Pass selected waveform mode back to user */ |
| upd_data->waveform_mode = upd_desc->upd_data.waveform_mode; |
| |
| /* Get rotation-adjusted coordinates */ |
| adjust_coordinates(fb_data->epdc_fb_var.xres, |
| fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate, |
| &upd_desc->upd_data.update_region, NULL); |
| |
| /* Grab lock for queue manipulation and update submission */ |
| mutex_lock(&fb_data->queue_mutex); |
| |
| /* |
| * Is the working buffer idle? |
| * If either the working buffer is busy, or there are no LUTs available, |
| * then we return and let the ISR handle the update later |
| */ |
| { |
| bool luts_available; |
| |
| luts_available = fb_data->epdc_wb_mode ? epdc_any_luts_real_available() : |
| epdc_any_luts_available(); |
| if ((fb_data->cur_update != NULL) || !luts_available) { |
| /* Add processed Y buffer to update list */ |
| list_add_tail(&upd_data_list->list, &fb_data->upd_buf_queue); |
| |
| /* Return and allow the update to be submitted by the ISR. */ |
| mutex_unlock(&fb_data->queue_mutex); |
| return 0; |
| } |
| } |
| |
| /* LUTs are available, so we get one here */ |
| ret = epdc_choose_next_lut(fb_data, &upd_data_list->lut_num); |
| if (ret && fb_data->tce_prevent && (fb_data->rev < 20)) { |
| dev_dbg(fb_data->dev, "Must wait for LUT15\n"); |
| /* Add processed Y buffer to update list */ |
| list_add_tail(&upd_data_list->list, &fb_data->upd_buf_queue); |
| |
| /* Return and allow the update to be submitted by the ISR. */ |
| mutex_unlock(&fb_data->queue_mutex); |
| return 0; |
| } |
| |
| if (!(upd_data_list->update_desc->upd_data.flags |
| & EPDC_FLAG_TEST_COLLISION)) { |
| |
| /* Save current update */ |
| fb_data->cur_update = upd_data_list; |
| |
| /* Reset mask for LUTS that have completed during WB processing */ |
| fb_data->luts_complete_wb = 0; |
| |
| /* Associate LUT with update marker */ |
| list_for_each_entry_safe(next_marker, temp_marker, |
| &upd_data_list->update_desc->upd_marker_list, upd_list) |
| next_marker->lut_num = upd_data_list->lut_num; |
| |
| /* Mark LUT as containing new update */ |
| fb_data->lut_update_order[upd_data_list->lut_num] = |
| upd_desc->update_order; |
| |
| epdc_lut_complete_intr(fb_data->rev, upd_data_list->lut_num, |
| true); |
| } |
| |
| /* Clear status and Enable LUT complete and WB complete IRQs */ |
| epdc_working_buf_intr(true); |
| |
| /* add working buffer update before display for external mode */ |
| if (fb_data->epdc_wb_mode) |
| ret = epdc_working_buffer_update(fb_data, upd_data_list, |
| screen_upd_region); |
| |
| /* Program EPDC update to process buffer */ |
| epdc_set_update_addr(upd_data_list->phys_addr + upd_desc->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 (fb_data->rev > 20) |
| epdc_set_update_stride(upd_desc->epdc_stride); |
| if (upd_desc->upd_data.temp != TEMP_USE_AMBIENT) { |
| temp_index = mxc_epdc_fb_get_temp_index(fb_data, |
| upd_desc->upd_data.temp); |
| epdc_set_temp(temp_index); |
| } else |
| epdc_set_temp(fb_data->temp_index); |
| if (fb_data->wv_modes_update && |
| (upd_desc->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) { |
| epdc_set_update_waveform(&fb_data->wv_modes); |
| fb_data->wv_modes_update = false; |
| } |
| |
| epdc_submit_update(upd_data_list->lut_num, |
| upd_desc->upd_data.waveform_mode, |
| upd_desc->upd_data.update_mode, |
| (upd_desc->upd_data.flags |
| & EPDC_FLAG_TEST_COLLISION) ? true : false, |
| false, 0); |
| |
| mutex_unlock(&fb_data->queue_mutex); |
| 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 = info ? |
| (struct mxc_epdc_fb_data *)info:g_fb_data; |
| |
| if (!fb_data->restrict_width) { |
| /* No width restriction, send entire update region */ |
| return mxc_epdc_fb_send_single_update(upd_data, info); |
| } else { |
| int ret; |
| __u32 width, left; |
| __u32 marker; |
| __u32 *region_width, *region_left; |
| u32 max_upd_width = EPDC_V2_MAX_UPDATE_WIDTH; |
| |
| /* Further restrict max width due to pxp rotation |
| * alignment requirement |
| */ |
| if (fb_data->epdc_fb_var.rotate != FB_ROTATE_UR) |
| max_upd_width -= EPDC_V2_ROTATION_ALIGNMENT; |
| |
| /* Select split of width or height based on rotation */ |
| if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) || |
| (fb_data->epdc_fb_var.rotate == FB_ROTATE_UD)) { |
| region_width = &upd_data->update_region.width; |
| region_left = &upd_data->update_region.left; |
| } else { |
| region_width = &upd_data->update_region.height; |
| region_left = &upd_data->update_region.top; |
| } |
| |
| if (*region_width <= max_upd_width) |
| return mxc_epdc_fb_send_single_update(upd_data, info); |
| |
| width = *region_width; |
| left = *region_left; |
| marker = upd_data->update_marker; |
| upd_data->update_marker = 0; |
| |
| do { |
| *region_width = max_upd_width; |
| *region_left = left; |
| ret = mxc_epdc_fb_send_single_update(upd_data, info); |
| if (ret) |
| return ret; |
| width -= max_upd_width; |
| left += max_upd_width; |
| } while (width > max_upd_width); |
| |
| *region_width = width; |
| *region_left = left; |
| upd_data->update_marker = marker; |
| return mxc_epdc_fb_send_single_update(upd_data, info); |
| } |
| } |
| |
| static int mxc_epdc_fb_wait_update_complete(struct mxcfb_update_marker_data *marker_data, |
| struct fb_info *info) |
| { |
| struct mxc_epdc_fb_data *fb_data = info ? |
| (struct mxc_epdc_fb_data *)info:g_fb_data; |
| struct update_marker_data *next_marker; |
| struct update_marker_data *temp; |
| bool marker_found = false; |
| int ret = 0; |
| |
| /* 0 is an invalid update_marker value */ |
| if (marker_data->update_marker == 0) |
| return -EINVAL; |
| |
| /* |
| * Find completion associated with update_marker requested. |
| * Note: If update completed already, marker will have been |
| * cleared, it won't be found, and function will just return. |
| */ |
| |
| /* Grab queue lock to protect access to marker list */ |
| mutex_lock(&fb_data->queue_mutex); |
| |
| list_for_each_entry_safe(next_marker, temp, |
| &fb_data->full_marker_list, full_list) { |
| if (next_marker->update_marker == marker_data->update_marker) { |
| dev_dbg(fb_data->dev, "Waiting for marker %d\n", |
| marker_data->update_marker); |
| next_marker->waiting = true; |
| marker_found = true; |
| break; |
| } |
| } |
| |
| mutex_unlock(&fb_data->queue_mutex); |
| |
| /* |
| * If marker not found, it has either been signalled already |
| * or the update request failed. In either case, just return. |
| */ |
| if (!marker_found) |
| return ret; |
| |
| ret = wait_for_completion_timeout(&next_marker->update_completion, |
| msecs_to_jiffies(5000)); |
| if (!ret) { |
| dev_err(fb_data->dev, |
| "Timed out waiting for update completion\n"); |
| return -ETIMEDOUT; |
| } |
| |
| marker_data->collision_test = next_marker->collision_test; |
| |
| /* Free update marker object */ |
| kfree(next_marker); |
| |
| return ret; |
| } |
| |
| static int mxc_epdc_fb_set_pwrdown_delay(u32 pwrdown_delay, |
| struct fb_info *info) |
| { |
| struct mxc_epdc_fb_data *fb_data = info ? |
| (struct mxc_epdc_fb_data *)info:g_fb_data; |
| |
| fb_data->pwrdown_delay = pwrdown_delay; |
| |
| return 0; |
| } |
| |
| static int mxc_epdc_get_pwrdown_delay(struct fb_info *info) |
| { |
| struct mxc_epdc_fb_data *fb_data = info ? |
| (struct mxc_epdc_fb_data *)info:g_fb_data; |
| |
| return fb_data->pwrdown_delay; |
| } |
| |
| 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; |
| if (!copy_from_user(&modes, argp, sizeof(modes))) { |
| mxc_epdc_fb_set_waveform_modes(&modes, info); |
| ret = 0; |
| } |
| break; |
| } |
| case MXCFB_SET_TEMPERATURE: |
| { |
| int temperature; |
| if (!get_user(temperature, (int32_t __user *) arg)) |
| ret = mxc_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_SET_UPDATE_SCHEME: |
| { |
| u32 upd_scheme = 0; |
| if (!get_user(upd_scheme, (__u32 __user *) arg)) |
| ret = mxc_epdc_fb_set_upd_scheme(upd_scheme, |
| info); |
| break; |
| } |
| case MXCFB_SEND_UPDATE: |
| { |
| struct mxcfb_update_data upd_data; |
| if (!copy_from_user(&upd_data, argp, |
| sizeof(upd_data))) { |
| ret = mxc_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: |
| { |
| struct mxcfb_update_marker_data upd_marker_data; |
| if (!copy_from_user(&upd_marker_data, argp, |
| sizeof(upd_marker_data))) { |
| ret = mxc_epdc_fb_wait_update_complete( |
| &upd_marker_data, info); |
| if (copy_to_user(argp, &upd_marker_data, |
| sizeof(upd_marker_data))) |
| ret = -EFAULT; |
| } else { |
| ret = -EFAULT; |
| } |
| |
| break; |
| } |
| |
| case MXCFB_SET_PWRDOWN_DELAY: |
| { |
| int delay = 0; |
| if (!get_user(delay, (__u32 __user *) arg)) |
| ret = |
| mxc_epdc_fb_set_pwrdown_delay(delay, info); |
| break; |
| } |
| |
| case MXCFB_GET_PWRDOWN_DELAY: |
| { |
| int pwrdown_delay = mxc_epdc_get_pwrdown_delay(info); |
| if (put_user(pwrdown_delay, |
| (int __user *)argp)) |
| ret = -EFAULT; |
| ret = 0; |
| break; |
| } |
| |
| case MXCFB_GET_WORK_BUFFER: |
| { |
| /* copy the epdc working buffer to the user space */ |
| struct mxc_epdc_fb_data *fb_data = info ? |
| (struct mxc_epdc_fb_data *)info:g_fb_data; |
| flush_cache_all(); |
| outer_flush_range(fb_data->working_buffer_phys, |
| fb_data->working_buffer_phys + |
| fb_data->working_buffer_size); |
| if (copy_to_user((void __user *)arg, |
| (const void *) fb_data->working_buffer_virt, |
| fb_data->working_buffer_size)) |
| ret = -EFAULT; |
| else |
| ret = 0; |
| flush_cache_all(); |
| outer_flush_range(fb_data->working_buffer_phys, |
| fb_data->working_buffer_phys + |
| fb_data->working_buffer_size); |
| 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->epdc_fb_var.xres; |
| update.update_region.top = y1; |
| update.update_region.height = y2 - y1; |
| update.waveform_mode = WAVEFORM_MODE_AUTO; |
| update.update_mode = UPDATE_MODE_FULL; |
| update.update_marker = 0; |
| update.temp = TEMP_USE_AMBIENT; |
| update.flags = 0; |
| |
| 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 >= fb_data->epdc_fb_var.yres) |
| y2 = fb_data->epdc_fb_var.yres - 1; |
| if (miny > y1) |
| miny = y1; |
| if (maxy < y2) |
| maxy = y2; |
| } |
| |
| mxc_epdc_fb_update_pages(fb_data, miny, maxy); |
| } |
| |
| void mxc_epdc_fb_flush_updates(struct mxc_epdc_fb_data *fb_data) |
| { |
| int ret; |
| |
| if (fb_data->in_init) |
| return; |
| |
| /* Grab queue lock to prevent any new updates from being submitted */ |
| mutex_lock(&fb_data->queue_mutex); |
| |
| /* |
| * 3 places to check for updates that are active or pending: |
| * 1) Updates in the pending list |
| * 2) Update buffers in use (e.g., PxP processing) |
| * 3) Active updates to panel - We can key off of EPDC |
| * power state to know if we have active updates. |
| */ |
| if (!list_empty(&fb_data->upd_pending_list) || |
| !is_free_list_full(fb_data) || |
| (fb_data->updates_active == true)) { |
| /* Initialize event signalling updates are done */ |
| init_completion(&fb_data->updates_done); |
| fb_data->waiting_for_idle = true; |
| |
| mutex_unlock(&fb_data->queue_mutex); |
| /* Wait for any currently active updates to complete */ |
| ret = wait_for_completion_timeout(&fb_data->updates_done, |
| msecs_to_jiffies(8000)); |
| if (!ret) |
| dev_err(fb_data->dev, |
| "Flush updates timeout! ret = 0x%x\n", ret); |
| |
| mutex_lock(&fb_data->queue_mutex); |
| fb_data->waiting_for_idle = false; |
| } |
| |
| mutex_unlock(&fb_data->queue_mutex); |
| } |
| |
| static int mxc_epdc_fb_blank(int blank, struct fb_info *info) |
| { |
| struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; |
| int ret; |
| |
| dev_dbg(fb_data->dev, "blank = %d\n", blank); |
| |
| if (fb_data->blank == blank) |
| return 0; |
| |
| fb_data->blank = blank; |
| |
| switch (blank) { |
| case FB_BLANK_POWERDOWN: |
| mxc_epdc_fb_flush_updates(fb_data); |
| /* Wait for powerdown */ |
| mutex_lock(&fb_data->power_mutex); |
| if ((fb_data->power_state == POWER_STATE_ON) && |
| (fb_data->pwrdown_delay == FB_POWERDOWN_DISABLE)) { |
| |
| /* Powerdown disabled, so we disable EPDC manually */ |
| int count = 0; |
| int sleep_ms = 10; |
| |
| mutex_unlock(&fb_data->power_mutex); |
| |
| /* If any active updates, wait for them to complete */ |
| while (fb_data->updates_active) { |
| /* Timeout after 1 sec */ |
| if ((count * sleep_ms) > 1000) |
| break; |
| msleep(sleep_ms); |
| count++; |
| } |
| |
| fb_data->powering_down = true; |
| epdc_powerdown(fb_data); |
| } else if (fb_data->power_state != POWER_STATE_OFF) { |
| fb_data->wait_for_powerdown = true; |
| init_completion(&fb_data->powerdown_compl); |
| mutex_unlock(&fb_data->power_mutex); |
| ret = wait_for_completion_timeout(&fb_data->powerdown_compl, |
| msecs_to_jiffies(5000)); |
| if (!ret) { |
| dev_err(fb_data->dev, |
| "No powerdown received!\n"); |
| return -ETIMEDOUT; |
| } |
| } else |
| mutex_unlock(&fb_data->power_mutex); |
| break; |
| case FB_BLANK_VSYNC_SUSPEND: |
| case FB_BLANK_HSYNC_SUSPEND: |
| case FB_BLANK_NORMAL: |
| mxc_epdc_fb_flush_updates(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->yoffset %d, info->var.yoffset %d\n", |
| __func__, var->yoffset, info->var.yoffset); |
| /* check if var is valid; also, xpan is not supported */ |
| if (!var || (var->xoffset != info->var.xoffset) || |
| (var->yoffset + var->yres > var->yres_virtual)) { |
| dev_dbg(info->device, "x panning not supported\n"); |
| return -EINVAL; |
| } |
| |
| if ((fb_data->epdc_fb_var.xoffset == var->xoffset) && |
| (fb_data->epdc_fb_var.yoffset == var->yoffset)) |
| return 0; /* No change, do nothing */ |
| |
| y_bottom = var->yoffset; |
| |
| if (!(var->vmode & FB_VMODE_YWRAP)) |
| y_bottom += var->yres; |
| |
| if (y_bottom > info->var.yres_virtual) |
| return -EINVAL; |
| |
| mutex_lock(&fb_data->queue_mutex); |
| |
| fb_data->fb_offset = (var->yoffset * var->xres_virtual + var->xoffset) |
| * (var->bits_per_pixel) / 8; |
| |
| fb_data->epdc_fb_var.xoffset = var->xoffset; |
| fb_data->epdc_fb_var.yoffset = var->yoffset; |
| |
| if (var->vmode & FB_VMODE_YWRAP) |
| info->var.vmode |= FB_VMODE_YWRAP; |
| else |
| info->var.vmode &= ~FB_VMODE_YWRAP; |
| |
| mutex_unlock(&fb_data->queue_mutex); |
| |
| return 0; |
| } |
| |
| static struct fb_ops mxc_epdc_fb_ops = { |
| .owner = THIS_MODULE, |
| .fb_check_var = mxc_epdc_fb_check_var, |
| .fb_set_par = mxc_epdc_fb_set_par, |
| .fb_setcmap = mxc_epdc_fb_setcmap, |
| .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) |
| count++; |
| |
| /* Check to see if all buffers are in this list */ |
| if (count == fb_data->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; |
| u32 ints_fired, luts1_ints_fired, luts2_ints_fired; |
| |
| /* |
| * 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(fb_data->rev, 0)) { |
| epdc_lut_complete_intr(fb_data->rev, 0, false); |
| epdc_clear_lut_complete_irq(fb_data->rev, 0); |
| fb_data->in_init = false; |
| dev_dbg(fb_data->dev, "Cleared LUT complete for init update\n"); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| ints_fired = __raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ); |
| if (fb_data->rev < 20) { |
| luts1_ints_fired = 0; |
| luts2_ints_fired = 0; |
| } else { |
| luts1_ints_fired = __raw_readl(EPDC_IRQ_MASK1) & __raw_readl(EPDC_IRQ1); |
| luts2_ints_fired = __raw_readl(EPDC_IRQ_MASK2) & __raw_readl(EPDC_IRQ2); |
| } |
| |
| if (!(ints_fired || luts1_ints_fired || luts2_ints_fired)) |
| return IRQ_HANDLED; |
| |
| if (__raw_readl(EPDC_IRQ) & EPDC_IRQ_TCE_UNDERRUN_IRQ) { |
| dev_err(fb_data->dev, |
| "TCE underrun! Will continue to update panel\n"); |
| /* Clear TCE underrun IRQ */ |
| __raw_writel(EPDC_IRQ_TCE_UNDERRUN_IRQ, EPDC_IRQ_CLEAR); |
| } |
| |
| /* Check if we are waiting on EOF to sync a new update submission */ |
| if (epdc_signal_eof()) { |
| epdc_eof_intr(false); |
| epdc_clear_eof_irq(); |
| complete(&fb_data->eof_event); |
| } |
| |
| /* |
| * Workaround for EPDC v2.0/v2.1 errata: Must read collision status |
| * before clearing IRQ, or else collision status for bits 16:63 |
| * will be automatically cleared. So we read it here, and there is |
| * no conflict with using it in epdc_intr_work_func since the |
| * working buffer processing flow is strictly sequential (i.e., |
| * only one WB processing done at a time, so the data grabbed |
| * here should be up-to-date and accurate when the WB processing |
| * completes. Also, note that there is no impact to other versions |
| * of EPDC by reading LUT status here. |
| */ |
| if (fb_data->cur_update != NULL) |
| fb_data->epdc_colliding_luts = epdc_get_colliding_luts(fb_data->rev); |
| |
| /* Clear the interrupt mask for any interrupts signalled */ |
| __raw_writel(ints_fired, EPDC_IRQ_MASK_CLEAR); |
| __raw_writel(luts1_ints_fired, EPDC_IRQ_MASK1_CLEAR); |
| __raw_writel(luts2_ints_fired, EPDC_IRQ_MASK2_CLEAR); |
| |
| dev_dbg(fb_data->dev, "EPDC interrupts fired = 0x%x, " |
| "LUTS1 fired = 0x%x, LUTS2 fired = 0x%x\n", |
| ints_fired, luts1_ints_fired, luts2_ints_fired); |
| |
| queue_work(fb_data->epdc_intr_workqueue, |
| &fb_data->epdc_intr_work); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void epdc_intr_work_func(struct work_struct *work) |
| { |
| struct mxc_epdc_fb_data *fb_data = |
| container_of(work, struct mxc_epdc_fb_data, epdc_intr_work); |
| struct update_data_list *collision_update; |
| struct mxcfb_rect *next_upd_region; |
| struct update_marker_data *next_marker; |
| struct update_marker_data *temp; |
| int temp_index; |
| u64 temp_mask; |
| u32 lut; |
| bool ignore_collision = false; |
| int i; |
| bool wb_lut_done = false; |
| bool free_update = true; |
| int next_lut, epdc_next_lut_15; |
| u32 epdc_luts_active, epdc_wb_busy, epdc_luts_avail, epdc_lut_cancelled; |
| u32 epdc_collision; |
| u64 epdc_irq_stat; |
| bool epdc_waiting_on_wb; |
| u32 coll_coord, coll_size; |
| struct mxcfb_rect coll_region; |
| |
| /* Protect access to buffer queues and to update HW */ |
| mutex_lock(&fb_data->queue_mutex); |
| |
| /* Capture EPDC status one time to limit exposure to race conditions */ |
| epdc_luts_active = epdc_any_luts_active(fb_data->rev); |
| epdc_wb_busy = epdc_is_working_buffer_busy(); |
| |
| /*XXX unsupport update cancelled in external mode temporarily */ |
| if (fb_data->epdc_wb_mode) |
| epdc_lut_cancelled = 0; |
| else |
| epdc_lut_cancelled = epdc_is_lut_cancelled(); |
| |
| if (fb_data->epdc_wb_mode) |
| epdc_luts_avail = epdc_any_luts_real_available(); |
| else |
| epdc_luts_avail = epdc_any_luts_available(); |
| |
| if (fb_data->epdc_wb_mode) |
| epdc_collision = fb_data->col_info.pixel_cnt ? 1 : 0; |
| else |
| epdc_collision = epdc_is_collision(); |
| |
| if (fb_data->rev < 20) |
| epdc_irq_stat = __raw_readl(EPDC_IRQ); |
| else |
| epdc_irq_stat = (u64)__raw_readl(EPDC_IRQ1) | |
| ((u64)__raw_readl(EPDC_IRQ2) << 32); |
| epdc_waiting_on_wb = (fb_data->cur_update != NULL) ? true : false; |
| |
| /* Free any LUTs that have completed */ |
| for (i = 0; i < fb_data->num_luts; i++) { |
| if ((epdc_irq_stat & (1ULL << i)) == 0) |
| continue; |
| |
| dev_dbg(fb_data->dev, "LUT %d completed\n", i); |
| |
| /* Disable IRQ for completed LUT */ |
| epdc_lut_complete_intr(fb_data->rev, 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) { |
| collision_update->collision_mask = |
| collision_update->collision_mask & ~(1ULL << i); |
| } |
| |
| epdc_clear_lut_complete_irq(fb_data->rev, i); |
| |
| fb_data->luts_complete_wb |= 1ULL << i; |
| if (i != 0) |
| fb_data->luts_complete |= 1ULL << i; |
| |
| fb_data->lut_update_order[i] = 0; |
| |
| /* Signal completion if submit workqueue needs a LUT */ |
| if (fb_data->waiting_for_lut) { |
| complete(&fb_data->update_res_free); |
| fb_data->waiting_for_lut = false; |
| } |
| |
| /* Signal completion if LUT15 free and is needed */ |
| if (fb_data->waiting_for_lut15 && (i == 15)) { |
| complete(&fb_data->lut15_free); |
| fb_data->waiting_for_lut15 = false; |
| } |
| |
| /* Detect race condition where WB and its LUT complete |
| (i.e. full update completes) in one swoop */ |
| if (epdc_waiting_on_wb && |
| (i == fb_data->cur_update->lut_num)) |
| wb_lut_done = true; |
| |
| /* Signal completion if anyone waiting on this LUT */ |
| if (!wb_lut_done) |
| list_for_each_entry_safe(next_marker, temp, |
| &fb_data->full_marker_list, |
| full_list) { |
| if (next_marker->lut_num != i) |
| continue; |
| |
| /* Found marker to signal - remove from list */ |
| list_del_init(&next_marker->full_list); |
| |
| /* Signal completion of update */ |
| dev_dbg(fb_data->dev, "Signaling marker %d\n", |
| next_marker->update_marker); |
| if (next_marker->waiting) |
| complete(&next_marker->update_completion); |
| else |
| kfree(next_marker); |
| } |
| } |
| |
| /* Check to see if all updates have completed */ |
| if (list_empty(&fb_data->upd_pending_list) && |
| is_free_list_full(fb_data) && |
| !epdc_waiting_on_wb && |
| !epdc_luts_active) { |
| |
| fb_data->updates_active = false; |
| |
| if (fb_data->pwrdown_delay != FB_POWERDOWN_DISABLE) { |
| /* |
| * Set variable to prevent overlapping |
| * enable/disable requests |
| */ |
| fb_data->powering_down = true; |
| |
| /* Schedule task to disable EPDC HW until next update */ |
| schedule_delayed_work(&fb_data->epdc_done_work, |
| msecs_to_jiffies(fb_data->pwrdown_delay)); |
| |
| /* Reset counter to reduce chance of overflow */ |
| fb_data->order_cnt = 0; |
| } |
| |
| if (fb_data->waiting_for_idle) |
| complete(&fb_data->updates_done); |
| } |
| |
| /* Is Working Buffer busy? */ |
| if (epdc_wb_busy) { |
| /* Can't submit another update until WB is done */ |
| mutex_unlock(&fb_data->queue_mutex); |
| return; |
| } |
| |
| /* |
| * Were we waiting on working buffer? |
| * If so, update queues and check for collisions |
| */ |
| if (epdc_waiting_on_wb) { |
| dev_dbg(fb_data->dev, "\nWorking buffer completed\n"); |
| |
| /* Signal completion if submit workqueue was waiting on WB */ |
| if (fb_data->waiting_for_wb) { |
| complete(&fb_data->update_res_free); |
| fb_data->waiting_for_wb = false; |
| } |
| |
| if (fb_data->cur_update->update_desc->upd_data.flags |
| & EPDC_FLAG_TEST_COLLISION) { |
| /* This was a dry run to test for collision */ |
| |
| /* Signal marker */ |
| list_for_each_entry_safe(next_marker, temp, |
| &fb_data->full_marker_list, |
| full_list) { |
| if (next_marker->lut_num != DRY_RUN_NO_LUT) |
| continue; |
| |
| if (epdc_collision) |
| next_marker->collision_test = true; |
| else |
| next_marker->collision_test = false; |
| |
| dev_dbg(fb_data->dev, |
| "In IRQ, collision_test = %d\n", |
| next_marker->collision_test); |
| |
| /* Found marker to signal - remove from list */ |
| list_del_init(&next_marker->full_list); |
| |
| /* Signal completion of update */ |
| dev_dbg(fb_data->dev, "Signaling marker " |
| "for dry-run - %d\n", |
| next_marker->update_marker); |
| complete(&next_marker->update_completion); |
| } |
| memset(&fb_data->col_info, 0x0, sizeof(struct pxp_collision_info)); |
| } else if (epdc_lut_cancelled && !epdc_collision) { |
| /* |
| * Note: The update may be cancelled (void) if all |
| * pixels collided. In that case we handle it as a |
| * collision, not a cancel. |
| */ |
| |
| /* Clear LUT status (might be set if no AUTOWV used) */ |
| |
| /* |
| * Disable and clear IRQ for the LUT used. |
| * Even though LUT is cancelled in HW, the LUT |
| * complete bit may be set if AUTOWV not used. |
| */ |
| epdc_lut_complete_intr(fb_data->rev, |
| fb_data->cur_update->lut_num, false); |
| epdc_clear_lut_complete_irq(fb_data->rev, |
| fb_data->cur_update->lut_num); |
| |
| fb_data->lut_update_order[fb_data->cur_update->lut_num] = 0; |
| |
| /* Signal completion if submit workqueue needs a LUT */ |
| if (fb_data->waiting_for_lut) { |
| complete(&fb_data->update_res_free); |
| fb_data->waiting_for_lut = false; |
| } |
| |
| list_for_each_entry_safe(next_marker, temp, |
| &fb_data->cur_update->update_desc->upd_marker_list, |
| upd_list) { |
| |
| /* Del from per-update & full list */ |
| list_del_init(&next_marker->upd_list); |
| list_del_init(&next_marker->full_list); |
| |
| /* Signal completion of update */ |
| dev_dbg(fb_data->dev, |
| "Signaling marker (cancelled) %d\n", |
| next_marker->update_marker); |
| if (next_marker->waiting) |
| complete(&next_marker->update_completion); |
| else |
| kfree(next_marker); |
| } |
| } else if (epdc_collision) { |
| /* Real update (no dry-run), collision occurred */ |
| |
| /* Check list of colliding LUTs, and add to our collision mask */ |
| if (fb_data->epdc_wb_mode) |
| fb_data->epdc_colliding_luts = (u64)fb_data->col_info.victim_luts[0] | |
| (((u64)fb_data->col_info.victim_luts[1]) << 32); |
| |
| fb_data->cur_update->collision_mask = |
| fb_data->epdc_colliding_luts; |
| |
| /* Clear collisions that completed since WB began */ |
| fb_data->cur_update->collision_mask &= |
| ~fb_data->luts_complete_wb; |
| |
| dev_dbg(fb_data->dev, "Collision mask = 0x%llx\n", |
| fb_data->epdc_colliding_luts); |
| |
| /* For EPDC 2.0 and later, minimum collision bounds |
| are provided by HW. Recompute new bounds here. */ |
| if ((fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) |
| && (fb_data->rev >= 20)) { |
| u32 xres, yres, rotate; |
| struct mxcfb_rect adj_update_region; |
| struct mxcfb_rect *cur_upd_rect = |
| &fb_data->cur_update->update_desc->upd_data.update_region; |
| |
| if (fb_data->epdc_wb_mode) { |
| adjust_coordinates(fb_data->epdc_fb_var.xres, |
| fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate, |
| cur_upd_rect, &adj_update_region); |
| |
| coll_region.left = fb_data->col_info.rect_min_x + adj_update_region.left; |
| coll_region.top = fb_data->col_info.rect_min_y + adj_update_region.top; |
| coll_region.width = fb_data->col_info.rect_max_x - fb_data->col_info.rect_min_x + 1; |
| coll_region.height = fb_data->col_info.rect_max_y - fb_data->col_info.rect_min_y + 1; |
| memset(&fb_data->col_info, 0x0, sizeof(struct pxp_collision_info)); |
| } else { |
| /* Get collision region coords from EPDC */ |
| coll_coord = __raw_readl(EPDC_UPD_COL_CORD); |
| coll_size = __raw_readl(EPDC_UPD_COL_SIZE); |
| coll_region.left = |
| (coll_coord & EPDC_UPD_COL_CORD_XCORD_MASK) |
| >> EPDC_UPD_COL_CORD_XCORD_OFFSET; |
| coll_region.top = |
| (coll_coord & EPDC_UPD_COL_CORD_YCORD_MASK) |
| >> EPDC_UPD_COL_CORD_YCORD_OFFSET; |
| coll_region.width = |
| (coll_size & EPDC_UPD_COL_SIZE_WIDTH_MASK) |
| >> EPDC_UPD_COL_SIZE_WIDTH_OFFSET; |
| coll_region.height = |
| (coll_size & EPDC_UPD_COL_SIZE_HEIGHT_MASK) |
| >> EPDC_UPD_COL_SIZE_HEIGHT_OFFSET; |
| } |
| dev_dbg(fb_data->dev, "Coll region: l = %d, " |
| "t = %d, w = %d, h = %d\n", |
| coll_region.left, coll_region.top, |
| coll_region.width, coll_region.height); |
| |
| /* Convert coords back to orig orientation */ |
| switch (fb_data->epdc_fb_var.rotate) { |
| case FB_ROTATE_CW: |
| xres = fb_data->epdc_fb_var.yres; |
| yres = fb_data->epdc_fb_var.xres; |
| rotate = FB_ROTATE_CCW; |
| break; |
| case FB_ROTATE_UD: |
| xres = fb_data->epdc_fb_var.xres; |
| yres = fb_data->epdc_fb_var.yres; |
| rotate = FB_ROTATE_UD; |
| break; |
| case FB_ROTATE_CCW: |
| xres = fb_data->epdc_fb_var.yres; |
| yres = fb_data->epdc_fb_var.xres; |
| rotate = FB_ROTATE_CW; |
| break; |
| default: |
| xres = fb_data->epdc_fb_var.xres; |
| yres = fb_data->epdc_fb_var.yres; |
| rotate = FB_ROTATE_UR; |
| break; |
| } |
| adjust_coordinates(xres, yres, rotate, |
| &coll_region, cur_upd_rect); |
| |
| dev_dbg(fb_data->dev, "Adj coll region: l = %d, " |
| "t = %d, w = %d, h = %d\n", |
| cur_upd_rect->left, cur_upd_rect->top, |
| cur_upd_rect->width, |
| cur_upd_rect->height); |
| } |
| |
| /* |
| * If we collide with newer updates, then |
| * we don't need to re-submit the update. The |
| * idea is that the newer updates should take |
| * precedence anyways, so we don't want to |
| * overwrite them. |
| */ |
| for (temp_mask = fb_data->cur_update->collision_mask, lut = 0; |
| temp_mask != 0; |
| lut++, temp_mask = temp_mask >> 1) { |
| if (!(temp_mask & 0x1)) |
| continue; |
| |
| if (fb_data->lut_update_order[lut] >= |
| fb_data->cur_update->update_desc->update_order) { |
| dev_dbg(fb_data->dev, |
| "Ignoring collision with" |
| "newer update.\n"); |
| ignore_collision = true; |
| break; |
| } |
| } |
| |
| if (!ignore_collision) { |
| free_update = false; |
| /* |
| * If update has markers, clear the LUTs to |
| * avoid signalling that they have completed. |
| */ |
| list_for_each_entry_safe(next_marker, temp, |
| &fb_data->cur_update->update_desc->upd_marker_list, |
| upd_list) |
| next_marker->lut_num = INVALID_LUT; |
| |
| /* Move to collision list */ |
| list_add_tail(&fb_data->cur_update->list, |
| &fb_data->upd_buf_collision_list); |
| } |
| } |
| |
| /* Do we need to free the current update descriptor? */ |
| if (free_update) { |
| /* Handle condition where WB & LUT are both complete */ |
| if (wb_lut_done) |
| list_for_each_entry_safe(next_marker, temp, |
| &fb_data->cur_update->update_desc->upd_marker_list, |
| upd_list) { |
| |
| /* Del from per-update & full list */ |
| list_del_init(&next_marker->upd_list); |
| list_del_init(&next_marker->full_list); |
| |
| /* Signal completion of update */ |
| dev_dbg(fb_data->dev, |
| "Signaling marker (wb) %d\n", |
| next_marker->update_marker); |
| if (next_marker->waiting) |
| complete(&next_marker->update_completion); |
| else |
| kfree(next_marker); |
| } |
| |
| /* Free marker list and update descriptor */ |
| kfree(fb_data->cur_update->update_desc); |
| |
| /* Add to free buffer list */ |
| list_add_tail(&fb_data->cur_update->list, |
| &fb_data->upd_buf_free_list); |
| |
| /* Check to see if all updates have completed */ |
| if (list_empty(&fb_data->upd_pending_list) && |
| is_free_list_full(fb_data) && |
| !epdc_luts_active) { |
| |
| fb_data->updates_active = false; |
| |
| if (fb_data->pwrdown_delay != |
| FB_POWERDOWN_DISABLE) { |
| /* |
| * Set variable to prevent overlapping |
| * enable/disable requests |
| */ |
| fb_data->powering_down = true; |
| |
| /* Schedule EPDC disable */ |
| schedule_delayed_work(&fb_data->epdc_done_work, |
| msecs_to_jiffies(fb_data->pwrdown_delay)); |
| |
| /* Reset counter to reduce chance of overflow */ |
| fb_data->order_cnt = 0; |
| } |
| |
| if (fb_data->waiting_for_idle) |
| complete(&fb_data->updates_done); |
| } |
| } |
| |
| /* Clear current update */ |
| fb_data->cur_update = NULL; |
| |
| /* Clear IRQ for working buffer */ |
| epdc_working_buf_intr(false); |
| epdc_clear_working_buf_irq(); |
| } |
| |
| if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) { |
| /* Queued update scheme processing */ |
| |
| /* Schedule task to submit collision and pending update */ |
| if (!fb_data->powering_down) |
| queue_work(fb_data->epdc_submit_workqueue, |
| &fb_data->epdc_submit_work); |
| |
| /* Release buffer queues */ |
| mutex_unlock(&fb_data->queue_mutex); |
| |
| return; |
| } |
| |
| /* Snapshot update scheme processing */ |
| |
| /* Check to see if any LUTs are free */ |
| if (!epdc_luts_avail) { |
| dev_dbg(fb_data->dev, "No luts available.\n"); |
| mutex_unlock(&fb_data->queue_mutex); |
| return; |
| } |
| |
| epdc_next_lut_15 = epdc_choose_next_lut(fb_data, &next_lut); |
| /* Check to see if there is a valid LUT to use */ |
| if (epdc_next_lut_15 && fb_data->tce_prevent && (fb_data->rev < 20)) { |
| dev_dbg(fb_data->dev, "Must wait for LUT15\n"); |
| mutex_unlock(&fb_data->queue_mutex); |
| return; |
| } |
| |
| /* |
| * 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) { |
| |
| 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)) { |
| dev_dbg(fb_data->dev, "No pending updates.\n"); |
| |
| /* No updates pending, so we are done */ |
| mutex_unlock(&fb_data->queue_mutex); |
| return; |
| } else { |
| dev_dbg(fb_data->dev, "Found a pending update!\n"); |
| |
| /* Process next item in update list */ |
| fb_data->cur_update = |
| list_entry(fb_data->upd_buf_queue.next, |
| struct update_data_list, list); |
| list_del_init(&fb_data->cur_update->list); |
| } |
| } |
| |
| /* Use LUT selected above */ |
| fb_data->cur_update->lut_num = next_lut; |
| |
| /* Associate LUT with update markers */ |
| list_for_each_entry_safe(next_marker, temp, |
| &fb_data->cur_update->update_desc->upd_marker_list, upd_list) |
| next_marker->lut_num = fb_data->cur_update->lut_num; |
| |
| /* Mark LUT as containing new update */ |
| fb_data->lut_update_order[fb_data->cur_update->lut_num] = |
| fb_data->cur_update->update_desc->update_order; |
| |
| /* Enable Collision and WB complete IRQs */ |
| epdc_working_buf_intr(true); |
| epdc_lut_complete_intr(fb_data->rev, fb_data->cur_update->lut_num, true); |
| |
| /* Program EPDC update to process buffer */ |
| next_upd_region = |
| &fb_data->cur_update->update_desc->upd_data.update_region; |
| |
| /* add working buffer update here for external mode */ |
| if (fb_data->epdc_wb_mode) |
| epdc_working_buffer_update(fb_data, fb_data->cur_update, |
| next_upd_region); |
| |
| if (fb_data->cur_update->update_desc->upd_data.temp |
| != TEMP_USE_AMBIENT) { |
| temp_index = mxc_epdc_fb_get_temp_index(fb_data, |
| fb_data->cur_update->update_desc->upd_data.temp); |
| epdc_set_temp(temp_index); |
| } else |
| epdc_set_temp(fb_data->temp_index); |
| epdc_set_update_addr(fb_data->cur_update->phys_addr + |
| fb_data->cur_update->update_desc->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); |
| if (fb_data->rev > 20) |
| epdc_set_update_stride(fb_data->cur_update->update_desc->epdc_stride); |
| if (fb_data->wv_modes_update && |
| (fb_data->cur_update->update_desc->upd_data.waveform_mode |
| == WAVEFORM_MODE_AUTO)) { |
| epdc_set_update_waveform(&fb_data->wv_modes); |
| fb_data->wv_modes_update = false; |
| } |
| |
| epdc_submit_update(fb_data->cur_update->lut_num, |
| fb_data->cur_update->update_desc->upd_data.waveform_mode, |
| fb_data->cur_update->update_desc->upd_data.update_mode, |
| false, false, 0); |
| |
| /* Release buffer queues */ |
| mutex_unlock(&fb_data->queue_mutex); |
| |
| return; |
| } |
| |
| static void draw_mode0(struct mxc_epdc_fb_data *fb_data) |
| { |
| u32 *upd_buf_ptr; |
| int i; |
| struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; |
| u32 xres, yres; |
| |
| upd_buf_ptr = (u32 *)fb_data->info.screen_base; |
| |
| epdc_working_buf_intr(true); |
| epdc_lut_complete_intr(fb_data->rev, 0, true); |
| |
| /* Use unrotated (native) width/height */ |
| if ((screeninfo->rotate == FB_ROTATE_CW) || |
| (screeninfo->rotate == FB_ROTATE_CCW)) { |
| xres = screeninfo->yres; |
| yres = screeninfo->xres; |
| } else { |
| xres = screeninfo->xres; |
| yres = screeninfo->yres; |
| } |
| |
| /* Program EPDC update to process buffer */ |
| epdc_set_update_addr(fb_data->phys_start); |
| epdc_set_update_coord(0, 0); |
| epdc_set_update_dimensions(xres, yres); |
| if (fb_data->rev > 20) |
| epdc_set_update_stride(0); |
| epdc_submit_update(0, fb_data->wv_modes.mode_init, UPDATE_MODE_FULL, |
| false, 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 void mxc_epdc_fb_fw_handler(const struct firmware *fw, |
| void *context) |
| { |
| struct mxc_epdc_fb_data *fb_data = context; |
| int ret; |
| struct mxcfb_waveform_data_file *wv_file; |
| int wv_data_offs; |
| int i; |
| struct mxcfb_update_data update; |
| struct mxcfb_update_marker_data upd_marker_data; |
| struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var; |
| u32 xres, yres; |
| struct clk *epdc_parent; |
| unsigned long rounded_parent_rate, epdc_pix_rate, |
| rounded_pix_clk, target_pix_clk; |
| |
| if (fw == NULL) { |
| /* If default FW file load failed, we give up */ |
| if (fb_data->fw_default_load) |
| return; |
| |
| /* Try to load default waveform */ |
| dev_dbg(fb_data->dev, |
| "Can't find firmware. Trying fallback fw\n"); |
| fb_data->fw_default_load = true; |
| ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, |
| "imx/epdc/epdc.fw", fb_data->dev, GFP_KERNEL, fb_data, |
| mxc_epdc_fb_fw_handler); |
| if (ret) |
| dev_err(fb_data->dev, |
| "Failed request_firmware_nowait err %d\n", ret); |
| |
| return; |
| } |
| |
| wv_file = (struct mxcfb_waveform_data_file *)fw->data; |
| |
| dump_fw_header(fb_data->dev, wv_file); |
| |
| /* 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); |
| |
| /* Set default temperature index using TRT and room temp */ |
| fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, DEFAULT_TEMP); |
| |
| /* 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 | GFP_KERNEL); |
| if (fb_data->waveform_buffer_virt == NULL) { |
| dev_err(fb_data->dev, "Can't allocate mem for waveform!\n"); |
| return; |
| } |
| |
| memcpy(fb_data->waveform_buffer_virt, (u8 *)(fw->data) + wv_data_offs, |
| fb_data->waveform_buffer_size); |
| |
| /* Check for advanced algorithms */ |
| if ((wv_file->wdh.luts & WAVEFORM_HDR_LUT_ADVANCED_ALGO_MASK) != 0) { |
| dev_dbg(fb_data->dev, |
| "Waveform file supports advanced algorithms\n"); |
| fb_data->waveform_is_advanced = true; |
| } else { |
| dev_dbg(fb_data->dev, |
| "Waveform file does not support advanced algorithms\n"); |
| fb_data->waveform_is_advanced = false; |
| } |
| |
| release_firmware(fw); |
| |
| /* Enable clocks to access EPDC regs */ |
| clk_prepare_enable(fb_data->epdc_clk_axi); |
| |
| target_pix_clk = fb_data->cur_mode->vmode->pixclock; |
| |
| rounded_pix_clk = clk_round_rate(fb_data->epdc_clk_pix, target_pix_clk); |
| |
| if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) || |
| (rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) { |
| /* Can't get close enough without changing parent clk */ |
| epdc_parent = clk_get_parent(fb_data->epdc_clk_pix); |
| rounded_parent_rate = clk_round_rate(epdc_parent, target_pix_clk); |
| |
| epdc_pix_rate = target_pix_clk; |
| while (epdc_pix_rate < rounded_parent_rate) |
| epdc_pix_rate *= 2; |
| clk_set_rate(epdc_parent, epdc_pix_rate); |
| |
| rounded_pix_clk = clk_round_rate(fb_data->epdc_clk_pix, target_pix_clk); |
| if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) || |
| (rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) |
| /* Still can't get a good clock, provide warning */ |
| dev_err(fb_data->dev, "Unable to get an accurate EPDC pix clk" |
| "desired = %lu, actual = %lu\n", target_pix_clk, |
| rounded_pix_clk); |
| } |
| |
| clk_set_rate(fb_data->epdc_clk_pix, rounded_pix_clk); |
| |
| /* Enable pix clk for EPDC */ |
| clk_prepare_enable(fb_data->epdc_clk_pix); |
| |
| epdc_init_sequence(fb_data); |
| |
| /* Disable clocks */ |
| clk_disable_unprepare(fb_data->epdc_clk_axi); |
| clk_disable_unprepare(fb_data->epdc_clk_pix); |
| |
| fb_data->hw_ready = true; |
| fb_data->hw_initializing = false; |
| |
| /* Use unrotated (native) width/height */ |
| if ((screeninfo->rotate == FB_ROTATE_CW) || |
| (screeninfo->rotate == FB_ROTATE_CCW)) { |
| xres = screeninfo->yres; |
| yres = screeninfo->xres; |
| } else { |
| xres = screeninfo->xres; |
| yres = screeninfo->yres; |
| } |
| |
| update.update_region.left = 0; |
| update.update_region.width = xres; |
| update.update_region.top = 0; |
| update.update_region.height = yres; |
| update.update_mode = UPDATE_MODE_FULL; |
| update.waveform_mode = WAVEFORM_MODE_AUTO; |
| update.update_marker = INIT_UPDATE_MARKER; |
| update.temp = TEMP_USE_AMBIENT; |
| update.flags = 0; |
| update.dither_mode = 0; |
| |
| upd_marker_data.update_marker = update.update_marker; |
| |
| mxc_epdc_fb_send_update(&update, &fb_data->info); |
| |
| /* Block on initial update */ |
| ret = mxc_epdc_fb_wait_update_complete(&upd_marker_data, |
| &fb_data->info); |
| if (ret < 0) |
| dev_err(fb_data->dev, |
| "Wait for initial update complete failed." |
| " Error = 0x%x", ret); |
| } |
| |
| static int mxc_epdc_fb_init_hw(struct fb_info *info) |
| { |
| struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info; |
| int ret; |
| |
| /* |
| * Create fw search string based on ID string in selected videomode. |
| * Format is "imx/epdc/epdc_[panel string].fw" |
| */ |
| if (fb_data->cur_mode) { |
| strcat(fb_data->fw_str, "imx/epdc/epdc_"); |
| strcat(fb_data->fw_str, fb_data->cur_mode->vmode->name); |
| strcat(fb_data->fw_str, ".fw"); |
| } |
| |
| fb_data->fw_default_load = false; |
| |
| ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, |
| fb_data->fw_str, fb_data->dev, GFP_KERNEL, |
| fb_data, mxc_epdc_fb_fw_handler); |
| if (ret) |
| dev_dbg(fb_data->dev, |
| "Failed request_firmware_nowait err %d\n", ret); |
| |
| return ret; |
| } |
| |
| 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 = fb_data->epdc_fb_var.xres; |
| update.update_region.top = 0; |
| update.update_region.height = fb_data->epdc_fb_var.yres; |
| update.update_mode = UPDATE_MODE_FULL; |
| update.temp = TEMP_USE_AMBIENT; |
| update.update_marker = 0; |
| update.flags = 0; |
| |
| mxc_epdc_fb_send_update(&update, info); |
| |
| return count; |
| } |
| |
| static struct device_attribute fb_attrs[] = { |
| __ATTR(update, S_IRUGO|S_IWUSR, NULL, store_update), |
| }; |
| |
| static const struct of_device_id imx_epdc_dt_ids[] = { |
| { .compatible = "fsl,imx7d-epdc", }, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(of, imx_epdc_dt_ids); |
| |
| static int mxc_epdc_fb_probe(struct platform_device *pdev) |
| { |
| int ret = 0; |
| struct pinctrl *pinctrl; |
| 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; |
| int xres_virt_rot, yres_virt_rot, pix_size_rot; |
| struct fb_var_screeninfo *var_info; |
| struct fb_fix_screeninfo *fix_info; |
| struct pxp_config_data *pxp_conf; |
| struct pxp_proc_data *proc_data; |
| struct scatterlist *sg; |
| struct update_data_list *upd_list; |
| struct update_data_list *plist, *temp_list; |
| int i; |
| unsigned long x_mem_size = 0; |
| u32 val; |
| int irq; |
| struct device_node *np = pdev->dev.of_node; |
| struct device_node *node; |
| phandle phandle; |
| u32 out_val[3]; |
| int enable_gpio; |
| enum of_gpio_flags flag; |
| unsigned short *wk_p; |
| |
| if (!np) |
| return -EINVAL; |
| |
| 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; |
| } |
| |
| ret = of_property_read_u32_array(np, "epdc-ram", out_val, 3); |
| if (ret) { |
| dev_dbg(&pdev->dev, "no epdc-ram property found\n"); |
| } else { |
| phandle = *out_val; |
| |
| node = of_find_node_by_phandle(phandle); |
| if (!node) { |
| dev_dbg(&pdev->dev, "not find gpr node by phandle\n"); |
| ret = PTR_ERR(node); |
| goto out_fbdata; |
| } |
| fb_data->gpr = syscon_node_to_regmap(node); |
| if (IS_ERR(fb_data->gpr)) { |
| dev_err(&pdev->dev, "failed to get gpr regmap\n"); |
| ret = PTR_ERR(fb_data->gpr); |
| goto out_fbdata; |
| } |
| of_node_put(node); |
| fb_data->req_gpr = out_val[1]; |
| fb_data->req_bit = out_val[2]; |
| |
| regmap_update_bits(fb_data->gpr, fb_data->req_gpr, |
| 1 << fb_data->req_bit, 0); |
| } |
| |
| if (of_find_property(np, "en-gpios", NULL)) { |
| enable_gpio = of_get_named_gpio_flags(np, "en-gpios", 0, &flag); |
| if (enable_gpio == -EPROBE_DEFER) { |
| dev_info(&pdev->dev, "GPIO requested is not" |
| "here yet, deferring the probe\n"); |
| return -EPROBE_DEFER; |
| } |
| if (!gpio_is_valid(enable_gpio)) { |
| dev_warn(&pdev->dev, "No dt property: en-gpios\n"); |
| } else { |
| |
| ret = devm_gpio_request_one(&pdev->dev, |
| enable_gpio, |
| (flag & OF_GPIO_ACTIVE_LOW) |
| ? GPIOF_OUT_INIT_LOW : |
| GPIOF_OUT_INIT_HIGH, |
| "en_pins"); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to request gpio" |
| " %d: %d\n", enable_gpio, ret); |
| return -EINVAL; |
| } |
| } |
| } |
| |
| fb_data->qos_regmap = syscon_regmap_lookup_by_phandle(np, "qos"); |
| if (IS_ERR(fb_data->qos_regmap)) { |
| dev_warn(&pdev->dev, "No qos phandle specified. Ignored.\n"); |
| } |
| |
| /* Get platform data and check validity */ |
| fb_data->pdata = &epdc_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; |
| } |
| |
| fb_data->epdc_wb_mode = 1; |
| fb_data->tce_prevent = 0; |
| |
| 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 = memparse(opt + 6, NULL); |
| else if (!strncmp(opt, "tce_prevent", 11)) |
| fb_data->tce_prevent = 1; |
| 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", |
| vmode->xres, 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); |
| fb_data->max_pix_size = PAGE_ALIGN(xres_virt * yres_virt); |
| |
| /* |
| * Have to check to see if aligned buffer size when rotated |
| * is bigger than when not rotated, and use the max |
| */ |
| xres_virt_rot = ALIGN(vmode->yres, 32); |
| yres_virt_rot = ALIGN(vmode->xres, 128); |
| pix_size_rot = PAGE_ALIGN(xres_virt_rot * yres_virt_rot); |
| fb_data->max_pix_size = (fb_data->max_pix_size > pix_size_rot) ? |
| fb_data->max_pix_size : pix_size_rot; |
| |
| buf_size = fb_data->max_pix_size * fb_data->default_bpp/8; |
| |
| /* Compute the number of screens needed based on X memory requested */ |
| if (x_mem_size > 0) { |
| fb_data->num_screens = DIV_ROUND_UP(x_mem_size, buf_size); |
| if (fb_data->num_screens < NUM_SCREENS_MIN) |
| fb_data->num_screens = NUM_SCREENS_MIN; |
| else if (buf_size * fb_data->num_screens > SZ_16M) |
| fb_data->num_screens = SZ_16M / buf_size; |
| } else |
| fb_data->num_screens = NUM_SCREENS_MIN; |
| |
| fb_data->map_size = buf_size * fb_data->num_screens; |
| dev_dbg(&pdev->dev, "memory 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_v2_base = devm_ioremap_resource(&pdev->dev, res); |
| if (epdc_v2_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_DMA | GFP_KERNEL); |
| |
| if (info->screen_base == NULL) { |
| ret = -ENOMEM; |
| goto out_cmap; |
| } |
| 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 * fb_data->num_screens; |
| |
| var_info->pixclock = vmode->pixclock; |
| var_info->left_margin = vmode->left_margin; |
| var_info->right_margin = vmode->right_margin; |
| var_info->upper_margin = vmode->upper_margin; |
| var_info->lower_margin = vmode->lower_margin; |
| var_info->hsync_len = vmode->hsync_len; |
| var_info->vsync_len = vmode->vsync_len; |
| var_info->vmode = FB_VMODE_NONINTERLACED; |
| |
| switch (fb_data->default_bpp) { |
| case 32: |
| case 24: |
| var_info->red.offset = 16; |
| var_info->red.length = 8; |
| var_info->green.offset = 8; |
| var_info->green.length = 8; |
| var_info->blue.offset = 0; |
| var_info->blue.length = 8; |
| break; |
| |
| case 16: |
| var_info->red.offset = 11; |
| var_info->red.length = 5; |
| var_info->green.offset = 5; |
| var_info->green.length = 6; |
| var_info->blue.offset = 0; |
| var_info->blue.length = 5; |
| break; |
| |
| case 8: |
| /* |
| * For 8-bit grayscale, R, G, and B offset are equal. |
| * |
| */ |
| var_info->grayscale = GRAYSCALE_8BIT; |
| |
| var_info->red.length = 8; |
| var_info->red.offset = 0; |
| var_info->red.msb_right = 0; |
| var_info->green.length = 8; |
| var_info->green.offset = 0; |
| var_info->green.msb_right = 0; |
| var_info->blue.length = 8; |
| var_info->blue.offset = 0; |
| var_info->blue.msb_right = 0; |
| break; |
| |
| 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; |
| info->flags = FBINFO_FLAG_DEFAULT; |
| |
| mxc_epdc_fb_set_fix(info); |
| |
| fb_data->auto_mode = AUTO_UPDATE_MODE_REGION_MODE; |
| fb_data->upd_scheme = UPDATE_SCHEME_QUEUE_AND_MERGE; |
| |
| /* Initialize our internal copy of the screeninfo */ |
| fb_data->epdc_fb_var = *var_info; |
| fb_data->fb_offset = 0; |
| fb_data->eof_sync_period = 0; |
| |
| fb_data->epdc_clk_axi = clk_get(fb_data->dev, "epdc_axi"); |
| if (IS_ERR(fb_data->epdc_clk_axi)) { |
| dev_err(&pdev->dev, "Unable to get EPDC AXI clk." |
| "err = %d\n", (int)fb_data->epdc_clk_axi); |
| ret = -ENODEV; |
| goto out_dma_fb; |
| } |
| fb_data->epdc_clk_pix = clk_get(fb_data->dev, "epdc_pix"); |
| if (IS_ERR(fb_data->epdc_clk_pix)) { |
| dev_err(&pdev->dev, "Unable to get EPDC pix clk." |
| "err = %d\n", (int)fb_data->epdc_clk_pix); |
| ret = -ENODEV; |
| goto out_dma_fb; |
| } |
| |
| clk_prepare_enable(fb_data->epdc_clk_axi); |
| clk_prepare_enable(fb_data->epdc_clk_pix); |
| val = __raw_readl(EPDC_VERSION); |
| clk_disable_unprepare(fb_data->epdc_clk_pix); |
| clk_disable_unprepare(fb_data->epdc_clk_axi); |
| fb_data->rev = ((val & EPDC_VERSION_MAJOR_MASK) >> |
| EPDC_VERSION_MAJOR_OFFSET) * 10 |
| + ((val & EPDC_VERSION_MINOR_MASK) >> |
| EPDC_VERSION_MINOR_OFFSET); |
| dev_dbg(&pdev->dev, "EPDC version = %d\n", fb_data->rev); |
| |
| if (fb_data->rev < 20) { |
| fb_data->num_luts = EPDC_V1_NUM_LUTS; |
| fb_data->max_num_updates = EPDC_V1_MAX_NUM_UPDATES; |
| } else { |
| fb_data->num_luts = EPDC_V2_NUM_LUTS; |
| fb_data->max_num_updates = EPDC_V2_MAX_NUM_UPDATES; |
| if (vmode->xres > EPDC_V2_MAX_UPDATE_WIDTH) |
| fb_data->restrict_width = true; |
| } |
| fb_data->max_num_buffers = EPDC_MAX_NUM_BUFFERS; |
| |
| /* |
| * Initialize lists for pending updates, |
| * active update requests, update collisions, |
| * and freely available updates. |
| */ |
| INIT_LIST_HEAD(&fb_data->upd_pending_list); |
| INIT_LIST_HEAD(&fb_data->upd_buf_queue); |
| INIT_LIST_HEAD(&fb_data->upd_buf_free_list); |
| INIT_LIST_HEAD(&fb_data->upd_buf_collision_list); |
| |
| /* Allocate update buffers and add them to the list */ |
| for (i = 0; i < fb_data->max_num_updates; i++) { |
| upd_list = kzalloc(sizeof(*upd_list), GFP_KERNEL); |
| if (upd_list == NULL) { |
| ret = -ENOMEM; |
| goto out_upd_lists; |
| } |
| |
| /* Add newly allocated buffer to free list */ |
| list_add(&upd_list->list, &fb_data->upd_buf_free_list); |
| } |
| |
| fb_data->virt_addr_updbuf = |
| kzalloc(sizeof(void *) * fb_data->max_num_buffers, GFP_KERNEL); |
| fb_data->phys_addr_updbuf = |
| kzalloc(sizeof(dma_addr_t) * fb_data->max_num_buffers, |
| GFP_KERNEL); |
| for (i = 0; i < fb_data->max_num_buffers; i++) { |
| /* |
| * Allocate memory for PxP output buffer. |
| * Each update buffer is 1 byte per pixel, and can |
| * be as big as the full-screen frame buffer |
| */ |
| fb_data->virt_addr_updbuf[i] = |
| kmalloc(fb_data->max_pix_size, GFP_KERNEL); |
| fb_data->phys_addr_updbuf[i] = |
| virt_to_phys(fb_data->virt_addr_updbuf[i]); |
| if (fb_data->virt_addr_updbuf[i] == NULL) { |
| ret = -ENOMEM; |
| goto out_upd_buffers; |
| } |
| |
| dev_dbg(fb_data->info.device, "allocated %d bytes @ 0x%08X\n", |
| fb_data->max_pix_size, fb_data->phys_addr_updbuf[i]); |
| } |
| |
| /* Counter indicating which update buffer should be used next. */ |
| fb_data->upd_buffer_num = 0; |
| |
| /* |
| * Allocate memory for PxP SW workaround buffer |
| * These buffers are used to hold copy of the update region, |
| * before sending it to PxP for processing. |
| */ |
| fb_data->virt_addr_copybuf = |
| dma_alloc_coherent(fb_data->info.device, fb_data->max_pix_size*2, |
| &fb_data->phys_addr_copybuf, |
| GFP_DMA | GFP_KERNEL); |
| if (fb_data->virt_addr_copybuf == NULL) { |
| ret = -ENOMEM; |
| goto out_upd_buffers; |
| } |
| |
| fb_data->virt_addr_y4 = |
| dma_alloc_coherent(fb_data->info.device, fb_data->max_pix_size*2, |
| &fb_data->phys_addr_y4, |
| GFP_DMA | GFP_KERNEL); |
| if (fb_data->virt_addr_y4 == NULL) { |
| ret = -ENOMEM; |
| goto out_upd_buffers; |
| } |
| |
| fb_data->virt_addr_y4c = |
| dma_alloc_coherent(fb_data->info.device, fb_data->max_pix_size*2, |
| &fb_data->phys_addr_y4c, |
| GFP_DMA | GFP_KERNEL); |
| if (fb_data->virt_addr_y4c == NULL) { |
| ret = -ENOMEM; |
| goto out_upd_buffers; |
| } |
| |
| fb_data->virt_addr_black = |
| dma_alloc_coherent(fb_data->info.device, fb_data->max_pix_size*2, |
| &fb_data->phys_addr_black, |
| GFP_DMA | GFP_KERNEL); |
| if (fb_data->virt_addr_black == NULL) { |
| ret = -ENOMEM; |
| goto out_upd_buffers; |
| } |
| |
| 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 | GFP_KERNEL); |
| if (fb_data->working_buffer_virt == NULL) { |
| dev_err(&pdev->dev, "Can't allocate mem for working buf!\n"); |
| ret = -ENOMEM; |
| goto out_copybuffer; |
| } |
| |
| /* initialize the working buffer */ |
| wk_p = (unsigned short *)fb_data->working_buffer_virt; |
| for (i = 0; i < fb_data->cur_mode->vmode->xres * |
| fb_data->cur_mode->vmode->yres; i++) { |
| *wk_p = 0x00F0; |
| wk_p++; |
| } |
| |
| fb_data->tmp_working_buffer_virt = |
| dma_alloc_coherent(&pdev->dev, fb_data->working_buffer_size, |
| &fb_data->tmp_working_buffer_phys, |
| GFP_DMA | GFP_KERNEL); |
| if (fb_data->tmp_working_buffer_virt == NULL) { |
| dev_err(&pdev->dev, "Can't allocate mem for tmp working buf!\n"); |
| ret = -ENOMEM; |
| goto out_copybuffer; |
| } |
| |
| /* Initialize EPDC pins */ |
| pinctrl = devm_pinctrl_get_select_default(&pdev->dev); |
| if (IS_ERR(pinctrl)) { |
| dev_err(&pdev->dev, "can't get/select pinctrl\n"); |
| ret = PTR_ERR(pinctrl); |
| goto out_copybuffer; |
| } |
| |
| fb_data->in_init = false; |
| |
| fb_data->hw_ready = false; |
| fb_data->hw_initializing = false; |
| |
| /* |
| * Set default waveform mode values. |
| * Should be overwritten via ioctl. |
| */ |
| fb_data->wv_modes.mode_init = 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; |
| fb_data->wv_modes_update = true; |
| |
| /* Initialize marker list */ |
| INIT_LIST_HEAD(&fb_data->full_marker_list); |
| |
| /* Initialize all LUTs to inactive */ |
| fb_data->lut_update_order = |
| kzalloc(fb_data->num_luts * sizeof(u32 *), GFP_KERNEL); |
| for (i = 0; i < fb_data->num_luts; i++) |
| fb_data->lut_update_order[i] = 0; |
| |
| INIT_DELAYED_WORK(&fb_data->epdc_done_work, epdc_done_work_func); |
| fb_data->epdc_submit_workqueue = alloc_workqueue("EPDC Submit", |
| WQ_MEM_RECLAIM | WQ_HIGHPRI | |
| WQ_CPU_INTENSIVE | WQ_UNBOUND, 1); |
| INIT_WORK(&fb_data->epdc_submit_work, epdc_submit_work_func); |
| fb_data->epdc_intr_workqueue = alloc_workqueue("EPDC Interrupt", |
| WQ_MEM_RECLAIM | WQ_HIGHPRI | |
| WQ_CPU_INTENSIVE | WQ_UNBOUND, 1); |
| INIT_WORK(&fb_data->epdc_intr_work, epdc_intr_work_func); |
| |
| /* Retrieve EPDC IRQ num */ |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) { |
| dev_err(&pdev->dev, "cannot get IRQ resource\n"); |
| ret = -ENODEV; |
| goto out_dma_work_buf; |
| } |
| fb_data->epdc_irq = irq; |
| |
| /* Register IRQ handler */ |
| ret = devm_request_irq(&pdev->dev, fb_data->epdc_irq, |
| mxc_epdc_irq_handler, 0, "epdc", 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; |
| } |
| |
| 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 = devm_regulator_get(&pdev->dev, "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_dma_work_buf; |
| } |
| fb_data->vcom_regulator = devm_regulator_get(&pdev->dev, "VCOM"); |
| if (IS_ERR(fb_data->vcom_regulator)) { |
| dev_err(&pdev->dev, "Unable to get VCOM regulator." |
| "err = 0x%x\n", (int)fb_data->vcom_regulator); |
| ret = -ENODEV; |
| goto out_dma_work_buf; |
| } |
| fb_data->v3p3_regulator = devm_regulator_get(&pdev->dev, "V3P3"); |
| if (IS_ERR(fb_data->v3p3_regulator)) { |
| dev_err(&pdev->dev, "Unable to get V3P3 regulator." |
| "err = 0x%x\n", (int)fb_data->vcom_regulator); |
| ret = -ENODEV; |
| goto out_dma_work_buf; |
| } |
| |
| 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; |
| |
| mutex_init(&fb_data->queue_mutex); |
| mutex_init(&fb_data->pxp_mutex); |
| mutex_init(&fb_data->power_mutex); |
| |
| /* |
| * 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; |
| proc_data->lut_map = NULL; |
| |
| /* |
| * We initially configure PxP for RGB->YUV conversion, |
| * and only write out Y component of the result. |
| */ |
| |
| /* |
| * Initialize S0 channel parameters |
| * Parameters should match FB format/width/height |
| */ |
| pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565; |
| pxp_conf->s0_param.width = fb_data->info.var.xres_virtual; |
| pxp_conf->s0_param.stride = (var_info->bits_per_pixel * pxp_conf->s0_param.width) >> 3; |
| 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 |
| */ |
| pxp_conf->ol_param[0].combine_enable = false; |
| pxp_conf->ol_param[0].width = 0; |
| pxp_conf->ol_param[0].height = 0; |
| pxp_conf->ol_param[0].pixel_fmt = PXP_PIX_FMT_RGB565; |
| pxp_conf->ol_param[0].color_key_enable = false; |
| pxp_conf->ol_param[0].color_key = -1; |
| pxp_conf->ol_param[0].global_alpha_enable = false; |
| pxp_conf->ol_param[0].global_alpha = 0; |
| pxp_conf->ol_param[0].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.stride = pxp_conf->out_param.width; |
| pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GREY; |
| |
| /* Initialize color map for conversion of 8-bit gray pixels */ |
| fb_data->pxp_conf.proc_data.lut_map = kmalloc(256, GFP_KERNEL); |
| if (fb_data->pxp_conf.proc_data.lut_map == NULL) { |
| dev_err(&pdev->dev, "Can't allocate mem for lut map!\n"); |
| ret = -ENOMEM; |
| goto out_dma_work_buf; |
| } |
| for (i = 0; i < 256; i++) |
| fb_data->pxp_conf.proc_data.lut_map[i] = i; |
| |
| fb_data->pxp_conf.proc_data.lut_map_updated = true; |
| |
| /* |
| * Ensure this is set to NULL here...we will initialize pxp_chan |
| * later in our thread. |
| */ |
| fb_data->pxp_chan = NULL; |
| |
| /* Initialize Scatter-gather list containing 2 buffer addresses. */ |
| sg = fb_data->sg; |
| sg_init_table(sg, SG_NUM); |
| |
| /* |
| * 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->order_cnt = 0; |
| fb_data->waiting_for_wb = false; |
| fb_data->waiting_for_lut = false; |
| fb_data->waiting_for_lut15 = false; |
| 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->wait_for_powerdown = false; |
| fb_data->updates_active = 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_lutmap; |
| } |
| |
| g_fb_data = fb_data; |
| |
| pm_runtime_enable(fb_data->dev); |
| |
| #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 |
| |
| goto out; |
| |
| out_lutmap: |
| kfree(fb_data->pxp_conf.proc_data.lut_map); |
| out_dma_work_buf: |
| dma_free_writecombine(&pdev->dev, fb_data->working_buffer_size, |
| fb_data->working_buffer_virt, fb_data->working_buffer_phys); |
| out_copybuffer: |
| dma_free_writecombine(&pdev->dev, fb_data->max_pix_size*2, |
| fb_data->virt_addr_copybuf, |
| fb_data->phys_addr_copybuf); |
| out_upd_buffers: |
| for (i = 0; i < fb_data->max_num_buffers; i++) |
| if (fb_data->virt_addr_updbuf[i] != NULL) |
| kfree(fb_data->virt_addr_updbuf[i]); |
| if (fb_data->virt_addr_updbuf != NULL) |
| kfree(fb_data->virt_addr_updbuf); |
| if (fb_data->phys_addr_updbuf != NULL) |
| kfree(fb_data->phys_addr_updbuf); |
| out_upd_lists: |
| list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list, |
| list) { |
| list_del(&plist->list); |
| kfree(plist); |
| } |
| out_dma_fb: |
| dma_free_writecombine(&pdev->dev, fb_data->map_size, info->screen_base, |
| fb_data->phys_start); |
| |
| 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); |
| int i; |
| |
| mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &fb_data->info); |
| |
| flush_workqueue(fb_data->epdc_submit_workqueue); |
| destroy_workqueue(fb_data->epdc_submit_workqueue); |
| |
| unregister_framebuffer(&fb_data->info); |
| |
| for (i = 0; i < fb_data->max_num_buffers; i++) |
| if (fb_data->virt_addr_updbuf[i] != NULL) |
| kfree(fb_data->virt_addr_updbuf[i]); |
| if (fb_data->virt_addr_updbuf != NULL) |
| kfree(fb_data->virt_addr_updbuf); |
| if (fb_data->phys_addr_updbuf != NULL) |
| kfree(fb_data->phys_addr_updbuf); |
| |
| 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); |
| if (fb_data->virt_addr_copybuf != NULL) |
| dma_free_writecombine(&pdev->dev, fb_data->max_pix_size*2, |
| fb_data->virt_addr_copybuf, |
| fb_data->phys_addr_copybuf); |
| list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list, |
| list) { |
| list_del(&plist->list); |
| kfree(plist); |
| } |
| #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); |
| |
| /* Release PxP-related resources */ |
| if (fb_data->pxp_chan != NULL) |
| dma_release_channel(&fb_data->pxp_chan->dma_chan); |
| |
| fb_dealloc_cmap(&fb_data->info.cmap); |
| |
| framebuffer_release(&fb_data->info); |
| if (!IS_ERR_OR_NULL(fb_data->gpr)) |
| regmap_update_bits(fb_data->gpr, fb_data->req_gpr, |
| 1 << fb_data->req_bit, 1 << fb_data->req_bit); |
| platform_set_drvdata(pdev, NULL); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int mxc_epdc_fb_suspend(struct device *dev) |
| { |
| struct mxc_epdc_fb_data *data = dev_get_drvdata(dev); |
| int ret; |
| |
| data->pwrdown_delay = FB_POWERDOWN_DISABLE; |
| ret = mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &data->info); |
| |
| if (ret) |
| goto out; |
| |
| out: |
| pinctrl_pm_select_sleep_state(dev); |
| |
| return ret; |
| } |
| |
| static void mxc_epdc_restore_qos(struct mxc_epdc_fb_data *data) |
| { |
| if (IS_ERR_OR_NULL(data->qos_regmap)) { |
| dev_dbg(data->dev, "no QoS setting found.\n"); |
| return; |
| } |
| |
| #define QOS_EPDC_OFFSET 0x3400 |
| #define QOS_PXP0_OFFSET 0x2C00 |
| #define QOS_PXP1_OFFSET 0x3C00 |
| regmap_write(data->qos_regmap, 0, 0); |
| regmap_write(data->qos_regmap, 0x60, 0); |
| regmap_write(data->qos_regmap, QOS_EPDC_OFFSET, 0); |
| regmap_write(data->qos_regmap, QOS_PXP0_OFFSET, 0); |
| regmap_write(data->qos_regmap, QOS_PXP1_OFFSET, 0); |
| |
| regmap_write(data->qos_regmap, QOS_EPDC_OFFSET + 0xd0, 0x0f020722); |
| regmap_write(data->qos_regmap, QOS_EPDC_OFFSET + 0xe0, 0x0f020722); |
| |
| regmap_write(data->qos_regmap, QOS_PXP0_OFFSET, 1); |
| regmap_write(data->qos_regmap, QOS_PXP1_OFFSET, 1); |
| |
| regmap_write(data->qos_regmap, QOS_PXP0_OFFSET + 0x50, 0x0f020222); |
| regmap_write(data->qos_regmap, QOS_PXP1_OFFSET + 0x50, 0x0f020222); |
| regmap_write(data->qos_regmap, QOS_PXP0_OFFSET + 0x60, 0x0f020222); |
| regmap_write(data->qos_regmap, QOS_PXP1_OFFSET + 0x60, 0x0f020222); |
| regmap_write(data->qos_regmap, QOS_PXP0_OFFSET + 0x70, 0x0f020422); |
| regmap_write(data->qos_regmap, QOS_PXP1_OFFSET + 0x70, 0x0f020422); |
| |
| if (!IS_ERR_OR_NULL(data->gpr)) |
| regmap_update_bits(data->gpr, 0x34, 0xe080, 0xe080); |
| } |
| |
| static int mxc_epdc_fb_resume(struct device *dev) |
| { |
| struct mxc_epdc_fb_data *data = dev_get_drvdata(dev); |
| |
| pinctrl_pm_select_default_state(dev); |
| |
| mxc_epdc_restore_qos(data); |
| |
| mxc_epdc_fb_blank(FB_BLANK_UNBLANK, &data->info); |
| epdc_init_settings(data); |
| data->updates_active = false; |
| |
| return 0; |
| } |
| #else |
| #define mxc_epdc_fb_suspend NULL |
| #define mxc_epdc_fb_resume NULL |
| #endif |
| |
| #ifdef CONFIG_PM |
| static int mxc_epdc_fb_runtime_suspend(struct device *dev) |
| { |
| release_bus_freq(BUS_FREQ_HIGH); |
| dev_dbg(dev, "epdc busfreq high release.\n"); |
| |
| return 0; |
| } |
| |
| static int mxc_epdc_fb_runtime_resume(struct device *dev) |
| { |
| request_bus_freq(BUS_FREQ_HIGH); |
| dev_dbg(dev, "epdc busfreq high request.\n"); |
| |
| return 0; |
| } |
| #else |
| #define mxc_epdc_fb_runtime_suspend NULL |
| #define mxc_epdc_fb_runtime_resume NULL |
| #endif |
| |
| static const struct dev_pm_ops mxc_epdc_fb_pm_ops = { |
| SET_RUNTIME_PM_OPS(mxc_epdc_fb_runtime_suspend, |
| mxc_epdc_fb_runtime_resume, NULL) |
| SET_SYSTEM_SLEEP_PM_OPS(mxc_epdc_fb_suspend, mxc_epdc_fb_resume) |
| }; |
| |
| static void mxc_epdc_fb_shutdown(struct platform_device *pdev) |
| { |
| struct mxc_epdc_fb_data *fb_data = platform_get_drvdata(pdev); |
| |
| /* Disable power to the EPD panel */ |
| if (regulator_is_enabled(fb_data->vcom_regulator)) |
| regulator_disable(fb_data->vcom_regulator); |
| if (regulator_is_enabled(fb_data->display_regulator)) |
| regulator_disable(fb_data->display_regulator); |
| |
| /* Disable clocks to EPDC */ |
| clk_prepare_enable(fb_data->epdc_clk_axi); |
| clk_prepare_enable(fb_data->epdc_clk_pix); |
| __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_SET); |
| clk_disable_unprepare(fb_data->epdc_clk_pix); |
| clk_disable_unprepare(fb_data->epdc_clk_axi); |
| |
| /* turn off the V3p3 */ |
| if (regulator_is_enabled(fb_data->v3p3_regulator)) |
| regulator_disable(fb_data->v3p3_regulator); |
| } |
| |
| static struct platform_driver mxc_epdc_fb_driver = { |
| .probe = mxc_epdc_fb_probe, |
| .remove = mxc_epdc_fb_remove, |
| .shutdown = mxc_epdc_fb_shutdown, |
| .driver = { |
| .name = "imx_epdc_v2_fb", |
| .owner = THIS_MODULE, |
| .of_match_table = of_match_ptr(imx_epdc_dt_ids), |
| .pm = &mxc_epdc_fb_pm_ops, |
| }, |
| }; |
| |
| /* 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; |
| |
| /* |
| * if epd works in external mode, we should queue epdc_intr_workqueue |
| * after a wfe_a process finishes. |
| */ |
| if (fb_data->epdc_wb_mode && (tx_desc->proc_data.engine_enable & PXP_ENABLE_WFE_A)) { |
| pxp_get_collision_info(&fb_data->col_info); |
| queue_work(fb_data->epdc_intr_workqueue, |
| &fb_data->epdc_intr_work); |
| } |
| |
| /* This call will signal wait_for_completion_timeout() in send_buffer_to_pxp */ |
| complete(&fb_data->pxp_tx_cmpl); |
| } |
| |
| static bool chan_filter(struct dma_chan *chan, void *arg) |
| { |
| if (imx_dma_is_pxp(chan)) |
| return true; |
| else |
| return false; |
| } |
| |
| /* Function to request PXP DMA channel */ |
| static int pxp_chan_init(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, chan_filter, NULL); |
| if (!chan) { |
| dev_err(fb_data->dev, "Unsuccessfully received channel!!!!\n"); |
| return -EBUSY; |
| } |
| |
| fb_data->pxp_chan = to_pxp_channel(chan); |
| fb_data->pxp_chan->client = fb_data; |
| |
| init_completion(&fb_data->pxp_tx_cmpl); |
| |
| return 0; |
| } |
| |
| static int pxp_wfe_a_process_clear_workingbuffer(struct mxc_epdc_fb_data *fb_data, |
| u32 panel_width, u32 panel_height) |
| { |
| 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, j = 0, ret; |
| int length; |
| |
| dev_dbg(fb_data->dev, "Starting PxP WFE_A process for clearing WB.\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); |
| |
| dma_chan = &fb_data->pxp_chan->dma_chan; |
| |
| txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2 + 4, |
| DMA_TO_DEVICE, |
| DMA_PREP_INTERRUPT, |
| NULL); |
| 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; |
| |
| proc_data->working_mode = PXP_MODE_STANDARD; |
| proc_data->engine_enable = PXP_ENABLE_WFE_A; |
| proc_data->lut = 0; |
| proc_data->detection_only = 0; |
| proc_data->reagl_en = 0; |
| proc_data->partial_update = 0; |
| proc_data->alpha_en = 1; |
| proc_data->lut_sels = fb_data->luts_complete; |
| proc_data->lut_cleanup = 1; |
| |
| pxp_conf->wfe_a_fetch_param[0].stride = panel_width; |
| pxp_conf->wfe_a_fetch_param[0].width = panel_width; |
| pxp_conf->wfe_a_fetch_param[0].height = panel_height; |
| pxp_conf->wfe_a_fetch_param[0].paddr = fb_data->phys_addr_black; |
| pxp_conf->wfe_a_fetch_param[1].stride = panel_width; |
| pxp_conf->wfe_a_fetch_param[1].width = panel_width; |
| pxp_conf->wfe_a_fetch_param[1].height = panel_height; |
| pxp_conf->wfe_a_fetch_param[1].paddr = fb_data->working_buffer_phys; |
| pxp_conf->wfe_a_fetch_param[0].left = 0; |
| pxp_conf->wfe_a_fetch_param[0].top = 0; |
| pxp_conf->wfe_a_fetch_param[1].left = 0; |
| pxp_conf->wfe_a_fetch_param[1].top = 0; |
| |
| pxp_conf->wfe_a_store_param[0].stride = panel_width; |
| pxp_conf->wfe_a_store_param[0].width = panel_width; |
| pxp_conf->wfe_a_store_param[0].height = panel_height; |
| pxp_conf->wfe_a_store_param[0].paddr = fb_data->phys_addr_y4c; |
| pxp_conf->wfe_a_store_param[1].stride = panel_width; |
| pxp_conf->wfe_a_store_param[1].width = panel_width; |
| pxp_conf->wfe_a_store_param[1].height = panel_height; |
| pxp_conf->wfe_a_store_param[1].paddr = fb_data->working_buffer_phys; |
| pxp_conf->wfe_a_store_param[0].left = 0; |
| pxp_conf->wfe_a_store_param[0].top = 0; |
| pxp_conf->wfe_a_store_param[1].left = 0; |
| pxp_conf->wfe_a_store_param[1].top = 0; |
| |
| desc = to_tx_desc(txd); |
| length = desc->len; |
| |
| memcpy(&desc->proc_data, proc_data, sizeof(struct pxp_proc_data)); |
| for (i = 0; i < length; i++) { |
| if (i == 0 || i == 1) {/* wfe_a won't use s0 or output at all */ |
| desc = desc->next; |
| |
| } else if ((pxp_conf->proc_data.engine_enable & PXP_ENABLE_WFE_A) && (j < 4)) { |
| for (j = 0; j < 4; j++) { |
| if (j == 0) { |
| memcpy(&desc->layer_param.processing_param, |
| &pxp_conf->wfe_a_fetch_param[0], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_FETCH0; |
| } else if (j == 1) { |
| memcpy(&desc->layer_param.processing_param, |
| &pxp_conf->wfe_a_fetch_param[1], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_FETCH1; |
| } else if (j == 2) { |
| memcpy(&desc->layer_param.processing_param, |
| &pxp_conf->wfe_a_store_param[0], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_STORE0; |
| } else if (j == 3) { |
| memcpy(&desc->layer_param.processing_param, |
| &pxp_conf->wfe_a_store_param[1], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_STORE1; |
| } |
| |
| desc = desc->next; |
| } |
| |
| i += 4; |
| } |
| } |
| |
| /* Submitting our TX starts the PxP processing task */ |
| cookie = txd->tx_submit(txd); |
| if (cookie < 0) { |
| dev_err(fb_data->info.device, "Error sending FB through PxP\n"); |
| return -EIO; |
| } |
| |
| fb_data->txd = txd; |
| |
| /* trigger ePxP */ |
| dma_async_issue_pending(dma_chan); |
| |
| return 0; |
| } |
| |
| static int pxp_clear_wb_work_func(struct mxc_epdc_fb_data *fb_data) |
| { |
| unsigned int hist_stat; |
| int ret; |
| |
| dev_dbg(fb_data->dev, "PxP WFE to clear working buffer.\n"); |
| |
| mutex_lock(&fb_data->pxp_mutex); |
| ret = pxp_wfe_a_process_clear_workingbuffer(fb_data, fb_data->cur_mode->vmode->xres, fb_data->cur_mode->vmode->yres); |
| 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) { |
| epdc_powerup(fb_data); |
| } |
| |
| /* This is a blocking call, so upon return PxP tx should be done */ |
| ret = pxp_complete_update(fb_data, &hist_stat); |
| if (ret) { |
| dev_err(fb_data->dev, "Unable to complete PxP update task: clear wb process\n"); |
| mutex_unlock(&fb_data->pxp_mutex); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| |
| /* PS_AS_OUT */ |
| static int pxp_legacy_process(struct mxc_epdc_fb_data *fb_data, |
| u32 src_width, u32 src_height, |
| struct mxcfb_rect *update_region) |
| { |
| dma_cookie_t cookie; |
| struct scatterlist *sg = fb_data->sg; |
| struct dma_chan *dma_chan; |
| struct pxp_tx_desc *desc; |
| struct dma_async_tx_descriptor *txd; |
| struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; |
| struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; |
| struct fb_var_screeninfo *screeninfo = &fb_data->info.var; |
| int i, ret; |
| int length; |
| |
| dev_dbg(fb_data->dev, "Starting PxP legacy process.\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); |
| |
| 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, |
| NULL); |
| 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; |
| |
| proc_data->working_mode = PXP_MODE_LEGACY; |
| proc_data->engine_enable = PXP_ENABLE_PS_AS_OUT; |
| |
| /* |
| * Configure PxP for processing of new update region |
| * The rest of our config params were set up in |
| * probe() and should not need to be changed. |
| */ |
| pxp_conf->s0_param.width = src_width; |
| pxp_conf->s0_param.stride = (screeninfo->bits_per_pixel * src_width) >> 3; |
| pxp_conf->s0_param.height = src_height; |
| proc_data->srect.top = update_region->top; |
| proc_data->srect.left = update_region->left; |
| proc_data->srect.width = update_region->width; |
| proc_data->srect.height = update_region->height; |
| proc_data->lut_cleanup = 0; |
| |
| /* |
| * 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; |
| |
| /* PXP expects rotation in terms of degrees */ |
| proc_data->rotate = fb_data->epdc_fb_var.rotate * 90; |
| if (proc_data->rotate > 270) |
| proc_data->rotate = 0; |
| |
| /* we should pass the rotated values to PXP */ |
| if ((proc_data->rotate == 90) || (proc_data->rotate == 270)) { |
| proc_data->drect.width = proc_data->srect.height; |
| proc_data->drect.height = proc_data->srect.width; |
| pxp_conf->out_param.width = update_region->height; |
| pxp_conf->out_param.height = update_region->width; |
| pxp_conf->out_param.stride = update_region->height; |
| } else { |
| proc_data->drect.width = proc_data->srect.width; |
| proc_data->drect.height = proc_data->srect.height; |
| pxp_conf->out_param.width = update_region->width; |
| pxp_conf->out_param.height = update_region->height; |
| pxp_conf->out_param.stride = update_region->width; |
| } |
| |
| /* For EPDC v2.0, we need output to be 64-bit |
| * aligned since EPDC stride does not work. */ |
| if (fb_data->rev <= 20) |
| pxp_conf->out_param.stride = ALIGN(pxp_conf->out_param.stride, 8); |
| |
| 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)); |
| desc = desc->next; |
| |
| } 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)); |
| desc = desc->next; |
| } |
| } |
| |
| /* Submitting our TX starts the PxP processing task */ |
| cookie = txd->tx_submit(txd); |
| if (cookie < 0) { |
| dev_err(fb_data->info.device, "Error sending FB through PxP\n"); |
| return -EIO; |
| } |
| |
| fb_data->txd = txd; |
| |
| /* trigger ePxP */ |
| dma_async_issue_pending(dma_chan); |
| |
| return 0; |
| } |
| |
| static int pxp_process_dithering(struct mxc_epdc_fb_data *fb_data, |
| struct mxcfb_rect *update_region) |
| { |
| dma_cookie_t cookie; |
| struct scatterlist *sg = fb_data->sg; |
| struct dma_chan *dma_chan; |
| struct pxp_tx_desc *desc; |
| struct dma_async_tx_descriptor *txd; |
| struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; |
| struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; |
| int i, j = 0, ret; |
| int length; |
| |
| dev_dbg(fb_data->dev, "Starting PxP Dithering process.\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); |
| |
| dma_chan = &fb_data->pxp_chan->dma_chan; |
| |
| txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2 + 4, |
| DMA_TO_DEVICE, |
| DMA_PREP_INTERRUPT, |
| NULL); |
| 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; |
| |
| proc_data->working_mode = PXP_MODE_STANDARD; |
| proc_data->engine_enable = PXP_ENABLE_DITHER; |
| |
| pxp_conf->dither_fetch_param[0].stride = update_region->width; |
| pxp_conf->dither_fetch_param[0].width = update_region->width; |
| pxp_conf->dither_fetch_param[0].height = update_region->height; |
| #ifdef USE_PS_AS_OUTPUT |
| pxp_conf->dither_fetch_param[0].paddr = sg_dma_address(&sg[1]); |
| #else |
| pxp_conf->dither_fetch_param[0].paddr = sg_dma_address(&sg[0]); |
| #endif |
| pxp_conf->dither_fetch_param[1].stride = update_region->width; |
| pxp_conf->dither_fetch_param[1].width = update_region->width; |
| pxp_conf->dither_fetch_param[1].height = update_region->height; |
| pxp_conf->dither_fetch_param[1].paddr = pxp_conf->dither_fetch_param[0].paddr; |
| |
| pxp_conf->dither_store_param[0].stride = update_region->width; |
| pxp_conf->dither_store_param[0].width = update_region->width; |
| pxp_conf->dither_store_param[0].height = update_region->height; |
| pxp_conf->dither_store_param[0].paddr = fb_data->phys_addr_y4; |
| pxp_conf->dither_store_param[1].stride = update_region->width; |
| pxp_conf->dither_store_param[1].width = update_region->width; |
| pxp_conf->dither_store_param[1].height = update_region->height; |
| pxp_conf->dither_store_param[1].paddr = pxp_conf->dither_store_param[0].paddr; |
| |
| 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)); |
| desc = desc->next; |
| } 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)); |
| desc = desc->next; |
| } else if ((pxp_conf->proc_data.engine_enable & PXP_ENABLE_DITHER) && (j < 4)) { |
| for (j = 0; j < 4; j++) { |
| if (j == 0) { |
| memcpy(&desc->layer_param.processing_param, |
| &pxp_conf->dither_fetch_param[0], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param.flag = PXP_BUF_FLAG_DITHER_FETCH0; |
| } else if (j == 1) { |
| memcpy(&desc->layer_param.processing_param, |
| &pxp_conf->dither_fetch_param[1], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param.flag = PXP_BUF_FLAG_DITHER_FETCH1; |
| } else if (j == 2) { |
| memcpy(&desc->layer_param.processing_param, |
| &pxp_conf->dither_store_param[0], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param.flag = PXP_BUF_FLAG_DITHER_STORE0; |
| } else if (j == 3) { |
| memcpy(&desc->layer_param.processing_param, |
| &pxp_conf->dither_store_param[1], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param.flag = PXP_BUF_FLAG_DITHER_STORE1; |
| } |
| |
| desc = desc->next; |
| } |
| |
| i += 4; |
| } |
| } |
| |
| /* Submitting our TX starts the PxP processing task */ |
| cookie = txd->tx_submit(txd); |
| if (cookie < 0) { |
| dev_err(fb_data->info.device, "Error sending FB through PxP\n"); |
| return -EIO; |
| } |
| |
| fb_data->txd = txd; |
| |
| /* trigger ePxP */ |
| dma_async_issue_pending(dma_chan); |
| |
| return 0; |
| } |
| |
| /* |
| * 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_wfe_a_process(struct mxc_epdc_fb_data *fb_data, |
| struct mxcfb_rect *update_region, |
| struct update_data_list *upd_data_list) |
| { |
| 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; |
| u32 wv_mode = upd_data_list->update_desc->upd_data.waveform_mode; |
| int i, j = 0, ret; |
| int length; |
| bool is_transform; |
| |
| dev_dbg(fb_data->dev, "Starting PxP WFE_A process.\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); |
| |
| dma_chan = &fb_data->pxp_chan->dma_chan; |
| |
| txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2 + 4, |
| DMA_TO_DEVICE, |
| DMA_PREP_INTERRUPT, |
| NULL); |
| 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; |
| |
| proc_data->working_mode = PXP_MODE_STANDARD; |
| proc_data->engine_enable = PXP_ENABLE_WFE_A; |
| proc_data->lut = upd_data_list->lut_num; |
| proc_data->alpha_en = 0; |
| proc_data->lut_sels = fb_data->luts_complete; |
| proc_data->lut_status_1 = __raw_readl(EPDC_STATUS_LUTS); |
| proc_data->lut_status_2 = __raw_readl(EPDC_STATUS_LUTS2); |
| proc_data->lut_cleanup = 0; |
| |
| if (upd_data_list->update_desc->upd_data.flags & EPDC_FLAG_TEST_COLLISION) { |
| proc_data->detection_only = 1; |
| dev_dbg(fb_data->info.device, |
| "collision test_only send to pxp\n"); |
| } else |
| proc_data->detection_only = 0; |
| |
| if (wv_mode == WAVEFORM_MODE_GLR16 |
| || wv_mode == WAVEFORM_MODE_GLD16) |
| proc_data->reagl_en = 1; |
| |
| if (upd_data_list->update_desc->upd_data.update_mode == UPDATE_MODE_PARTIAL) |
| proc_data->partial_update = 1; |
| else |
| proc_data->partial_update = 0; |
| |
| /* fetch0 is upd buffer */ |
| pxp_conf->wfe_a_fetch_param[0].stride = upd_data_list->update_desc->epdc_stride; |
| pxp_conf->wfe_a_fetch_param[0].width = update_region->width; |
| pxp_conf->wfe_a_fetch_param[0].height = update_region->height; |
| /* upd buffer left and top should be always 0 */ |
| pxp_conf->wfe_a_fetch_param[0].left = 0; |
| pxp_conf->wfe_a_fetch_param[0].top = 0; |
| if (proc_data->dither_mode) { |
| pxp_conf->wfe_a_fetch_param[0].paddr = fb_data->phys_addr_y4; |
| } else { |
| is_transform = ((upd_data_list->update_desc->upd_data.flags & |
| (EPDC_FLAG_ENABLE_INVERSION | EPDC_FLAG_USE_DITHERING_Y1 | |
| EPDC_FLAG_USE_DITHERING_Y4 | EPDC_FLAG_FORCE_MONOCHROME | |
| EPDC_FLAG_USE_CMAP)) && (proc_data->scaling == 0) && |
| (proc_data->hflip == 0) && (proc_data->vflip == 0)) ? |
| true : false; |
| |
| if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) && |
| (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT) && |
| !is_transform && (proc_data->dither_mode == 0) && |
| !(upd_data_list->update_desc->upd_data.flags & |
| EPDC_FLAG_USE_ALT_BUFFER) && |
| !fb_data->restrict_width) { |
| sg_dma_address(&sg[0]) = fb_data->info.fix.smem_start; |
| sg_set_page(&sg[0], |
| virt_to_page(fb_data->info.screen_base), |
| fb_data->info.fix.smem_len, |
| offset_in_page(fb_data->info.screen_base)); |
| pxp_conf->wfe_a_fetch_param[0].paddr = |
| sg_dma_address(&sg[0]); |
| |
| pxp_conf->wfe_a_fetch_param[0].left = update_region->left; |
| pxp_conf->wfe_a_fetch_param[0].top = update_region->top; |
| } else |
| pxp_conf->wfe_a_fetch_param[0].paddr = |
| upd_data_list->phys_addr + upd_data_list->update_desc->epdc_offs; |
| } |
| |
| /* fetch1 is working buffer */ |
| pxp_conf->wfe_a_fetch_param[1].stride = fb_data->cur_mode->vmode->xres; |
| pxp_conf->wfe_a_fetch_param[1].width = update_region->width; |
| pxp_conf->wfe_a_fetch_param[1].height = update_region->height; |
| pxp_conf->wfe_a_fetch_param[1].paddr = fb_data->working_buffer_phys; |
| pxp_conf->wfe_a_fetch_param[1].left = update_region->left; |
| pxp_conf->wfe_a_fetch_param[1].top = update_region->top; |
| |
| /* store0 is y4c buffer */ |
| pxp_conf->wfe_a_store_param[0].stride = fb_data->cur_mode->vmode->xres; |
| pxp_conf->wfe_a_store_param[0].width = update_region->width; |
| pxp_conf->wfe_a_store_param[0].height = update_region->height; |
| pxp_conf->wfe_a_store_param[0].paddr = fb_data->phys_addr_y4c; |
| |
| /* store1 is (temp) working buffer */ |
| pxp_conf->wfe_a_store_param[1].stride = fb_data->cur_mode->vmode->xres; |
| pxp_conf->wfe_a_store_param[1].width = update_region->width; |
| pxp_conf->wfe_a_store_param[1].height = update_region->height; |
| if (wv_mode == WAVEFORM_MODE_GLR16 |
| || wv_mode == WAVEFORM_MODE_GLD16) |
| pxp_conf->wfe_a_store_param[1].paddr = fb_data->tmp_working_buffer_phys; |
| else |
| pxp_conf->wfe_a_store_param[1].paddr = fb_data->working_buffer_phys; |
| pxp_conf->wfe_a_store_param[1].left = update_region->left; |
| pxp_conf->wfe_a_store_param[1].top = update_region->top; |
| |
| desc = to_tx_desc(txd); |
| length = desc->len; |
| |
| memcpy(&desc->proc_data, proc_data, sizeof(struct pxp_proc_data)); |
| for (i = 0; i < length; i++) { |
| if (i == 0 || i == 1) {/* wfe_a won't use s0 or output at all */ |
| desc = desc->next; |
| } else if ((pxp_conf->proc_data.engine_enable & PXP_ENABLE_WFE_A) && (j < 4)) { |
| for (j = 0; j < 4; j++) { |
| if (j == 0) { |
| memcpy(&desc->layer_param.processing_param, |
| &pxp_conf->wfe_a_fetch_param[0], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_FETCH0; |
| } else if (j == 1) { |
| memcpy(&desc->layer_param.processing_param, |
| &pxp_conf->wfe_a_fetch_param[1], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_FETCH1; |
| } else if (j == 2) { |
| memcpy(&desc->layer_param.processing_param, |
| &pxp_conf->wfe_a_store_param[0], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_STORE0; |
| } else if (j == 3) { |
| memcpy(&desc->layer_param.processing_param, |
| &pxp_conf->wfe_a_store_param[1], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param.flag = PXP_BUF_FLAG_WFE_A_STORE1; |
| } |
| |
| desc = desc->next; |
| } |
| |
| i += 4; |
| } |
| } |
| |
| /* Submitting our TX starts the PxP processing task */ |
| cookie = txd->tx_submit(txd); |
| if (cookie < 0) { |
| dev_err(fb_data->info.device, "Error sending FB through PxP\n"); |
| return -EIO; |
| } |
| |
| fb_data->txd = txd; |
| |
| /* trigger ePxP */ |
| dma_async_issue_pending(dma_chan); |
| |
| return 0; |
| } |
| |
| /* For REAGL/-D processing */ |
| static int pxp_wfe_b_process_update(struct mxc_epdc_fb_data *fb_data, |
| struct mxcfb_rect *update_region) |
| { |
| dma_cookie_t cookie; |
| struct scatterlist *sg = fb_data->sg; |
| struct dma_chan *dma_chan; |
| struct pxp_tx_desc *desc; |
| struct dma_async_tx_descriptor *txd; |
| struct pxp_config_data *pxp_conf = &fb_data->pxp_conf; |
| struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data; |
| int i, j = 0, ret; |
| int length; |
| |
| dev_dbg(fb_data->dev, "Starting PxP WFE_B process.\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); |
| |
| dma_chan = &fb_data->pxp_chan->dma_chan; |
| |
| txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2 + 4, |
| DMA_TO_DEVICE, |
| DMA_PREP_INTERRUPT, |
| NULL); |
| 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; |
| |
| proc_data->working_mode = PXP_MODE_STANDARD; |
| proc_data->engine_enable = PXP_ENABLE_WFE_B; |
| proc_data->lut_update = false; |
| proc_data->lut_cleanup = 0; |
| |
| pxp_conf->wfe_b_fetch_param[0].stride = fb_data->cur_mode->vmode->xres; |
| pxp_conf->wfe_b_fetch_param[0].width = fb_data->cur_mode->vmode->xres; |
| pxp_conf->wfe_b_fetch_param[0].height = fb_data->cur_mode->vmode->yres; |
| pxp_conf->wfe_b_fetch_param[0].paddr = fb_data->phys_addr_black; |
| pxp_conf->wfe_b_fetch_param[1].stride = fb_data->cur_mode->vmode->xres; |
| pxp_conf->wfe_b_fetch_param[1].width = update_region->width; |
| pxp_conf->wfe_b_fetch_param[1].height = update_region->height; |
| pxp_conf->wfe_b_fetch_param[1].top = update_region->top; |
| pxp_conf->wfe_b_fetch_param[1].left = update_region->left; |
| pxp_conf->wfe_b_fetch_param[1].paddr = fb_data->tmp_working_buffer_phys; |
| |
| pxp_conf->wfe_b_store_param[0].stride = fb_data->cur_mode->vmode->xres; |
| pxp_conf->wfe_b_store_param[0].width = update_region->width; |
| pxp_conf->wfe_b_store_param[0].height = update_region->height; |
| pxp_conf->wfe_b_store_param[0].top = update_region->top; |
| pxp_conf->wfe_b_store_param[0].left = update_region->left; |
| pxp_conf->wfe_b_store_param[0].paddr = fb_data->working_buffer_phys; |
| pxp_conf->wfe_b_store_param[1].stride = fb_data->cur_mode->vmode->xres; |
| pxp_conf->wfe_b_store_param[1].width = update_region->width; |
| pxp_conf->wfe_b_store_param[1].height = update_region->height; |
| pxp_conf->wfe_b_store_param[1].paddr = 0; |
| |
| 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)); |
| desc = desc->next; |
| } 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)); |
| desc = desc->next; |
| } else |
| if ((pxp_conf->proc_data.engine_enable & PXP_ENABLE_WFE_B) |
| && (j < 4)) { |
| for (j = 0; j < 4; j++) { |
| if (j == 0) { |
| memcpy(&desc->layer_param. |
| processing_param, |
| &pxp_conf->wfe_b_fetch_param[0], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param. |
| flag = PXP_BUF_FLAG_WFE_B_FETCH0; |
| } else if (j == 1) { |
| memcpy(&desc->layer_param. |
| processing_param, |
| &pxp_conf->wfe_b_fetch_param[1], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param. |
| flag = PXP_BUF_FLAG_WFE_B_FETCH1; |
| } else if (j == 2) { |
| memcpy(&desc->layer_param. |
| processing_param, |
| &pxp_conf->wfe_b_store_param[0], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param. |
| flag = PXP_BUF_FLAG_WFE_B_STORE0; |
| } else if (j == 3) { |
| memcpy(&desc->layer_param. |
| processing_param, |
| &pxp_conf->wfe_b_store_param[1], |
| sizeof(struct pxp_layer_param)); |
| desc->layer_param.processing_param. |
| flag = PXP_BUF_FLAG_WFE_B_STORE1; |
| } |
| |
| desc = desc->next; |
| } |
| |
| i += 4; |
| } |
| } |
| |
| /* Submitting our TX starts the PxP processing task */ |
| cookie = txd->tx_submit(txd); |
| if (cookie < 0) { |
| dev_err(fb_data->info.device, "Error sending FB through PxP\n"); |
| return -EIO; |
| } |
| |
| fb_data->txd = txd; |
| |
| /* trigger ePxP */ |
| dma_async_issue_pending(dma_chan); |
| |
| return 0; |
| } |
| |
| static int pxp_complete_update(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 * 2); |
| if (ret <= 0) { |
| dev_info(fb_data->info.device, |
| "PxP operation failed due to %s\n", |
| ret < 0 ? "user interrupt" : "timeout"); |
| dma_release_channel(&fb_data->pxp_chan->dma_chan); |
| fb_data->pxp_chan = NULL; |
| return ret ? : -ETIMEDOUT; |
| } |
| |
| if ((fb_data->pxp_conf.proc_data.lut_transform & EPDC_FLAG_USE_CMAP) && |
| fb_data->pxp_conf.proc_data.lut_map_updated) |
| fb_data->pxp_conf.proc_data.lut_map_updated = false; |
| |
| *hist_stat = to_tx_desc(fb_data->txd)->hist_status; |
| dma_release_channel(&fb_data->pxp_chan->dma_chan); |
| fb_data->pxp_chan = NULL; |
| |
| dev_dbg(fb_data->dev, "TX completed\n"); |
| |
| return 0; |
| } |
| |
| /* |
| * Different dithering algorithm can be used. We chose |
| * to implement Bill Atkinson's algorithm as an example |
| * Thanks Bill Atkinson for his dithering algorithm. |
| */ |
| |
| /* |
| * Dithering algorithm implementation - Y8->Y1 version 1.0 for i.MX |
| */ |
| static void do_dithering_processing_Y1_v1_0( |
| unsigned char *update_region_virt_ptr, |
| dma_addr_t update_region_phys_ptr, |
| struct mxcfb_rect *update_region, |
| unsigned long update_region_stride, |
| int *err_dist) |
| { |
| |
| /* create a temp error distribution array */ |
| int bwPix; |
| int y; |
| int col; |
| int *err_dist_l0, *err_dist_l1, *err_dist_l2, distrib_error; |
| int width_3 = update_region->width + 3; |
| char *y8buf; |
| int x_offset = 0; |
| |
| /* prime a few elements the error distribution array */ |
| for (y = 0; y < update_region->height; y++) { |
| /* Dithering the Y8 in sbuf to BW suitable for A2 waveform */ |
| err_dist_l0 = err_dist + (width_3) * (y % 3); |
| err_dist_l1 = err_dist + (width_3) * ((y + 1) % 3); |
| err_dist_l2 = err_dist + (width_3) * ((y + 2) % 3); |
| |
| y8buf = update_region_virt_ptr + x_offset; |
| |
| /* scan the line and convert the Y8 to BW */ |
| for (col = 1; col <= update_region->width; col++) { |
| bwPix = *(err_dist_l0 + col) + *y8buf; |
| |
| if (bwPix >= 128) { |
| *y8buf++ = 0xff; |
| distrib_error = (bwPix - 255) >> 3; |
| } else { |
| *y8buf++ = 0; |
| distrib_error = bwPix >> 3; |
| } |
| |
| /* modify the error distribution buffer */ |
| *(err_dist_l0 + col + 2) += distrib_error; |
| *(err_dist_l1 + col + 1) += distrib_error; |
| *(err_dist_l0 + col + 1) += distrib_error; |
| *(err_dist_l1 + col - 1) += distrib_error; |
| *(err_dist_l1 + col) += distrib_error; |
| *(err_dist_l2 + col) = distrib_error; |
| } |
| x_offset += update_region_stride; |
| } |
| |
| flush_cache_all(); |
| outer_flush_range(update_region_phys_ptr, update_region_phys_ptr + |
| update_region->height * update_region->width); |
| } |
| |
| /* |
| * Dithering algorithm implementation - Y8->Y4 version 1.0 for i.MX |
| */ |
| |
| static void do_dithering_processing_Y4_v1_0( |
| unsigned char *update_region_virt_ptr, |
| dma_addr_t update_region_phys_ptr, |
| struct mxcfb_rect *update_region, |
| unsigned long update_region_stride, |
| int *err_dist) |
| { |
| |
| /* create a temp error distribution array */ |
| int gcPix; |
| int y; |
| int col; |
| int *err_dist_l0, *err_dist_l1, *err_dist_l2, distrib_error; |
| int width_3 = update_region->width + 3; |
| char *y8buf; |
| int x_offset = 0; |
| |
| /* prime a few elements the error distribution array */ |
| for (y = 0; y < update_region->height; y++) { |
| /* Dithering the Y8 in sbuf to Y4 */ |
| err_dist_l0 = err_dist + (width_3) * (y % 3); |
| err_dist_l1 = err_dist + (width_3) * ((y + 1) % 3); |
| err_dist_l2 = err_dist + (width_3) * ((y + 2) % 3); |
| |
| y8buf = update_region_virt_ptr + x_offset; |
| |
| /* scan the line and convert the Y8 to Y4 */ |
| for (col = 1; col <= update_region->width; col++) { |
| gcPix = *(err_dist_l0 + col) + *y8buf; |
| |
| if (gcPix > 255) |
| gcPix = 255; |
| else if (gcPix < 0) |
| gcPix = 0; |
| |
| distrib_error = (*y8buf - (gcPix & 0xf0)) >> 3; |
| |
| *y8buf++ = gcPix & 0xf0; |
| |
| /* modify the error distribution buffer */ |
| *(err_dist_l0 + col + 2) += distrib_error; |
| *(err_dist_l1 + col + 1) += distrib_error; |
| *(err_dist_l0 + col + 1) += distrib_error; |
| *(err_dist_l1 + col - 1) += distrib_error; |
| *(err_dist_l1 + col) += distrib_error; |
| *(err_dist_l2 + col) = distrib_error; |
| } |
| x_offset += update_region_stride; |
| } |
| |
| flush_cache_all(); |
| outer_flush_range(update_region_phys_ptr, update_region_phys_ptr + |
| update_region->height * update_region->width); |
| } |
| |
| 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 V2 framebuffer driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_SUPPORTED_DEVICE("fb"); |