| /* |
| * linux/drivers/video/pxa95xfb.c |
| */ |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/string.h> |
| #include <linux/interrupt.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/cpufreq.h> |
| #include <linux/platform_device.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/clk.h> |
| #include <linux/err.h> |
| #include <linux/kthread.h> |
| #include <linux/earlysuspend.h> |
| #include <linux/vmalloc.h> |
| |
| #include <mach/pxa3xx-regs.h> |
| #include <mach/regs-ost.h> |
| #include <mach/dvfm.h> |
| |
| #include "pxa95xfb.h" |
| |
| struct pxa95xfb_info * pxa95xfbi[PXA95xFB_FB_NUM]; |
| struct pxa95xfb_conv_info pxa95xfb_conv[4] = { |
| [0] = { |
| .name = "LCD_PARALLEL", |
| .converter = LCD_M2PARALELL_CONVERTER, |
| .on = 0, |
| .inited = 0, |
| .ref_count = 0, |
| }, |
| [1] = { |
| .name = "LCD_DSI0", |
| .converter = LCD_M2DSI0, |
| .on = 0, |
| .inited = 0, |
| .ref_count = 0, |
| }, |
| [2] = { |
| .name = "LCD_DSI1", |
| .converter = LCD_M2DSI1, |
| .on = 0, |
| .inited = 0, |
| .ref_count = 0, |
| }, |
| [3] = { |
| .name = "LCD_HDMI", |
| .converter = LCD_M2HDMI, |
| .on = 0, |
| .inited = 0, |
| .ref_count = 0, |
| }, |
| }; |
| |
| int display_enabled = 0; |
| #ifdef CONFIG_CPU_PXA978 |
| #define MIXER_NUM 3 |
| #else |
| #define MIXER_NUM 2 |
| #endif |
| static wait_queue_head_t wq_mixer_update[MIXER_NUM]; |
| static int b_mixer_update[MIXER_NUM]; |
| static struct mutex mutex_mixer_update[MIXER_NUM]; |
| static wait_queue_head_t g_vsync_wq; |
| |
| #define DSI_CMDLINE_TIMEOUT 0 |
| #define DSI_CMDLINE_CMD 1 |
| #define DSI_CMDLINE_PARM 2 |
| |
| /* cmd line: 0: timeout; 1 type: is loop, | is cmd or data; 2: cmd(or data); 3- parms*/ |
| static u8 init_board [][BOARD_INIT_MAX_DATA_BYTES] = { |
| { 0xFF,LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER,LCD_Controller_DCS_SOFT_RESET}, /*Soft reset*/ |
| { 10,LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER,LCD_Controller_DCS_NOP}, /*Nop*/ |
| /*wait 10ms*/ |
| { 0xFF,LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER,LCD_Controller_DCS_EXIT_SLEEP_MODE}, /* Exit sleep*/ |
| { 20,LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER,LCD_Controller_DCS_NOP}, /*Nop*/ |
| /*wait 20ms*/ |
| { 1,LCD_Controller_DCS_SHORT_WRITE_WITH_PARAMETER,LCD_Controller_DCS_SET_OUTPUT_PIXEL_CLOCK_FREQUENCY,0x52}, /* SET_OUTPUT_CLOCK_FREQUENCY*/ |
| { 1,LCD_Controller_DCS_LONG_WRITE,0x05,0x00,LCD_Controller_DCS_ENABLE_SET_SPECIAL_COMMAND,0x03,0x7F,0x5C,0x33}, /* Enable_Set_Special_command*/ |
| { 1,LCD_Controller_DCS_SHORT_WRITE_WITH_PARAMETER,LCD_Controller_DCS_DISPLAY_BUFFER_IO_CONTROL,0x05}, /*2 THSSI channels and RGB16*/ |
| /* { 1,LCD_Controller_DCS_SHORT_WRITE_WITH_PARAMETER,LCD_Controller_DCS_SET_TEAR_ON,0x00}, //Set TEAR ON */ |
| { 1,LCD_Controller_DCS_LONG_WRITE,0x07,0x00,LCD_Controller_DCS_SET_OUTPUT_VERTICAL_TIMINGS,0x04,0x09,0x09,0x00,0x02,0x80}, /*set_output_vertical_timing*/ |
| { 1,LCD_Controller_DCS_LONG_WRITE,0x07,0x00,LCD_Controller_DCS_SET_OUTPUT_HORIZONTAL_TIMINGS,0x28,0x13,0x3B,0x00,0x01,0xE0}, /*set_output_horizontal_timing*/ |
| /*wait 1ms*/ |
| { 1, LCD_Controller_DCS_LONG_WRITE,0x05,0x00 , LCD_Controller_DCS_WRITE_EDISCO_REGISTER , 0xCC, 0x00, 0x00, 0x0E}, /* MRESET*/ |
| { 1, LCD_Controller_DCS_LONG_WRITE,0x05,0x00 , LCD_Controller_DCS_WRITE_EDISCO_REGISTER , 0xC8, 0x00, 0x72, 0x42}, /* IOCTRL*/ |
| { 1, LCD_Controller_DCS_LONG_WRITE,0x05,0x00 , LCD_Controller_DCS_WRITE_EDISCO_REGISTER , 0x44, 0x00, 0xFF, 0x00}, /* SSITIM1*/ |
| { 1, LCD_Controller_DCS_LONG_WRITE,0x05,0x00 , LCD_Controller_DCS_WRITE_EDISCO_REGISTER , 0x40, 0x14, 0x00, 0x1A}, /* SSICTL*/ |
| /*wait 1ms*/ |
| { 1,LCD_Controller_DCS_SHORT_WRITE_WITH_PARAMETER, LCD_Controller_DCS_GET_PIXEL_FORMAT , 0x05}, /* FIXME: hardcode here: set format*/ |
| { 1,LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER,LCD_Controller_DCS_SET_DISPLAY_ON}, /* Display on*/ |
| { 1,LCD_Controller_DCS_LONG_WRITE,0x05,0x00,LCD_Controller_DCS_WRITE_EDISCO_REGISTER,0x20,0x00,0x00,0x24}, /*LCDCTRL*/ |
| /*wait 1ms*/ |
| {0xFF,0xFF}, |
| }; |
| |
| static u8 enter_sleep [][BOARD_INIT_MAX_DATA_BYTES] = { |
| {0xff,LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER,LCD_Controller_DCS_SET_DISPLAY_OFF}, |
| {0x10,LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER,LCD_Controller_DCS_NOP}, |
| /*wait 10ms*/ |
| {0xff,LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER,LCD_Controller_DCS_ENTER_SLEEP_MODE}, |
| {0x10,LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER,LCD_Controller_DCS_NOP}, |
| /*wait 10ms*/ |
| {0xff,0xff}, |
| }; |
| |
| static int dvfm_dev_idx; |
| static void set_dvfm_constraint(void) |
| { |
| /* Disable Lowpower mode */ |
| dvfm_disable_op_name_no_change("D1", dvfm_dev_idx); |
| dvfm_disable_op_name_no_change("D2", dvfm_dev_idx); |
| if (!cpu_is_pxa978_Dx()) |
| dvfm_disable_op_name_no_change("CG", dvfm_dev_idx); |
| } |
| |
| void unset_dvfm_constraint(void) |
| { |
| /* Enable Lowpower mode */ |
| dvfm_enable_op_name_no_change("D1", dvfm_dev_idx); |
| dvfm_enable_op_name_no_change("D2", dvfm_dev_idx); |
| if (!cpu_is_pxa978_Dx()) |
| dvfm_enable_op_name_no_change("CG", dvfm_dev_idx); |
| } |
| |
| static void dsi_set_time(struct pxa95xfb_conv_info *conv, int freq) |
| { |
| u32 THS_prep_min, THS_prep_max; |
| u32 THS_trail_min, THS_trail_max, THS_zero_min, THS_lpx_min, CL_zero_min; |
| u32 CL_trail_min, THS_exit_min; |
| u8 DSI_HSPREP, DSI_HSTRAIL, DSI_HSZERO, DSI_HSEXIT, DSI_LPX, DSI_CLZERO, DSI_CLTRAIL; |
| |
| void *conv_base = conv->conv_base; |
| |
| THS_prep_min = 20; |
| THS_prep_max = 50; |
| THS_trail_min = 50; |
| THS_trail_max = 75; |
| THS_zero_min = 70; |
| THS_exit_min = 100; |
| THS_lpx_min = 50; |
| CL_zero_min = 150; |
| CL_trail_min = 60; |
| |
| DSI_HSPREP = (THS_prep_min + THS_prep_max-THS_prep_min/2)*52/1000; |
| if (freq > 156) |
| DSI_HSTRAIL = (THS_trail_min + THS_trail_max-THS_trail_min/2)*52/1000; |
| else |
| DSI_HSTRAIL = (THS_trail_min + THS_trail_max-THS_trail_min/2 + freq*8/1000)*52/1000; |
| DSI_HSZERO = (THS_zero_min - freq*3/1000)*52/1000; |
| DSI_HSEXIT = THS_exit_min*52/1000; |
| DSI_LPX = THS_lpx_min*52/1000; |
| DSI_CLZERO = CL_zero_min*52/1000; |
| DSI_CLTRAIL = CL_trail_min*52/1000; |
| |
| /*Set timing configuration according to PXA95x docs*/ |
| |
| /*Clear previous states*/ |
| writel(0, conv_base + LCD_DSI_DxPHY_TIM0_OFFSET); |
| writel(0, conv_base + LCD_DSI_DxPHY_TIM1_OFFSET); |
| writel(0, conv_base + LCD_DSI_DxPHY_TIM2_OFFSET); |
| |
| writel(LCD_DSI_DxPHY_TIM0_HSTRAIL(DSI_HSTRAIL) |
| |LCD_DSI_DxPHY_TIM0_HSZERO(DSI_HSZERO) |
| |LCD_DSI_DxPHY_TIM0_HSPREP(DSI_HSPREP) |
| |LCD_DSI_DxPHY_TIM0_LPX(DSI_LPX), |
| conv_base + LCD_DSI_DxPHY_TIM0_OFFSET); |
| |
| writel(LCD_DSI_DxPHY_TIM1_HSEXIT(DSI_HSEXIT) |
| |LCD_DSI_DxPHY_TIM1_CLTRAIL(DSI_CLTRAIL) |
| |LCD_DSI_DxPHY_TIM1_CLZERO(DSI_CLZERO) |
| |LCD_DSI_DxPHY_TIM1_TAGO(12), |
| conv_base + LCD_DSI_DxPHY_TIM1_OFFSET); |
| |
| writel(LCD_DSI_DxPHY_TIM2_REQ_RDY_DELAY(15) |
| |LCD_DSI_DxPHY_TIM2_TAGET(15) |
| |LCD_DSI_DxPHY_TIM2_WAKEUP(0xFFFF), |
| conv_base + LCD_DSI_DxPHY_TIM2_OFFSET); |
| |
| writel(4, |
| conv_base + LCD_DSI_DxPHY_CAL_OFFSET); |
| |
| } |
| |
| |
| static u8 dsi_enable_disable(struct pxa95xfb_conv_info * conv, int enable_disable) |
| { |
| u8 ret = 1; |
| u32 x; |
| void *conv_base = conv->conv_base; |
| |
| switch(enable_disable) |
| { |
| case DSI_DISABLE: |
| x = readl(conv_base + LCD_DSI_DxSCR1_OFFSET) & |
| (~LCD_DSI_DxSCR1_DSI_EN); |
| writel(x, conv_base + LCD_DSI_DxSCR1_OFFSET); |
| break; |
| case DSI_ENABLE: |
| x = readl(conv_base + LCD_DSI_DxSCR1_OFFSET) |
| |LCD_DSI_DxSCR1_DSI_EN; |
| writel(x, conv_base + LCD_DSI_DxSCR1_OFFSET); |
| break; |
| default: |
| ret = 0; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static u8 dsi_set_lanes_number(struct pxa95xfb_conv_info * conv, int nol) |
| { |
| u8 ret = 1; |
| u32 x; |
| void *conv_base = conv->conv_base; |
| |
| if(nol>LCD_Controller_DSI_4LANE){ |
| /*only 4 lanes available*/ |
| ret = 0; |
| }else{ |
| /*Clear previous state*/ |
| x = readl(conv_base + LCD_DSI_DxSCR1_OFFSET) |
| & (~LCD_DSI_DxSCR1_NOL_MASK); |
| writel(x, conv_base + LCD_DSI_DxSCR1_OFFSET); |
| /*Set number of lanes*/ |
| x |= LCD_DSI_DxSCR1_NOL(nol); |
| writel(x, conv_base + LCD_DSI_DxSCR1_OFFSET); |
| } |
| |
| return ret; |
| } |
| |
| static u8 dsi_set_power_mode(struct pxa95xfb_conv_info * conv, int power_mode) |
| { |
| u8 ret = 1; |
| u8 rihs_mode,ulps_mode,lpdt_mode,bta_mode; |
| u32 x; |
| void *conv_base = conv->conv_base; |
| |
| /* TODO: issue with panel??*/ |
| if(conv->output == OUTPUT_PANEL) |
| rihs_mode = 0; |
| else |
| rihs_mode = power_mode&LCD_Controller_DSI_RIHS; |
| ulps_mode = (power_mode&LCD_Controller_DSI_ULPS)>>1; |
| lpdt_mode = (power_mode&LCD_Controller_DSI_LPDT)>>2; |
| bta_mode = (power_mode&LCD_Controller_DSI_BTA)>>3; |
| |
| /*Clear previous state*/ |
| x = readl(conv_base + LCD_DSI_DxSCR1_OFFSET) |
| & (~LCD_DSI_DxSCR1_POWER_MASK); |
| writel(x, conv_base + LCD_DSI_DxSCR1_OFFSET); |
| |
| /*Set power mode*/ |
| x |= LCD_DSI_DxSCR1_RIHS(rihs_mode)|LCD_DSI_DxSCR1_ULPS(ulps_mode) |
| |LCD_DSI_DxSCR1_LPDT(lpdt_mode)|LCD_DSI_DxSCR1_BTA(bta_mode); |
| writel(x, conv_base + LCD_DSI_DxSCR1_OFFSET); |
| |
| return ret; |
| } |
| |
| static u8 dsi_wait_interrupt(struct pxa95xfb_conv_info * conv, u32 time_to_wait) |
| { |
| u8 ret = 1; |
| u32 x; |
| int i = 0; |
| void *conv_base = conv->conv_base; |
| |
| /*Wait until we received interrupt from the command fifo*/ |
| x = readl(conv_base + LCD_DSI_DxINST0_OFFSET); |
| while ((x & LCD_DSI_DxINST0_SP_GEN_INT_STS) != |
| LCD_DSI_DxINST0_SP_GEN_INT_STS){ |
| i++; |
| msleep(100); |
| x = readl(conv_base + LCD_DSI_DxINST0_OFFSET); |
| if (i>50){ |
| ret = 0; |
| break; |
| } |
| } |
| if (i > 50) |
| printk("%s: failed to receive DSI interrupt!!!\n", __func__); |
| msleep(time_to_wait); |
| writel(x|LCD_DSI_DxINST0_SP_GEN_INT_STS, |
| conv_base + LCD_DSI_DxINST0_OFFSET); |
| return ret; |
| |
| } |
| |
| static inline void dsi_add_cmd(struct pxa95xfb_conv_info * conv, u8 dsi_cmd, u8 dsi_data, int dsi_loop) |
| { |
| conv->dsi_cmd_buf[conv->dsi_cmd_index++] = LCD_DSI_COMMAND_DATA(dsi_data) |
| |LCD_DSI_COMMAND_CMD(dsi_cmd) |
| |LCD_DSI_COMMAND_LOOP(dsi_loop); |
| } |
| |
| static void dsi_send_cmd(struct pxa95xfb_conv_info * conv) |
| { |
| int i; |
| |
| if(conv->dsi_cmd_index%2==1) |
| dsi_add_cmd(conv, LCD_Controller_NO_OPERATION,0,0); |
| |
| /* avoid interrupted when send cmd, time cost is 2-3 us*/ |
| local_irq_disable(); |
| /*send commands to the FIFO to enable frame readings*/ |
| for(i=0;i<conv->dsi_cmd_index-1;i=i+2){ |
| writel(((u32)conv->dsi_cmd_buf[i])|((u32)conv->dsi_cmd_buf[i+1]<<16), |
| conv->conv_base + LCD_DSI_DxCFIF_OFFSET); |
| } |
| local_irq_enable(); |
| conv->dsi_cmd_index = 0; |
| } |
| |
| static u32 dsi_send_cmd_array(struct pxa95xfb_conv_info * conv, u8* cmd_array) |
| { |
| int i, line_index = 0; |
| u8 parm1,parm2; |
| u8 command, timeout; |
| u8 *cmd_line; |
| u16 param_count; |
| |
| conv->dsi_cmd_index=0; |
| |
| /*Get board parameters*/ |
| cmd_line = cmd_array + line_index* BOARD_INIT_MAX_DATA_BYTES; |
| |
| command = cmd_line[DSI_CMDLINE_CMD]; |
| while(command != 0xFF){ |
| parm1 = cmd_line[DSI_CMDLINE_PARM]; |
| parm2 = cmd_line[DSI_CMDLINE_PARM + 1]; |
| timeout = cmd_line[DSI_CMDLINE_TIMEOUT]; |
| |
| switch(command){ |
| case LCD_Controller_DCS_READ_NO_PARAMETER: |
| case LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER: |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, parm1, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| break; |
| case LCD_Controller_DCS_SHORT_WRITE_WITH_PARAMETER: |
| case LCD_Controller_GENERIC_SHORT_WRITE_TWO_PARAMETERS: |
| case LCD_Controller_SET_MAXIMUM_RETURN_PACKET_SIZE: |
| case LCD_Controller_TURN_ON_PERIPHERAL: |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, command, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, parm1, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, parm2, 0); |
| break; |
| case LCD_Controller_DCS_LONG_WRITE: |
| case LCD_Controller_GENERIC_LONG_WRITE: |
| case LCD_Controller_LONG_PACKET_TBD: |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, command, 0); |
| param_count = ((u16)parm1|((u16)parm2<<8)); |
| for(i=0;i<param_count+2;i++) |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE,cmd_line[i+DSI_CMDLINE_PARM],0); |
| break; |
| default: |
| break; |
| } |
| |
| /*Get timeout - if not 0 - add interrupt and exit. if 0xFF - dont add a nop*/ |
| if(!timeout){ |
| dsi_add_cmd(conv, LCD_Controller_NO_OPERATION,10,0); |
| }else if (timeout !=0xFF){ |
| /*Interrupt the proccesor for finishing*/ |
| dsi_add_cmd(conv, LCD_Controller_INTERRUPT_THE_PROCESSOR,0,0); |
| |
| /*Send the commands*/ |
| dsi_send_cmd(conv); |
| |
| /*wait until the processor finish the DSI fifo sequence*/ |
| if(!dsi_wait_interrupt(conv,timeout)){ |
| printk("%s: dsi_wait_interrupt failed at commandline %d!!\n", __func__, line_index); |
| return 0; |
| } |
| |
| conv->dsi_cmd_index = 0; |
| } |
| |
| /*Get the next command*/ |
| line_index ++; |
| cmd_line = cmd_array + line_index* BOARD_INIT_MAX_DATA_BYTES; |
| command = cmd_line[DSI_CMDLINE_CMD]; |
| } |
| |
| /*end with out send? send out cmds*/ |
| if(conv->dsi_cmd_index) |
| dsi_send_cmd(conv); |
| |
| return 1; |
| } |
| |
| static void dsi_frame_update_dcs(struct pxa95xfb_conv_info *conv) |
| { |
| u16 vlines; |
| u16 x ; |
| |
| switch (conv->pix_fmt_out){ |
| case PIX_FMTOUT_16_RGB565: |
| x = (u16)(conv->xres *2 +1); |
| break; |
| case PIX_FMTOUT_24_RGB888: |
| x = (u16)(conv->xres *3 +1); |
| break; |
| default: |
| printk(KERN_ERR "%s: format %d not supported\n", __func__, conv->pix_fmt_out); |
| return; |
| } |
| |
| conv->dsi_cmd_index=0; |
| |
| dsi_add_cmd(conv, LCD_Controller_NO_OPERATION,0x22,0); |
| dsi_add_cmd(conv, LCD_Controller_NO_OPERATION,0x22,0); |
| |
| /*WRITE_MEMORY_START with line from the LCD controller*/ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE_HOLD, LCD_Controller_DCS_LONG_WRITE, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (u8)(x & 0xff), 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (u8)((x & 0xff00) >> 0x8), 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_DCS_WRITE_MEMORY_START, 0); |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE,0,0); |
| |
| /*Invalidate all loop buffer contents*/ |
| dsi_add_cmd(conv, LCD_Controller_FLUSH_LOOP_BUFFER,0,0); |
| |
| /*WRITE_MEMORY_CONTINUE with line from the LCD controller*/ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE_HOLD, LCD_Controller_DCS_LONG_WRITE, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (u8)(x & 0xff), 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (u8)((x & 0xff00) >> 0x8), 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_DCS_WRITE_MEMORY_CONTINUE, 1); |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE,0,1); |
| |
| /*Execute loop according to Vertical resolution */ |
| vlines = conv->yres -2; |
| while(vlines>0x100){ |
| dsi_add_cmd(conv, LCD_Controller_EXECUTE_LOOP_BUFFER,0xff,0); |
| vlines -= 0x100; |
| } |
| dsi_add_cmd(conv, LCD_Controller_EXECUTE_LOOP_BUFFER,(u8)(vlines-1),0); |
| |
| dsi_add_cmd(conv, LCD_Controller_NO_OPERATION,0x22,0); |
| |
| /*Invalidate all loop buffer contents*/ |
| dsi_add_cmd(conv, LCD_Controller_FLUSH_LOOP_BUFFER,0,0); |
| /*Send end of transmition*/ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_END_OF_TRANSMITION, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0xf, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0xf, 0); |
| |
| dsi_send_cmd(conv); |
| } |
| |
| static void dsi_frame_update_packet(struct pxa95xfb_conv_info * conv) |
| { |
| u16 vlines; |
| u16 x = (u16)(conv->xres); |
| |
| conv->dsi_cmd_index=0; |
| |
| dsi_add_cmd(conv, LCD_Controller_NO_OPERATION,0x22,0); |
| dsi_add_cmd(conv, LCD_Controller_NO_OPERATION,0x22,0); |
| |
| /*WRITE_MEMORY_START with line from the LCD controller*/ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE_HOLD, LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_DCS_WRITE_MEMORY_START, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| |
| /*workaround due d-phy SI issue*/ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE_HOLD, LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_DCS_WRITE_MEMORY_START, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| |
| #if 0 |
| /*if such code is not removed, panel would be floating*/ |
| /*RGB packet with loop*/ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, dsi_rgb_mode, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (u8)(x & 0xff), 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (u8)((x & 0xff00) >> 0x8), 0); |
| |
| /*Line data write with loop - send a line from memory*/ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE,0,0); |
| #endif |
| /*Invalidate all loop buffer contents*/ |
| dsi_add_cmd(conv, LCD_Controller_FLUSH_LOOP_BUFFER,0,0); |
| |
| /*WRITE_MEMORY_CONTINUE with line from the LCD controller*/ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE_HOLD, LCD_Controller_DCS_SHORT_WRITE_NO_PARAMETER, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_DCS_WRITE_MEMORY_CONTINUE, 1); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, 0, 1); |
| /*RGB packet with loop*/ |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, LCD_Controller_RGB_565_PACKET, 1); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, (u8)(x & 0xff), 1); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, (u8)((x & 0xff00) >> 0x8), 1); |
| /*Line data write with loop - send a line from memory*/ |
| dsi_add_cmd(conv,LCD_Controller_LINE_DATA_WRITE,0,1); |
| |
| /*Execute loop according to Vertical resolution */ |
| vlines = conv->yres -1; |
| while(vlines>0x100){ |
| dsi_add_cmd(conv,LCD_Controller_EXECUTE_LOOP_BUFFER,0xff,0); |
| vlines -= 0x100; |
| } |
| dsi_add_cmd(conv,LCD_Controller_EXECUTE_LOOP_BUFFER,(u8)(vlines-1),0); |
| |
| dsi_add_cmd(conv,LCD_Controller_NO_OPERATION,0x22,0); |
| |
| /*Invalidate all loop buffer contents*/ |
| dsi_add_cmd(conv,LCD_Controller_FLUSH_LOOP_BUFFER,0,0); |
| /*Send end of transmition*/ |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, LCD_Controller_END_OF_TRANSMITION, 0); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, 0xf, 0); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, 0xf, 0); |
| |
| dsi_send_cmd(conv); |
| |
| } |
| |
| static void dsi_init_video_burst(struct pxa95xfb_conv_info * conv) |
| { |
| u16 vlines; |
| u16 x = (u16)(conv->xres); |
| |
| conv->dsi_cmd_index=0; |
| |
| dsi_add_cmd(conv,LCD_Controller_START_LABEL,0,0); |
| |
| /*Set delay for Nop - for line*/ |
| dsi_add_cmd(conv,LCD_Controller_SET_DLY_MULT,20,0); |
| |
| /*vsync start*/ |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, LCD_Controller_VSYNC_START, 0); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, 0, 0); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, 0, 0); |
| |
| /*vsync end*/ |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, LCD_Controller_VSYNC_END, 0); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, 0, 0); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, 0, 0); |
| |
| /*Invalidate all loop buffer contents*/ |
| dsi_add_cmd(conv,LCD_Controller_FLUSH_LOOP_BUFFER,0,0); |
| |
| /*hsync start*/ |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, LCD_Controller_HSYNC_START, 1); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, 0, 1); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /*hsync end*/ |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, LCD_Controller_HSYNC_END, 1); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, 0, 1); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /*write RGB packet*/ |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, conv->dsi_rgb_mode, 1); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, (u8)(x & 0xff), 1); |
| dsi_add_cmd(conv,LCD_Controller_COMMAND_WRITE, (u8)((x & 0xff00) >> 0x8), 1); |
| /*Line data write - send a line from memory*/ |
| dsi_add_cmd(conv,LCD_Controller_LINE_DATA_WRITE,0,1); |
| |
| /*Execute loop according to Vertical resolution */ |
| vlines = conv->yres -1; |
| while(vlines>0x100){ |
| dsi_add_cmd(conv,LCD_Controller_EXECUTE_LOOP_BUFFER,0xff,0); |
| vlines -= 0x100; |
| } |
| dsi_add_cmd(conv,LCD_Controller_EXECUTE_LOOP_BUFFER,(u8)(vlines-1),0); |
| |
| /*Invalidate all loop buffer contents*/ |
| dsi_add_cmd(conv,LCD_Controller_FLUSH_LOOP_BUFFER,0,0); |
| |
| /*go to start*/ |
| dsi_add_cmd(conv,LCD_Controller_GOTO_START, 0, 0); |
| |
| dsi_send_cmd(conv); |
| } |
| |
| static void dsi_init_video_non_burst(struct pxa95xfb_conv_info * conv) |
| { |
| u16 vlines; |
| u8 hsync_blank = conv->hsync_len; |
| int active_hsw_compensation = 4,active_blw_compensation = 4, active_elw_compensation = 4, blank_pixel_compensation = 8; |
| u32 blank_per_line_no_hsw = conv->left_margin + conv->xres + conv->right_margin - blank_pixel_compensation; |
| |
| conv->dsi_cmd_index=0; |
| |
| /* Begin Subroutine */ |
| dsi_add_cmd(conv, LCD_Controller_START_LABEL, 0, 0); |
| |
| /*Assert VSYNC*/ |
| /* VSYNC start */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_VSYNC_START, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| |
| /* HSW delay */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_BLANKING_PACKET, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, hsync_blank, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 0); |
| |
| /* HSYNC end */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_HSYNC_END, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| |
| /* Rest of the line delay */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_BLANKING_PACKET, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, blank_per_line_no_hsw & 0xFF, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (blank_per_line_no_hsw & 0xFF00)>>8, 0); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 0); |
| |
| /* Add DSI phy compensation for the next line will start from byte 0 */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_SET_MAXIMUM_RETURN_PACKET_SIZE, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 1, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| |
| /* Invalidate all loop buffer contents */ |
| dsi_add_cmd(conv, LCD_Controller_FLUSH_LOOP_BUFFER, 0, 0); |
| |
| /* HSYNC start */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_HSYNC_START, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* HSW delay */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_BLANKING_PACKET, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, hsync_blank, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 1); |
| |
| /* HSYNC end */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_HSYNC_END, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* Rest of the line delay */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_BLANKING_PACKET, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, blank_per_line_no_hsw & 0xFF, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (blank_per_line_no_hsw & 0xFF00)>>8, 1); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 1); |
| |
| /* Add DSI phy compensation for the next line will start from byte 0 */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_SET_MAXIMUM_RETURN_PACKET_SIZE, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 1, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* loop according to VSW */ |
| if(conv->vsync_len > 2) |
| dsi_add_cmd(conv, LCD_Controller_EXECUTE_LOOP_BUFFER, conv->vsync_len - 3, 0); |
| |
| /* Invalidate all loop buffer contents */ |
| dsi_add_cmd(conv, LCD_Controller_FLUSH_LOOP_BUFFER, 0, 0); |
| |
| /* VSYNC END */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_VSYNC_END, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| |
| /* HSW delay */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_BLANKING_PACKET, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, hsync_blank, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 0); |
| |
| /* HSYNC end */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_HSYNC_END, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| |
| /* Rest of the line delay */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_BLANKING_PACKET, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, blank_per_line_no_hsw & 0xFF, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (blank_per_line_no_hsw & 0xFF00)>>8, 0); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 0); |
| |
| /* Add DSI phy compensation for the next line will start from byte 0 */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_SET_MAXIMUM_RETURN_PACKET_SIZE, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 1, 0); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 0); |
| |
| /*Frame back porch*/ |
| /* HSYNC start */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_HSYNC_START, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* HSW delay */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_BLANKING_PACKET, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, hsync_blank, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 1); |
| |
| /* HSYNC end */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_HSYNC_END, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* Rest of the line delay */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_BLANKING_PACKET, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, blank_per_line_no_hsw & 0xFF, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (blank_per_line_no_hsw & 0xFF00)>>8, 1); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 1); |
| |
| /* Add DSI phy compensation for the next line will start from byte 0 */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_SET_MAXIMUM_RETURN_PACKET_SIZE, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 1, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* loop according to BFW */ |
| if(conv->upper_margin > 2) |
| dsi_add_cmd(conv, LCD_Controller_EXECUTE_LOOP_BUFFER, conv->upper_margin - 3, 0); |
| |
| /* Invalidate all loop buffer contents */ |
| dsi_add_cmd(conv, LCD_Controller_FLUSH_LOOP_BUFFER, 0, 0); |
| |
| /*Output actual frame*/ |
| /* HSYNC start */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_HSYNC_START, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* HSW delay */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_BLANKING_PACKET, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, hsync_blank - active_hsw_compensation, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 1); |
| |
| /* HSYNC end */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_HSYNC_END, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* Add DSI phy compensation for the next line will start from byte 0 */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_SET_MAXIMUM_RETURN_PACKET_SIZE, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 1, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* HBP delay */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_BLANKING_PACKET, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, conv->left_margin - active_blw_compensation, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 1); |
| |
| /* Send Line */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, conv->dsi_rgb_mode, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (u8)(conv->xres & 0xff), 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (u8)((conv->xres & 0xff00) >> 0x8), 1); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 1); |
| |
| /* HFP delay */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_BLANKING_PACKET, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (conv->right_margin - active_elw_compensation) & 0xFF, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, ((conv->right_margin - active_elw_compensation) & 0xFF00) >> 8, 1); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 1); |
| |
| /* Execute loop according to Vertical resolution - 1 (above) */ |
| vlines = conv->yres - 1; |
| while(vlines > 0x100) { |
| dsi_add_cmd(conv, LCD_Controller_EXECUTE_LOOP_BUFFER, 0xFF, 0); |
| vlines -= 0x100; |
| } |
| dsi_add_cmd(conv, LCD_Controller_EXECUTE_LOOP_BUFFER, (u8)(vlines - 1), 0); |
| |
| /* Invalidate all loop buffer contents */ |
| dsi_add_cmd(conv, LCD_Controller_FLUSH_LOOP_BUFFER, 0, 0); |
| |
| /*Frame front porch*/ |
| /* HSYNC start */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_HSYNC_START, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* HSW delay */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_BLANKING_PACKET, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, hsync_blank, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 1); |
| |
| /* HSYNC end */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_HSYNC_END, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* Rest of the line delay */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_BLANKING_PACKET, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, blank_per_line_no_hsw & 0xFF, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, (blank_per_line_no_hsw & 0xFF00)>>8, 1); |
| |
| /* Line data write - send a blank data from memory */ |
| dsi_add_cmd(conv, LCD_Controller_LINE_DATA_WRITE, 0, 1); |
| |
| /* Add DSI phy compensation for the next line will start from byte 0 */ |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, LCD_Controller_SET_MAXIMUM_RETURN_PACKET_SIZE, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 1, 1); |
| dsi_add_cmd(conv, LCD_Controller_COMMAND_WRITE, 0, 1); |
| |
| /* loop according to EFW */ |
| if(conv->lower_margin > 1) |
| dsi_add_cmd(conv, LCD_Controller_EXECUTE_LOOP_BUFFER, conv->lower_margin - 2, 0); |
| |
| /* Invalidate all loop buffer contents */ |
| dsi_add_cmd(conv, LCD_Controller_FLUSH_LOOP_BUFFER, 0, 0); |
| |
| /*END - go to start for a new frame*/ |
| dsi_add_cmd(conv, LCD_Controller_GOTO_START, 0, 0); |
| |
| dsi_send_cmd(conv); |
| } |
| |
| static void dsi_send_frame(struct pxa95xfb_conv_info * conv) |
| { |
| u32 x; |
| if(CONVERTER_IS_DSI(conv->converter)){ |
| x = readl(conv->conv_base + LCD_DSI_DxSSR_OFFSET); |
| while((LCD_DSI_DxSSR_STOP_ST | LCD_DSI_DxSSR_STOP_ST_ALL) != |
| (x & (LCD_DSI_DxSSR_STOP_ST | LCD_DSI_DxSSR_STOP_ST_ALL))){ |
| msleep(1); |
| x = readl(conv->conv_base + LCD_DSI_DxSSR_OFFSET); |
| } |
| if (conv->conf_dsi_video_mode == DSI_MODE_COMMAND_DCS) |
| dsi_frame_update_dcs(conv); |
| if (conv->conf_dsi_video_mode == DSI_MODE_COMMAND_PACKET) |
| dsi_frame_update_packet(conv); |
| } |
| } |
| |
| static int loop_kthread_body(void *arg) |
| { |
| struct loop_kthread * thread = arg; |
| int save_status = thread->is_run; |
| int timeout = save_status ? thread->interval: MAX_SCHEDULE_TIMEOUT; |
| u32 s=0, c=0, t; |
| while(1) { |
| if(wait_event_interruptible_timeout(thread->wq_main, (save_status != thread->is_run), timeout)) { |
| mutex_lock(&thread->mutex); |
| save_status = thread->is_run; |
| timeout = save_status ? thread->interval: MAX_SCHEDULE_TIMEOUT; |
| if(!save_status) |
| complete(&thread->stopped); |
| mutex_unlock(&thread->mutex); |
| } |
| if (save_status) |
| thread->op(thread->par); |
| |
| if(c) |
| s += (OSCR - t)*4 /13000; |
| c++; |
| t = OSCR; |
| /*if(c %100 == 0) |
| printk("%s: count %d, average dur %d\n", __func__, c, s/c);*/ |
| } |
| return 0; |
| } |
| |
| static int loop_kthread_init(struct loop_kthread * thread, const char * name, void *par, void *op, int interval) |
| { |
| thread->is_run = 0; |
| thread->op = op; |
| thread->interval = interval; |
| thread->par = par; |
| init_waitqueue_head(&thread->wq_main); |
| mutex_init(&thread->mutex); |
| init_completion(&thread->stopped); |
| |
| thread->thread = kthread_run(loop_kthread_body, (void *)thread, name); |
| if (IS_ERR(thread->thread)) { |
| pr_err("%s: unable to create kernel thread %s\n", __func__, name); |
| return 0; |
| } |
| return 1; |
| } |
| |
| static void loop_kthread_resume(struct loop_kthread * thread) |
| { |
| mutex_lock(&thread->mutex); |
| thread->is_run = 1; |
| wake_up(&thread->wq_main); |
| mutex_unlock(&thread->mutex); |
| } |
| |
| static void loop_kthread_pause(struct loop_kthread * thread) |
| { |
| mutex_lock(&thread->mutex); |
| thread->is_run = 0; |
| wake_up(&thread->wq_main); |
| mutex_unlock(&thread->mutex); |
| wait_for_completion(&thread->stopped); |
| } |
| |
| static void converter_set_gamma(struct pxa95xfb_conv_info *conv) |
| { |
| int i, x; |
| u32 RedLinearLUT[] = {0x30201000, 0x70605040, 0xb0a09080, 0xf0e0d0c0, 0x000000ff}; |
| u32 GreenLinearLUT[] = {0x30201000, 0x70605040, 0xb0a09080, 0xf0e0d0c0, 0x000000ff}; |
| u32 BlueLinearLUT[] = {0x30201000, 0x70605040, 0xb0a09080, 0xf0e0d0c0, 0x000000ff}; |
| void *conv_base = conv->conv_base; |
| |
| for(i=0;i<5;i++){ |
| writel(LCD_CONVx_G_CTL_ADD_PTR_R(i) |
| | LCD_CONVx_G_CTL_ADD_PTR_G(i) |
| | LCD_CONVx_G_CTL_ADD_PTR_B(i), |
| conv_base + LCD_DSI_Dx_G_CTL_OFFSET); |
| writel(RedLinearLUT[i], |
| conv_base + LCD_DSI_Dx_G_DAT_RED_OFFSET); |
| writel(GreenLinearLUT[i], |
| conv_base + LCD_DSI_Dx_G_DAT_GREEN_OFFSET); |
| writel(BlueLinearLUT[i], |
| conv_base + LCD_DSI_Dx_G_DAT_BLUE_OFFSET); |
| } |
| x = readl(conv_base + LCD_DSI_Dx_G_CTL_OFFSET); |
| x |= LCD_CONVx_G_CTL_Q4_C(0)|LCD_CONVx_G_CTL_Q3_C(0) |
| |LCD_CONVx_G_CTL_Q2_C(0)|LCD_CONVx_G_CTL_Q1_C(0); |
| writel(x, conv_base + LCD_DSI_Dx_G_CTL_OFFSET); |
| } |
| |
| static void set_clock_divider(struct pxa95xfb_info *fbi) |
| { |
| int divider_int; |
| int needed_pixclk; |
| int lcd_src_clk; |
| u64 div_result; |
| u32 x = 0; |
| struct fb_videomode * m = &fbi->mode; |
| |
| /* |
| * Calc divider according to refresh rate. |
| */ |
| div_result = 1000000000000ll; |
| do_div(div_result, m->pixclock); |
| needed_pixclk = (u32)div_result; |
| |
| x = ACCR1 & (0x1<<9); |
| if(x) |
| lcd_src_clk = 120000000ll; |
| else |
| lcd_src_clk = 208000000ll; |
| |
| divider_int = lcd_src_clk / needed_pixclk; |
| |
| /* check whether divisor is too small. */ |
| if (divider_int < 2) { |
| printk(KERN_WARNING "Warning: clock source is too slow." |
| "Try smaller resolution\n"); |
| divider_int = 2; |
| } |
| |
| /* Set setting to reg. */ |
| x = readl(fbi->reg_base + LCD_CTL); |
| x &= 0xfffffe00; |
| x |= divider_int; |
| writel(x, fbi->reg_base + LCD_CTL); |
| |
| } |
| |
| static void converter_set_parallel(struct pxa95xfb_info *fbi) |
| { |
| u32 x; |
| struct pxa95xfb_conv_info *conv = &pxa95xfb_conv[fbi->converter - 1]; |
| struct fb_videomode * m = &fbi->mode; |
| |
| /* gamma is in converter set*/ |
| converter_set_gamma(conv); |
| if (conv->output == OUTPUT_HDMI) { |
| /*Hezi - Temp W/A for fixing HDMI Color issue*/ |
| x = LCD_MIXERx_TIM0_HSW(m->hsync_len) |
| |LCD_MIXERx_TIM0_ELW(m->left_margin) |
| |LCD_MIXERx_TIM0_BLW(m->right_margin); |
| }else{ |
| x = ((m->sync & FB_SYNC_VERT_HIGH_ACT) ? 0 : (1 << 2)) |
| |((m->sync & FB_SYNC_HOR_HIGH_ACT) ? 0 : (1 << 3)) |
| |(1 << 6) /* VSYNC delay to remove the bottom white line */ |
| |(conv->invert_pixclock ? 1 : 0) |
| |LCD_MIXERx_TIM0_HSW(m->hsync_len) |
| |LCD_MIXERx_TIM0_ELW(m->left_margin) |
| |LCD_MIXERx_TIM0_BLW(m->right_margin); |
| } |
| writel(x, fbi->reg_base + LCD_MIXER0_TIM0); |
| |
| x = LCD_MIXERx_TIM1_VSW(m->vsync_len) |
| |LCD_MIXERx_TIM1_EFW(m->upper_margin) |
| |LCD_MIXERx_TIM1_BFW(m->lower_margin); |
| writel(x,fbi->reg_base + LCD_MIXER0_TIM1); |
| |
| /* set converter registers */ |
| x = LCD_CONVx_CTL_TV_FOR |
| |LCD_CONVx_CTL_OP_FOR(conv->pix_fmt_out) |
| |LCD_CONVx_CTL_DISP_TYPE(conv->panel_type) |
| |LCD_CONVx_CTL_DISP_EOF_INT_EN; |
| writel(x, fbi->reg_base + LCD_CONV0_CTL); |
| } |
| |
| static void converter_set_dsi(struct pxa95xfb_conv_info *conv) |
| { |
| void *conv_base = conv->conv_base; |
| u32 x, format = 0; |
| |
| /*Set Gamma correction*/ |
| converter_set_gamma(conv); |
| |
| /*Set DSI timings according to the frequency*/ |
| clk_enable(conv->clk); |
| clk_set_rate(conv->clk, conv->dsi_clock_val * 1000000); |
| dsi_set_time(conv, conv->dsi_clock_val); |
| |
| /*Enable the DSI converter with primary channel*/ |
| writel( |
| LCD_DSI_DxINEN_LP_CONT_EN |
| |LCD_DSI_DxINEN_PORT_ERR_EN |
| |LCD_DSI_DxINEN_TIMEOUT_EN |
| |LCD_DSI_DxINEN_ACK_ERR_EN |
| |LCD_DSI_DxINEN_PHY_ERR_EN |
| ,conv_base + LCD_DSI_DxINEN_OFFSET); |
| |
| writel( |
| LCD_DSI_DxCONV_FIFO_THRESH_PIXEL_FIFO_THRESH(400) |
| |LCD_DSI_DxCONV_FIFO_THRESH_PIXEL_FIFO_SIZE(1280) |
| ,conv_base + LCD_DSI_DxCONV_FIFO_THRESH_OFFSET); |
| |
| writel( |
| LCD_DSI_DxCONV_FIFO_THRESH_INT_THRESH_INT_EN |
| ,conv_base + LCD_DSI_DxCONV_FIFO_THRESH_INT_OFFSET); |
| |
| switch (conv->pix_fmt_out){ |
| case PIX_FMTOUT_16_RGB565: |
| format = 0; |
| conv->dsi_rgb_mode = LCD_Controller_RGB_565_PACKET; |
| break; |
| case PIX_FMTOUT_24_RGB888: |
| format = 3; |
| conv->dsi_rgb_mode = LCD_Controller_RGB_888_PACKET; |
| break; |
| default: |
| printk(KERN_ERR "%s: format %d not supported\n", __func__, conv->pix_fmt_out); |
| } |
| |
| x = readl(conv_base + LCD_DSI_DxSCR1_OFFSET); |
| x &= (~LCD_DSI_DxSCR1_BLANK_NULL_FRMT_MASK); |
| x |= LCD_DSI_DxSCR1_BLANK_NULL_FRMT(format);/*24bpp*/ |
| writel(x, conv_base + LCD_DSI_DxSCR1_OFFSET); |
| |
| writel(DxCONV_GEN_NULL_BLANK_NULL_BLANK_DATA(0) |
| |DxCONV_GEN_NULL_BLANK_GEN_PXL_FORMAT(format) |
| |DxCONV_GEN_NULL_BLANK_GEN_PXL_FORMAT2(format) |
| |DxCONV_GEN_NULL_BLANK_GEN_PXL_FORMAT3(format), |
| conv_base + LCD_DSI_DxCONV_GEN_NULL_BLANK_OFFSET); |
| |
| x = LCD_DSI_DxSCR0_PRIM_VC |
| |LCD_DSI_DxSCR0_FLOW_DIS |
| |LCD_DSI_DxSCR0_CONV_EN |
| |LCD_CONVx_CTL_DISP_EOF_INT_EN; |
| if (!cpu_is_pxa978_Dx()) |
| x |= LCD_DSI_DxSCR0_DISP_URUN_INT_EN; |
| writel(x, conv_base + LCD_DSI_DxSCR0_OFFSET); |
| |
| /*Send DSI commands and enable dsi*/ |
| /*Set default values - Low Power and 2 lanes*/ |
| dsi_set_lanes_number(conv, conv->dsi_lanes); |
| dsi_set_power_mode(conv, LCD_Controller_DSI_LPDT); |
| |
| /*Enable the DSI*/ |
| if(!dsi_enable_disable(conv, DSI_ENABLE)) |
| printk("%s: dsi_enable_disable failed!!\n", __func__); |
| } |
| |
| |
| static void converter_stop_dsi(struct pxa95xfb_conv_info *conv) |
| { |
| u32 x; |
| /*for video mode, we need to disable FIFO*/ |
| if (DSI_IS_VIDEO_MODE(conv->conf_dsi_video_mode)) { |
| x = readl(conv->conv_base + LCD_DSI_DxSCR0_OFFSET); |
| writel(x | LCD_DSI_DxSCR0_SP_BREAK_INT_EN | LCD_DSI_DxSCR0_BREAK, |
| conv->conv_base + LCD_DSI_DxSCR0_OFFSET); |
| /* TODO: checking DxINST0 would always fail |
| * and only SP_UNDRUN_INT_STS is set, just do wait now*/ |
| msleep(50); |
| pr_info("%s: disable fifo when video mode done, DxINST0 = %x!!!\n", |
| __func__, readl(conv->conv_base + LCD_DSI_DxINST0_OFFSET)); |
| writel(LCD_DSI_DxINST0_BREAK_INT_STS, |
| conv->conv_base + LCD_DSI_DxINST0_OFFSET); |
| } else |
| loop_kthread_pause(&conv->thread); |
| |
| msleep(10); |
| |
| dsi_set_power_mode(conv, LCD_Controller_DSI_LPDT); |
| |
| /* will not send dsi sleep cmds now: |
| * currently as mixer off may failed when dsi is stopped, |
| * we do mixer off before dsi stop. however, when mixer is off |
| * send dsi command might be failed. So not send dsi sleep commands |
| * as we will finally reset dsi output device to save power |
| msleep(10); |
| if (conv->dsi_sleep_cmds && !dsi_send_cmd_array(conv, conv->dsi_sleep_cmds)) |
| printk("%s: dsi_send_enter_sleep failed!!\n", __func__); |
| */ |
| /*Enable the DSI*/ |
| if(!dsi_enable_disable(conv, DSI_DISABLE)) |
| printk("%s: dsi_enable_disable failed!!\n", __func__); |
| |
| return; |
| } |
| |
| |
| static void converter_start_dsi(struct pxa95xfb_conv_info *conv) |
| { |
| /*End of inialization - init board*/ |
| printk("%s: Init_board\n", __func__); |
| /*Handle board - only support this currently */ |
| if (conv->dsi_init_cmds && !dsi_send_cmd_array(conv, conv->dsi_init_cmds)){ |
| printk("%s: dsi_init_board failed!!\n", __func__); |
| return; |
| } |
| |
| /*Set power mode*/ |
| dsi_set_power_mode(conv, LCD_Controller_DSI_RIHS); |
| |
| if (conv->conf_dsi_video_mode == DSI_MODE_VIDEO_BURST) |
| dsi_init_video_burst(conv); |
| else if (conv->conf_dsi_video_mode == DSI_MODE_VIDEO_NON_BURST) |
| dsi_init_video_non_burst(conv); |
| else |
| loop_kthread_resume(&conv->thread); |
| } |
| |
| static void converter_set_hdmi(struct pxa95xfb_info *fbi) |
| { |
| struct pxa95xfb_conv_info *conv = &pxa95xfb_conv[fbi->converter - 1]; |
| struct fb_videomode *m = &fbi->mode; |
| |
| void *conv_base = conv->conv_base; |
| u32 x, hdmi_format = 4; |
| |
| /*Set Gamma correction*/ |
| converter_set_gamma(conv); |
| |
| x = readl(conv_base + HDMI_MIXER_TIM0); |
| if ((hdmi_format == 4) |
| || (hdmi_format == 5) |
| || (hdmi_format == 16) |
| || (hdmi_format == 19) |
| || (hdmi_format == 20)) |
| x &= ~(HDMI_MIXERx_TIM0_HSP|HDMI_MIXERx_TIM0_VSP); |
| else |
| x |= HDMI_MIXERx_TIM0_HSP|HDMI_MIXERx_TIM0_VSP; |
| |
| x |= HDMI_MIXERx_TIM0_PCP | HDMI_MIXERx_TIM0_VSYNC_DEL; |
| writel(x, conv_base + HDMI_MIXER_TIM0); |
| x = HDMI_MIXERx_TIM2_BLW(m->left_margin - 1) |
| |HDMI_MIXERx_TIM2_ELW(m->right_margin - 1); |
| writel(x, conv_base + HDMI_MIXER_TIM2); |
| |
| x = HDMI_MIXERx_TIM3_HSW(m->hsync_len - 1) |
| |HDMI_MIXERx_TIM3_BFW(m->upper_margin); |
| writel(x, conv_base + HDMI_MIXER_TIM3); |
| |
| x = HDMI_MIXERx_TIM4_EFW(m->lower_margin) |
| |HDMI_MIXERx_TIM4_VSW(m->vsync_len - 1); |
| writel(x, conv_base + HDMI_MIXER_TIM4); |
| |
| /* Enable the converter */ |
| x = HDMI_CONVx_CTL_WRITE_INTERLACER_REG_EN |
| |HDMI_CONVx_CTL_OP_FOR(conv->pix_fmt_out) |
| |HDMI_CONVx_CTL_DISP_TYPE(conv->panel_type); |
| writel(x, conv_base + HDMI_CONV_CTL); |
| |
| if (cpu_is_pxa978_Dx()) { |
| /* Set clock dividers */ |
| writel(0x20085, conv_base + HDMI_CLK_DIV); |
| writel(0x20085, conv_base + HDMI_PCLK_DIV); |
| writel(0x20085, conv_base + HDMI_TCLK_DIV); |
| writel(0x20085, conv_base + HDMI_PRCLK_DIV); |
| writel(0x7ff00000, conv_base + HDMI_CONV_FIFO);/* fifo set to max 2047*/ |
| } else { |
| writel(0x5, conv_base + HDMI_CLK_DIV); |
| writel(0x5, conv_base + HDMI_PCLK_DIV); |
| writel(0x5, conv_base + HDMI_TCLK_DIV); |
| writel(0x5, conv_base + HDMI_PRCLK_DIV); |
| } |
| } |
| |
| static void hdmi_set_SSP_CLK(struct pxa95xfb_info *fbi, int on) |
| { |
| u32 x = readl(fbi->reg_base + LCD_CTL); |
| if (on) |
| x |= LCD_CTL_CLK_SSP_EN; |
| else |
| x &= ~LCD_CTL_CLK_SSP_EN; |
| writel(x, fbi->reg_base + LCD_CTL); |
| } |
| |
| |
| static void parallel_enable_pix_CLK(struct pxa95xfb_info *fbi, int on) |
| { |
| u32 x = readl(fbi->reg_base + LCD_CTL); |
| if (on) |
| x |= LCD_CTL_LCD_PCLK_EN_BO; |
| else |
| x &= ~LCD_CTL_LCD_PCLK_EN_BO; |
| writel(x, fbi->reg_base + LCD_CTL); |
| } |
| |
| static void converter_mode_set(struct pxa95xfb_conv_info *conv, struct fb_videomode *mode) |
| { |
| conv->xres = mode->xres; |
| conv->yres = mode->yres; |
| conv->left_margin = mode->left_margin; |
| conv->right_margin = mode->right_margin; |
| conv->upper_margin = mode->upper_margin; |
| conv->lower_margin = mode->lower_margin; |
| conv->hsync_len = mode->hsync_len; |
| conv->vsync_len = mode->vsync_len; |
| } |
| |
| void update_lcd_controller_clock(int on) |
| { |
| static int enabled; |
| unsigned int clock_rate; |
| struct pxa95xfb_conv_info *panel_conv = NULL; |
| if (enabled == on) |
| return; |
| if (!cpu_is_pxa978() || !pxa95xfbi[0]) |
| return; |
| if (on) |
| clock_rate = 416000000; |
| else |
| clock_rate = 104000000; |
| /* WORKAROUND here: find some safe way to update clock: disable panel converters???? */ |
| panel_conv = &fb2conv(pxa95xfbi[0]); |
| enabled = on; |
| |
| if (!panel_conv || panel_conv->output != OUTPUT_PANEL || !panel_conv->on) { |
| printk(KERN_INFO "directly update clock to %d\n", clock_rate); |
| clk_set_rate(pxa95xfbi[0]->clk_lcd, clock_rate); |
| return; |
| } |
| /*WR: disable converter of panel then do update*/ |
| converter_onoff(pxa95xfbi[0], 0); |
| clk_set_rate(pxa95xfbi[0]->clk_lcd, clock_rate); |
| converter_onoff(pxa95xfbi[0], 1); |
| printk(KERN_INFO "update lcd clock to %d\n", clock_rate); |
| } |
| |
| EXPORT_SYMBOL(update_lcd_controller_clock); |
| |
| static void mixer_onoff(struct pxa95xfb_info *fbi, int on); |
| void converter_onoff(struct pxa95xfb_info *fbi, int on) |
| { |
| struct pxa95xfb_conv_info *conv = &fb2conv(fbi); |
| if(conv->on == on) { |
| printk(KERN_INFO "converter %s is already %s\n", conv->name, conv->on?"On":"Off"); |
| } else if(on) { |
| if(conv->reset) |
| conv->reset(); |
| if(conv->power) |
| conv->power(1); |
| converter_mode_set(conv, &fbi->mode); |
| if (CONVERTER_IS_DSI(conv->converter)) |
| converter_set_dsi(conv); |
| else if (LCD_M2PARALELL_CONVERTER == conv->converter) { |
| converter_set_parallel(fbi); |
| /* set/enable pixel clock */ |
| set_clock_divider(fbi); |
| parallel_enable_pix_CLK(fbi, 1); |
| } else if (LCD_M2HDMI == conv->converter) { |
| converter_set_hdmi(fbi); |
| hdmi_set_SSP_CLK(fbi, 1); |
| } |
| |
| /* enable mixer to do real conv enable! */ |
| mixer_onoff(fbi, 1); |
| |
| if (CONVERTER_IS_DSI(conv->converter)) |
| converter_start_dsi(conv); |
| |
| conv->on = on; |
| printk(KERN_INFO "converter %s is On\n", conv->name); |
| }else{ |
| conv->on = on; |
| /* disable mixer to do real conv disable! */ |
| mixer_onoff(fbi, 0); |
| |
| if (CONVERTER_IS_DSI(conv->converter)) |
| converter_stop_dsi(conv); |
| else if (LCD_M2HDMI == conv->converter) |
| hdmi_set_SSP_CLK(fbi, 0); |
| else if (LCD_M2PARALELL_CONVERTER == conv->converter) |
| parallel_enable_pix_CLK(fbi, 0); |
| |
| if (conv->clk) |
| clk_disable(conv->clk); |
| if(conv->power) |
| conv->power(0); |
| |
| /* reset it to save 1mA more */ |
| if(conv->reset) |
| conv->reset(); |
| printk(KERN_INFO "converter %s is Off\n", conv->name); |
| } |
| } |
| |
| /* use dynamic converter unmask/mask eof to reduce converter interrupts: |
| * converter mask eof will be always called when intr |
| * converter unmask eof will be always called when wait_vsync |
| */ |
| static void converter_mask_eof(struct pxa95xfb_conv_info *conv) |
| { |
| u32 x; |
| /* for MG platform, a SI workaround is required to do freq change in converter eof |
| * so converter irq could not be masked in MG |
| * for HDMI converter, as no converter irq exported and fetch irq is used, so not do that |
| */ |
| if (!cpu_is_pxa978() || conv->converter == LCD_M2HDMI) |
| return; |
| |
| conv->irq_pending = (conv->irq_pending > 0) ? (conv->irq_pending - 1) : 0; |
| |
| if (conv->irq_pending == 0) { |
| x = readl(conv->conv_base + LCD_DSI_DxSCR0_OFFSET); |
| x &= ~LCD_CONVx_CTL_DISP_EOF_INT_EN; |
| writel(x, conv->conv_base + LCD_DSI_DxSCR0_OFFSET); |
| } |
| printk(KERN_DEBUG "%s: mask eof: pending irq %d\n", |
| conv->name, conv->irq_pending); |
| } |
| |
| static void converter_unmask_eof(struct pxa95xfb_conv_info *conv) |
| { |
| u32 x; |
| if (!cpu_is_pxa978() || conv->converter == LCD_M2HDMI) |
| return; |
| |
| /* although there's possibility be inserted by mask_eof, spin_lock is not added here: |
| * 1: set irq_pending before do real unmask to avoid case: |
| * irq_pending is set to non-zero but irq is masked, |
| * so it would never be unmasked again |
| * 2: set irq_pending to 2 so that for 60fps cases, irq would not be really masked/unmasked |
| */ |
| if (!conv->irq_pending) { |
| conv->irq_pending = 2; |
| x = readl(conv->conv_base + LCD_DSI_DxINST0_OFFSET); |
| writel(x, conv->conv_base + LCD_DSI_DxINST0_OFFSET); |
| x = readl(conv->conv_base + LCD_DSI_DxSCR0_OFFSET); |
| x |= LCD_CONVx_CTL_DISP_EOF_INT_EN; |
| writel(x, conv->conv_base + LCD_DSI_DxSCR0_OFFSET); |
| } else |
| conv->irq_pending = 2; |
| printk(KERN_DEBUG "%s: unmask eof: pending irq %d\n", |
| conv->name, conv->irq_pending); |
| } |
| |
| |
| #ifdef CONFIG_PXA95x_DVFM |
| extern void update_hss(void); /* FIXME: workaround for hss change issue*/ |
| #else |
| static void update_hss(void) {} |
| #endif |
| static irqreturn_t converter_handle_irq(int irq, void *dev_id) |
| { |
| struct pxa95xfb_conv_info *conv = (struct pxa95xfb_conv_info *)dev_id; |
| u32 inst0, inst1, inthresh; |
| void *conv_base; |
| |
| if (conv->converter == LCD_MIXER_DISABLE) |
| return IRQ_HANDLED; |
| /* int sts for dpi/dsi0/dsi1 is 0x4410 1028/2028/3028, the eof bit are same 1<< 1*/ |
| conv_base = conv->conv_base; |
| inst0 = readl(conv_base + LCD_DSI_DxINST0_OFFSET); |
| writel(inst0, conv_base + LCD_DSI_DxINST0_OFFSET); |
| if (inst0 & LCD_CONVx_CTL_DISP_URUN_INT_EN) |
| printk(KERN_ALERT "%s: LCD underrun observed!\n", __func__); |
| if (inst0 & LCD_CONVx_CTL_DISP_EOF_INT_EN){ |
| /*workaround here: hss change when dvfm is only permitted when converter eof*/ |
| if(conv->output == OUTPUT_PANEL) |
| update_hss(); |
| if (!atomic_read(&conv->w_intr)) { |
| atomic_set(&conv->w_intr, 1); |
| wake_up(&g_vsync_wq); |
| } |
| |
| if (OUTPUT_PANEL == conv->output && pxa95xfbi[0]->vsync_u_en) |
| schedule_work(&pxa95xfbi[0]->uevent_work); |
| else |
| converter_mask_eof(conv); |
| /*printk(KERN_ALERT "%s: DSI Converter eof observed!\n", __func__);*/ |
| } |
| if (CONVERTER_IS_DSI(conv->converter)){ |
| /*if (inst0 & LCD_CONVx_CTL_DISP_SOF_INT_EN) |
| printk(KERN_ALERT "%s: DSI Converter sof observed!\n", __func__);*/ |
| inst1 = readl(conv_base + LCD_DSI_DxINST1_OFFSET); |
| writel(inst1, conv_base + LCD_DSI_DxINST1_OFFSET); |
| /*printk(KERN_INFO "%s: INT STS0= %x, STS1= %x\n", __func__, inst0, inst1);*/ |
| |
| inthresh = readl(conv_base + LCD_DSI_DxCONV_FIFO_THRESH_INT_OFFSET); |
| writel(inthresh |LCD_DSI_DxCONV_FIFO_THRESH_INT_THRESH_INT_ST, |
| conv_base + LCD_DSI_DxCONV_FIFO_THRESH_INT_OFFSET); |
| /*printk(KERN_INFO "%s: thresh int= %x\n", __func__, inthresh);*/ |
| } |
| return IRQ_HANDLED; |
| } |
| |
| void converter_init(struct pxa95xfb_info *fbi) |
| { |
| struct pxa95xfb_conv_info *conv = &pxa95xfb_conv[fbi->converter - 1]; |
| int ret; |
| conv->conv_base = CONVERTER_BASE_ADDRESS(fbi->reg_base, conv->converter); |
| |
| if (conv->converter == LCD_M2DSI0) |
| conv->clk = clk_get(NULL, "PXA95x_DSI0CLK"); |
| else if (conv->converter == LCD_M2DSI1) |
| conv->clk = clk_get(NULL, "PXA95x_DSI1CLK"); |
| |
| if (IS_ERR(conv->clk)) |
| pr_err("unable to get converter DSI0 CLK\n"); |
| |
| /* TODO : hard code here: if DSI + HDMI, it's adv chip*/ |
| if(CONVERTER_IS_DSI(conv->converter) && conv->output == OUTPUT_PANEL) { |
| if (!conv->dsi_clock_val) |
| conv->dsi_clock_val = 156; |
| if (!conv->dsi_init_cmds) |
| conv->dsi_init_cmds = &init_board[0][0]; |
| if (!conv->dsi_sleep_cmds) |
| conv->dsi_sleep_cmds = &enter_sleep[0][0]; |
| if (!conv->dsi_lanes) |
| conv->dsi_lanes = LCD_Controller_DSI_2LANE; |
| } else if(CONVERTER_IS_DSI(conv->converter) && conv->output == OUTPUT_HDMI){ |
| conv->dsi_clock_val = (((27000000) * 8)/1000000)+1; |
| conv->conf_dsi_video_mode = DSI_MODE_VIDEO_NON_BURST; |
| conv->dsi_lanes = LCD_Controller_DSI_3LANE; |
| } |
| |
| ret = request_irq(conv->irq, converter_handle_irq, IRQF_SHARED, conv->name, conv); |
| if (ret < 0) { |
| printk(KERN_INFO "unable to request IRQ\n"); |
| } |
| |
| if (CONVERTER_IS_DSI(conv->converter) && DSI_IS_COMMAND_MODE(conv->conf_dsi_video_mode)) { |
| ret = loop_kthread_init(&conv->thread, "lcd_refresh_thread", (void *)conv, (void *)dsi_send_frame,28 * HZ / 1000); |
| if(!ret ){ |
| printk(KERN_INFO "pxa95xfb: DSI update thread init failed\n"); |
| } |
| } |
| } |
| |
| static void controller_enable_disable(struct pxa95xfb_info *fbi, int onoff) |
| { |
| int ctrl, stat, retry=50; |
| ctrl = readl(fbi->reg_base + LCD_CTL); |
| if (!(ctrl & LCD_CTL_AXI32_EN)) |
| printk(KERN_ERR "%s: AXI32_EN is zero which should not happen!!!\n", __func__); |
| switch(onoff) |
| { |
| case LCD_Controller_Enable: |
| if ((ctrl & LCD_CTL_LCD_EN) && !(ctrl & LCD_CTL_LCD_QD)) |
| retry = 0; |
| ctrl |= LCD_CTL_LCD_EN | LCD_CTL_GMIX_INT_EN | LCD_CTL_AXI32_EN |
| | LCD_CTL_GFETCH_INT_EN | LCD_CTL_GWIN_INT_EN; |
| /*make sure quick disable is cleared*/ |
| ctrl &= ~LCD_CTL_LCD_QD; |
| writel(ctrl, fbi->reg_base + LCD_CTL); |
| stat = readl(fbi->reg_base + LCD_CTL_INT_STS); |
| while (!(stat & LCD_CTL_INT_STS_LCD_EN_INT_STS) && retry--) { |
| stat = readl(fbi->reg_base + LCD_CTL_INT_STS); |
| msleep(1); |
| } |
| if(retry <= 0) |
| printk(KERN_ERR "%s: lcd en sts not set when enable!!!\n", __func__); |
| writel(LCD_CTL_INT_STS_LCD_EN_INT_STS, fbi->reg_base + LCD_CTL_INT_STS); |
| display_enabled = 1; |
| break; |
| case LCD_Controller_Disable: |
| if (!(ctrl & LCD_CTL_LCD_EN)) |
| retry = 0; |
| ctrl = readl(fbi->reg_base + LCD_CTL); |
| ctrl &= ~(LCD_CTL_LCD_EN); |
| writel(ctrl, fbi->reg_base + LCD_CTL); |
| display_enabled = 0; |
| /* after disable, the LCD_CTL_INT_STS[LCD_CTL_INT_STS_LCD_DIS_INT_STS] |
| * is not cleared |
| * -- now we use quick disable when display off according to BBU team |
| */ |
| stat = readl(fbi->reg_base + LCD_CTL_INT_STS); |
| while ((stat & LCD_CTL_INT_STS_LCD_DIS_INT_STS) && retry--) { |
| stat = readl(fbi->reg_base + LCD_CTL_INT_STS); |
| msleep(1); |
| } |
| if(retry <= 0) |
| printk(KERN_ERR "%s: lcd en sts not cleared when disable!!!\n", __func__); |
| writel(LCD_CTL_INT_STS_LCD_DIS_INT_STS, fbi->reg_base + LCD_CTL_INT_STS); |
| break; |
| case LCD_Controller_Quick_Disable: |
| if (ctrl & LCD_CTL_LCD_QD) |
| retry = 0; |
| ctrl = readl(fbi->reg_base + LCD_CTL); |
| ctrl |= LCD_CTL_LCD_QD; |
| writel(ctrl, fbi->reg_base + LCD_CTL); |
| /* move to LCD_CTL_INT_STS reg to check*/ |
| stat = readl(fbi->reg_base + LCD_CTL_INT_STS); |
| while (!(stat & LCD_CTL_INT_STS_LCD_QD_INT_STS) && retry--){ |
| stat = readl(fbi->reg_base + LCD_CTL_INT_STS); |
| msleep(1); |
| } |
| if(retry <= 0) |
| printk(KERN_ERR "%s: lcd qd sts not set when quick disable!!!\n", __func__); |
| writel(LCD_CTL_INT_STS_LCD_QD_INT_STS, fbi->reg_base + LCD_CTL_INT_STS); |
| display_enabled = 0; |
| break; |
| default: |
| printk(KERN_ERR "%s: invalid parameter\n", __func__); |
| break; |
| } |
| } |
| |
| static int determine_best_pix_fmt(struct fb_var_screeninfo *var) |
| { |
| /* |
| * Pseudocolor mode? |
| */ |
| if (var->bits_per_pixel == 8) |
| return PIX_FMT_PSEUDOCOLOR; |
| |
| /* |
| * Check for YUV422PLANAR. |
| */ |
| if (var->bits_per_pixel == 16 && var->red.length == 8 && |
| var->green.length == 4 && var->blue.length == 4) { |
| if (var->red.offset >= var->blue.offset) |
| return PIX_FMTIN_YUV422; |
| else |
| printk("%s: YVU422PLANAR format is not supported!\n", __func__); |
| } |
| |
| /* |
| * Check for YUV420PLANAR. |
| */ |
| if (var->bits_per_pixel == 12 && var->red.length == 8 && |
| var->green.length == 2 && var->blue.length == 2) { |
| if (var->green.offset >= var->blue.offset) |
| return PIX_FMTIN_YUV420; |
| else |
| return PIX_FMTIN_YVU420; |
| } |
| |
| /* |
| * Check for YUV444PLANAR. |
| */ |
| if (var->bits_per_pixel == 24 && var->red.length == 16 && |
| var->green.length == 16 && var->blue.length == 16) { |
| if (var->red.offset >= var->blue.offset) |
| return PIX_FMTIN_YUV444; |
| else |
| printk("%s: YVU420PLANAR format is not supported!\n", __func__); |
| } |
| /* |
| * Check for YUV422PACK. |
| */ |
| if (var->bits_per_pixel == 16 && var->red.length == 16 && |
| var->green.length == 16 && var->blue.length == 16) { |
| if ((var->red.offset >= var->blue.offset) && (var->red.offset == 8)) |
| return PIX_FMTIN_YUV422IL; |
| else |
| printk("%s: YVU422PACK or UYVY format is not supported!\n", __func__); |
| } |
| |
| /* |
| * Check for 565/1555. |
| */ |
| if (var->bits_per_pixel == 16 && var->red.length <= 5 && |
| var->green.length <= 6 && var->blue.length <= 5) { |
| if (var->transp.length == 0) { |
| if (var->red.offset >= var->blue.offset) |
| return PIX_FMTIN_RGB_16; |
| else |
| printk("%s: BGR565 format is not supported!\n", __func__); |
| } |
| } |
| |
| /* |
| * Check for 888/A888. |
| */ |
| if (var->bits_per_pixel <= 32 && var->red.length <= 8 && |
| var->green.length <= 8 && var->blue.length <= 8) { |
| if (var->bits_per_pixel == 24 && var->transp.length == 0) { |
| if (var->red.offset >= var->blue.offset) |
| return PIX_FMTIN_RGB_24_PACK; |
| else |
| printk("%s: BGR888 packed format is not supported!\n", __func__); |
| } |
| |
| if (var->bits_per_pixel == 32 && var->transp.offset == 24) { |
| if (var->red.offset >= var->blue.offset) |
| return PIX_FMTIN_RGB_32; |
| else |
| printk("%s: BGRA888 format is not supported!\n", __func__); |
| } else { |
| if (var->red.offset >= var->blue.offset) |
| return PIX_FMTIN_RGB_24; |
| else |
| printk("%s: BGR888 format is not supported!\n", __func__);; |
| } |
| |
| /* fall through */ |
| } |
| |
| return -EINVAL; |
| |
| |
| } |
| |
| |
| static void set_fetch(struct pxa95xfb_info *fbi) |
| { |
| u32 x; |
| struct fb_videomode * m = &fbi->mode; |
| /* set DMA descriptor register |
| * NOT set LCD_FR_ADDRx, set it in set_graphics_start function |
| */ |
| DisplayControllerFrameDescriptor *dmadesc_cpu; |
| u32 dmadesc_dma; |
| int start_channel, end_channel, channel; |
| /* for normal format, set channel = window, for YUV, set 4,5,6 channels*/ |
| start_channel = fbi->window; |
| end_channel = (format_is_yuv_planar(fbi->pix_fmt) && start_channel == 4)? |
| 6 : start_channel; |
| |
| for(channel = start_channel; channel <= end_channel; channel ++){ |
| dmadesc_cpu = (DisplayControllerFrameDescriptor *)((u32)fbi->fb_start- 16*(channel+1)); |
| dmadesc_dma = fbi->fb_start_dma - 16*(channel+1); |
| dmadesc_cpu->LCD_FR_IDx = 0; |
| /*FIXME: for yuv, this value might be not correct*/ |
| dmadesc_cpu->LCD_CH_CMDx = (fbi->user_addr)? |
| fbi->surface.viewPortInfo.srcHeight * fbi->surface.viewPortInfo.yPitch: |
| m->yres * m->xres * pix_fmt_to_bpp(fbi->pix_fmt); |
| dmadesc_cpu->LCD_NXT_DESC_ADDRx = dmadesc_dma; |
| writel(dmadesc_dma, fbi->reg_base + LCD_NXT_DESC_ADDR0 + channel * 0x40); |
| |
| /* set format */ |
| x = readl(fbi->reg_base + LCD_FETCH_CTL0 + fbi->window * 0x40); |
| x &= ~(LCD_FETCH_CTLx_SRC_FOR(0x7)); |
| if (fbi->pix_fmt == PIX_FMTIN_YVU420) |
| x |= LCD_FETCH_CTLx_SRC_FOR(PIX_FMTIN_YUV420); |
| else |
| x |= LCD_FETCH_CTLx_SRC_FOR(fbi->pix_fmt); |
| x |= LCD_FETCH_CTLx_CHAN_EN | LCD_FETCH_CTLx_BUS_ERR_INT_EN; |
| if(fbi->eof_handler && channel == start_channel) |
| x |= LCD_FETCH_CTLx_END_FR_INT_EN; |
| else |
| x &= ~LCD_FETCH_CTLx_END_FR_INT_EN; |
| if (cpu_is_pxa978()) { |
| x |= LCD_FETCH_CTLx_MAX_OUTSTANDING_REQ(0x7) |
| | LCD_FETCH_CTLx_ARLEN(0xf); |
| } |
| writel(x, fbi->reg_base + LCD_FETCH_CTL0 + channel * 0x40); |
| } |
| |
| } |
| |
| static void set_window(struct pxa95xfb_info *fbi) |
| { |
| u32 x; |
| struct fb_videomode * m = &fbi->mode; |
| u32 xsrc, ysrc, xdst, ydst, xscale, yscale; |
| u32 rcrop; |
| u32 ctl_offset = (fbi->window == 4)? LCD_WIN4_CTL: LCD_WIN0_CTL + fbi->window * 0x40; |
| u32 crop1_offset = (fbi->window == 4) ? LCD_WIN4_CROP1: |
| LCD_WIN0_CROP1 + fbi->window * 0x40; |
| |
| xsrc = (fbi->surface.viewPortInfo.yPitch)? |
| fbi->surface.viewPortInfo.yPitch / pix_fmt_to_bpp(fbi->pix_fmt) : m->xres; |
| ysrc = (fbi->surface.viewPortInfo.srcHeight)? |
| fbi->surface.viewPortInfo.srcHeight : m->yres; |
| |
| /* set input image resolution*/ |
| x = LCD_WINx_CTL_WIN_XRES(xsrc/4)|LCD_WINx_CTL_WIN_YRES(ysrc/4); |
| x |= LCD_WINx_CTL_WIN_URUN_INT_EN; |
| writel(x, fbi->reg_base + ctl_offset); |
| |
| /* set cropping */ |
| rcrop = (fbi->surface.viewPortInfo.yPitch / pix_fmt_to_bpp(fbi->pix_fmt) - |
| fbi->surface.viewPortInfo.srcWidth); |
| x = LCD_WINx_CROP1_RC(rcrop/4); |
| writel(x, fbi->reg_base + crop1_offset); |
| |
| /*scale is supported when wge = 4, and in fb0, surface is always not set*/ |
| if(4 != fbi->window) |
| return; |
| |
| xdst = (fbi->surface.viewPortInfo.zoomXSize)? |
| fbi->surface.viewPortInfo.zoomXSize : m->xres; |
| ydst = (fbi->surface.viewPortInfo.zoomYSize)? |
| fbi->surface.viewPortInfo.zoomYSize : m->yres; |
| |
| x = readl(fbi->reg_base + LCD_WIN0_CFG + fbi->window * 0x40); |
| if(xdst < xsrc){ |
| x &= ~(1<<24); |
| xscale = (100000 - xdst*100000/(xsrc))/3125 - 1; |
| }else{ |
| x |= (1<<24); |
| xscale = (xdst*1000/(xsrc) - 1000)/125; |
| } |
| xscale &= 0x1f; |
| if(ydst < ysrc){ |
| x &= ~(1<<18); |
| yscale = (100000 - ydst*100000/(ysrc))/3125 - 1; |
| }else{ |
| x |= (1<<18); |
| yscale = (ydst*1000/(ysrc) - 1000)/125; |
| } |
| yscale &= 0x1f; |
| x &= ~(LCD_WINx_CFG_WIN_XSCALE(0x1f)|LCD_WINx_CFG_WIN_YSCALE(0x1f)); |
| x |= LCD_WINx_CFG_WIN_XSCALE(xscale)|LCD_WINx_CFG_WIN_YSCALE(yscale); |
| writel(x, fbi->reg_base + LCD_WIN4_CFG); |
| } |
| |
| static void mixer_onoff(struct pxa95xfb_info *fbi, int on) |
| { |
| u32 ctl0_off = LCD_MIXER0_CTL0 + fbi->mixer_id* 0x100; |
| int ret; |
| u32 x; |
| |
| mutex_lock(&mutex_mixer_update[fbi->mixer_id]); |
| x = readl(fbi->reg_base + ctl0_off); |
| /* not root cause issue: sometime mixer value are all 0!!!*/ |
| WARN_ON(x == 0); |
| if (x == 0) |
| x = readl(fbi->reg_base + ctl0_off); |
| |
| /* check */ |
| if ((!!(x & LCD_MIXERx_CTL0_MIX_EN)) == on) { |
| printk(KERN_INFO "mixer%d status %x already %d\n", fbi->mixer_id, x, on); |
| mutex_unlock(&mutex_mixer_update[fbi->mixer_id]); |
| return; |
| } |
| if (on && ((x & LCD_MIXERx_CTL0_ALL_OL_MASK) == LCD_MIXERx_CTL0_ALL_OL_DISABLE)) { |
| printk(KERN_ERR "mixer%d on but all layers are off\n", fbi->mixer_id); |
| mutex_unlock(&mutex_mixer_update[fbi->mixer_id]); |
| return; |
| } |
| |
| x &= ~LCD_MIXERx_CTL0_MIX_EN; |
| if (on) |
| x |= LCD_MIXERx_CTL0_MIX_EN; |
| |
| printk(KERN_INFO "Mixer%d %s\n", fbi->mixer_id, on ? "On": "Off"); |
| b_mixer_update[fbi->mixer_id] = 1; |
| writel(x, fbi->reg_base + ctl0_off); |
| |
| ret = wait_event_interruptible_timeout(wq_mixer_update[fbi->mixer_id], |
| !b_mixer_update[fbi->mixer_id], 60 * HZ / 1000); |
| if (!ret) |
| printk(KERN_ERR "Mixer%d %s failed!\n", fbi->mixer_id, on ? "On": "Off"); |
| mutex_unlock(&mutex_mixer_update[fbi->mixer_id]); |
| |
| } |
| |
| /* contains only mixer update, mixer onoff set to mixer_onoff */ |
| static void set_mixer(struct pxa95xfb_info *fbi) |
| { |
| u32 x; |
| u32 mixer_stat, layer_stat, mixer_update; |
| struct fb_videomode * m = &fbi->mode; |
| |
| u32 ctl0_off = LCD_MIXER0_CTL0 + fbi->mixer_id* 0x100; |
| u32 ctl1_off = LCD_MIXER0_CTL1 + fbi->mixer_id* 0x100; |
| u32 ctl2_off = LCD_MIXER0_CTL2 + fbi->mixer_id* 0x100; |
| u32 cfg0_off = LCD_MIXER0_BP_CFG0 + fbi->mixer_id* 0x100 + fbi->zorder* 0x8; |
| u32 cfg1_off = LCD_MIXER0_BP_CFG1 + fbi->mixer_id* 0x100 + fbi->zorder* 0x8; |
| u32 converter = fbi->converter; |
| |
| u32 xsize, ysize, xpos, ypos; |
| xsize = (fbi->surface.viewPortInfo.zoomXSize)? |
| fbi->surface.viewPortInfo.zoomXSize : m->xres; |
| ysize = (fbi->surface.viewPortInfo.zoomYSize)? |
| fbi->surface.viewPortInfo.zoomYSize : m->yres; |
| /* in fb0, surface is always set to 0*/ |
| xpos = fbi->surface.viewPortOffset.xOffset; |
| ypos = fbi->surface.viewPortOffset.yOffset; |
| |
| if (converter == LCD_MIXER_DISABLE) |
| return; |
| |
| x = readl(fbi->reg_base + cfg0_off); |
| x &= ~(LCD_MIXERx_CFG0_XPOS(0x7ff) | LCD_MIXERx_CFG0_YPOS(0x7ff)); |
| x |= LCD_MIXERx_CFG0_XPOS(xpos) |
| | LCD_MIXERx_CFG0_YPOS(ypos); |
| writel(x, fbi->reg_base + cfg0_off); |
| |
| x = readl(fbi->reg_base + cfg1_off); |
| x &= ~(LCD_MIXERx_CFG1_XRES(0x7ff) | LCD_MIXERx_CFG1_YRES(0x7ff)); |
| x |= LCD_MIXERx_CFG1_XRES(xsize)| LCD_MIXERx_CFG1_YRES(ysize); |
| writel(x, fbi->reg_base + cfg1_off); |
| |
| x = readl(fbi->reg_base + ctl1_off); |
| x &= ~(LCD_MIXERx_CTL1_DISP_XRES(0x7ff) | LCD_MIXERx_CTL1_DISP_YRES(0x7ff)); |
| x |= LCD_MIXERx_CTL1_DISP_XRES(m->xres)| LCD_MIXERx_CTL1_DISP_YRES(m->yres) |
| | LCD_MIXERx_CTL1_DISP_UPDATE_INT_EN |
| | LCD_MIXERx_CTL1_DISP_EN_INT_EN; |
| writel(x, fbi->reg_base + ctl1_off); |
| |
| x = LCD_MIXERx_CTL2_CONV_ID(LCD_Controller_P_CONVERTER0 - LCD_M2PARALELL_CONVERTER |
| + converter); |
| writel(x, fbi->reg_base + ctl2_off); |
| |
| /* laste setp: check whether separate to mixer on/off, layer on/off |
| * LCD_MIXERx_CTL0_DISP_UPDATE should be set as the last step */ |
| x = readl(fbi->reg_base + ctl0_off); |
| mixer_stat = x & LCD_MIXERx_CTL0_MIX_EN; |
| layer_stat = (x & LCD_MIXERx_CTL0_OLx_ID_MASK(fbi->zorder)) |
| != LCD_MIXERx_CTL0_OLx_ID_MASK(fbi->zorder); |
| x &= ~ LCD_MIXERx_CTL0_OLx_ID_MASK(fbi->zorder); |
| x |= (fbi->on)? |
| LCD_MIXERx_CTL0_OLx_ID(fbi->zorder, LCD_Controller_WGe0 + fbi->window): |
| LCD_MIXERx_CTL0_OLx_ID_MASK(fbi->zorder); |
| |
| /* mixer would not update in follow cases: |
| * 1. mixer is off before, both turning on and update would not real update |
| * 2. mixer is turning off |
| * 3. layer is off and would not turned on |
| */ |
| if (!mixer_stat) { |
| printk(KERN_INFO "mixer%d: mixer is off, for both first layer on and reg setting, no update executed\n", fbi->mixer_id); |
| mixer_update = 0; |
| } else if ((x & LCD_MIXERx_CTL0_ALL_OL_MASK) == LCD_MIXERx_CTL0_ALL_OL_DISABLE) { |
| printk(KERN_INFO "mixer%d: last layer shutoff, no update executed, wait later mixer off\n", fbi->mixer_id); |
| mixer_update = 0; |
| } else if (!layer_stat && !fbi->on) { |
| printk(KERN_INFO "mixer%d: layer is kept off so no update is required\n", fbi->mixer_id); |
| mixer_update = 0; |
| } else { |
| printk(KERN_INFO "Mixer%d update\n", fbi->mixer_id); |
| mixer_update = 1; |
| x |= LCD_MIXERx_CTL0_DISP_UPDATE; |
| b_mixer_update[fbi->mixer_id] = 1; |
| } |
| |
| writel(x, fbi->reg_base + ctl0_off); |
| |
| if (mixer_update) { |
| int ret = wait_event_interruptible_timeout(wq_mixer_update[fbi->mixer_id], |
| !b_mixer_update[fbi->mixer_id], 60 * HZ / 1000); |
| if (!ret) |
| printk(KERN_ERR "mixer update failed\n"); |
| } |
| } |
| |
| static void set_scale(struct pxa95xfb_info *fbi) |
| { |
| u32 scale_lut[] = {0x00004000, 0xff0940f8, 0xfe0f40f3, 0xfc163ef0, 0xfa1c3cee, |
| 0xf7223aed, 0xf42837ed, 0xf22c34ee, 0xf03030f0, 0xee342cf2, 0xed3728f4, |
| 0xed3a22f7, 0xee3c1cfa, 0xf03e16fc, 0xf3400ffe, 0xf84009ff}; |
| int i; |
| for (i = 0; i < 16; i++){ |
| writel(i, fbi->reg_base + LCD_WIN4_SCALE_PTR); |
| writel(scale_lut[i], fbi->reg_base + LCD_WIN4_SCALE_RW); |
| } |
| } |
| |
| static void set_dither(struct pxa95xfb_info *fbi, int table_4x8, int enable, int mode) |
| { |
| u32 ctl_off, tbl_off, ctl; |
| if (fbi->converter == LCD_M2PARALELL_CONVERTER) { |
| ctl_off = LCD_DITHER_CTL; |
| tbl_off = LCD_DITHER_TBL; |
| } else if (fbi->converter == LCD_M2DSI1) { |
| ctl_off = LCD_DSI_DITHER_CTL; |
| tbl_off = LCD_DSI_DITHER_TBL; |
| } else { |
| printk(KERN_ERR "%s: converter[%d] not support dither!\n", __func__, fbi->converter); |
| return; |
| } |
| |
| if (!enable) { |
| ctl = readl(fbi->reg_base + ctl_off); |
| ctl &= ~LCD_DITHER_EN; |
| writel(ctl, fbi->reg_base + ctl_off); |
| printk(KERN_INFO "%s: converter[%d] disabled!\n", __func__, fbi->converter); |
| } else { |
| if (table_4x8) { |
| ctl = LCD_DITHER_EN | LCD_DITHER_4X8_EN | LCD_DITHER_MODE(mode); |
| writel(ctl | LCD_DITHER_TBL_IND(0), fbi->reg_base + ctl_off); |
| writel(LCD_DITHER_TB_4X8_IND0, fbi->reg_base + tbl_off); |
| writel(ctl | LCD_DITHER_TBL_IND(1), fbi->reg_base + ctl_off); |
| writel(LCD_DITHER_TB_4X8_IND1, fbi->reg_base + tbl_off); |
| writel(ctl | LCD_DITHER_TBL_IND(2), fbi->reg_base + ctl_off); |
| writel(LCD_DITHER_TB_4X8_IND2, fbi->reg_base + tbl_off); |
| writel(ctl | LCD_DITHER_TBL_IND(3), fbi->reg_base + ctl_off); |
| writel(LCD_DITHER_TB_4X8_IND3, fbi->reg_base + tbl_off); |
| } else { |
| ctl = LCD_DITHER_EN | LCD_DITHER_MODE(mode); |
| writel(ctl | LCD_DITHER_TBL_IND(0), fbi->reg_base + ctl_off); |
| writel(LCD_DITHER_TB_4X4_IND0, fbi->reg_base + tbl_off); |
| writel(ctl | LCD_DITHER_TBL_IND(1), fbi->reg_base + ctl_off); |
| writel(LCD_DITHER_TB_4X4_IND1, fbi->reg_base + tbl_off); |
| } |
| } |
| } |
| |
| static void vsync_uevent_worker(struct work_struct *work) |
| { |
| struct pxa95xfb_info *fbi = container_of((struct work_struct *)work, |
| struct pxa95xfb_info, uevent_work); |
| char str[64]; |
| char *vsync_str[] = {str, NULL}; |
| struct timespec vsync_time; |
| struct platform_device *pdev; |
| uint64_t nanoseconds; |
| |
| if (!fbi) { |
| printk(KERN_ERR "failed to get fbi when report vsync uevent!!!\n"); |
| return; |
| } |
| |
| pdev = to_platform_device(fbi->dev); |
| ktime_get_ts(&vsync_time); |
| |
| nanoseconds = ((uint64_t)vsync_time.tv_sec)*1000*1000*1000 + ((uint64_t)vsync_time.tv_nsec); |
| snprintf(str, 64, "DISP_VSYNC=%lld", (uint64_t)nanoseconds); |
| printk(KERN_DEBUG "%s\n", str); |
| kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, vsync_str); |
| } |
| |
| static ssize_t vsync_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct pxa95xfb_info *fbi = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "fbi%d: wait vsync uevent %d\n", |
| fbi->id, fbi->vsync_u_en); |
| } |
| |
| static ssize_t vsync_store( |
| struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| struct pxa95xfb_info *fbi = dev_get_drvdata(dev); |
| int conv = pxa95xfbi[0]->converter; |
| int vsync; |
| sscanf(buf, "%d", &vsync); |
| if (vsync == pxa95xfbi[0]->vsync_u_en) { |
| printk(KERN_INFO "vsync is already %s\n", vsync?"on":"off"); |
| return size; |
| } |
| |
| /* only need to unmask, no need to mask when disable, as in irq it will mask when irq_pending=0*/ |
| pxa95xfbi[0]->vsync_u_en = vsync; |
| if (vsync) |
| converter_unmask_eof(&pxa95xfb_conv[conv - 1]); |
| |
| printk(KERN_INFO "fb%d vsync %s\n", fbi->id, fbi->vsync_u_en?"on":"off"); |
| return size; |
| } |
| |
| static DEVICE_ATTR(vsync, S_IRUGO | S_IWUSR, vsync_show, vsync_store); |
| |
| /* |
| * The hardware clock divider has an integer and a fractional |
| * stage: |
| * |
| * clk2 = clk_in / integer_divider |
| * clk_out = clk2 * (1 - (fractional_divider >> 12)) |
| * |
| * Calculate integer and fractional divider for given clk_in |
| * and clk_out. |
| */ |
| #define DEFAULT_REFRESH 60 /* Hz */ |
| |
| void lcdc_correct_pixclock(struct fb_videomode * m) |
| { |
| u64 div_result; |
| u32 total_w, total_h, refresh; |
| if ((m->xres) && (m->yres)) { |
| refresh = (m->refresh)? m->refresh: DEFAULT_REFRESH; |
| total_w = m->xres + m->left_margin + m->right_margin + m->hsync_len; |
| total_h = m->yres + m->upper_margin + m->lower_margin + m->vsync_len; |
| div_result = 1000000000000ll; |
| do_div(div_result, total_w * total_h * refresh); |
| m->pixclock = div_result; |
| printk(KERN_INFO "LCD %s: pixclock %d\n", __func__, m->pixclock); |
| } |
| } |
| |
| /* alphamode: 0: colorkey, 1: window alpha, 2: per-pixel alpha*/ |
| u32 lcdc_set_colorkeyalpha(struct pxa95xfb_info *fbi) |
| { |
| if (fbi->alphamode == LCD_COLORKEY) { |
| /* color key mode, alpha regs = 0*/ |
| writel(fbi->alphacolor, fbi->reg_base + LCD_CH0_CLR_MATCH + 4*fbi->window); |
| writel(0, fbi->reg_base + LCD_CH0_ALPHA + 4*fbi->window); |
| pr_info("fb%d: colorkey mode, key %x\n", fbi->id, fbi->alphacolor); |
| } else if (fbi->alphamode == LCD_WINALPHA) { |
| /* global alpha mode */ |
| u32 alpha = LCD_CHx_ALPHA_CH_ALPHA(fbi->alphacolor) | LCD_CHx_ALPHA_W_ALPHA_EN; |
| writel(alpha, fbi->reg_base + LCD_CH0_ALPHA + 4*fbi->window); |
| /*pr_info("fb%d: global alpha mode, alpha %x\n", |
| fbi->id, fbi->alphacolor);*/ |
| } else if (fbi->alphamode == LCD_PIXELALPHA) { |
| writel(LCD_CHx_ALPHA_CLR_KEY_EN(2), fbi->reg_base + LCD_CH0_ALPHA + 4*fbi->window); |
| pr_info("fb%d: per-pixel alpha mode\n", fbi->id); |
| } else { |
| writel(0x800000ff, fbi->reg_base + LCD_CH0_ALPHA + 4*fbi->window); |
| pr_info("fb%d: unsupported mode, set un-transparent\n", fbi->id); |
| } |
| return 0; |
| } |
| |
| void lcdc_set_pix_fmt(struct fb_var_screeninfo *var, int pix_fmt) |
| { |
| switch (pix_fmt) { |
| case PIX_FMTIN_RGB_16: |
| var->bits_per_pixel = 16; |
| var->red.offset = 11; var->red.length = 5; |
| var->green.offset = 5; var->green.length = 6; |
| var->blue.offset = 0; var->blue.length = 5; |
| var->transp.offset = 0; var->transp.length = 0; |
| break; |
| case PIX_FMTIN_RGB_24: |
| var->bits_per_pixel = 32; |
| var->red.offset = 16; var->red.length = 8; |
| var->green.offset = 8; var->green.length = 8; |
| var->blue.offset = 0; var->blue.length = 8; |
| var->transp.offset = 0; var->transp.length = 0; |
| break; |
| case PIX_FMTIN_RGB_32: |
| var->bits_per_pixel = 32; |
| var->red.offset = 16; var->red.length = 8; |
| var->green.offset = 8; var->green.length = 8; |
| var->blue.offset = 0; var->blue.length = 8; |
| var->transp.offset = 24; var->transp.length = 8; |
| break; |
| case PIX_FMTIN_RGB_24_PACK: |
| var->bits_per_pixel = 24; |
| var->red.offset = 16; var->red.length = 8; |
| var->green.offset = 8; var->green.length = 8; |
| var->blue.offset = 0; var->blue.length = 8; |
| var->transp.offset = 0; var->transp.length = 0; |
| break; |
| case PIX_FMTIN_YUV420: |
| var->bits_per_pixel = 12; |
| var->red.offset = 4; var->red.length = 8; |
| var->green.offset = 2; var->green.length = 2; |
| var->blue.offset = 0; var->blue.length = 2; |
| var->transp.offset = 0; var->transp.length = 0; |
| break; |
| case PIX_FMTIN_YVU420: |
| var->bits_per_pixel = 12; |
| var->red.offset = 4; var->red.length = 8; |
| var->green.offset = 0; var->green.length = 2; |
| var->blue.offset = 2; var->blue.length = 2; |
| var->transp.offset = 0; var->transp.length = 0; |
| break; |
| case PIX_FMTIN_YUV422: |
| var->bits_per_pixel = 16; |
| var->red.offset = 8; var->red.length = 8; |
| var->green.offset = 4; var->green.length = 4; |
| var->blue.offset = 0; var->blue.length = 4; |
| var->transp.offset = 0; var->transp.length = 0; |
| break; |
| case PIX_FMTIN_YUV444: |
| var->bits_per_pixel = 24; |
| var->red.offset = 16; var->red.length = 16; |
| var->green.offset = 8; var->green.length = 16; |
| var->blue.offset = 0; var->blue.length = 16; |
| var->transp.offset = 0; var->transp.length = 0; |
| break; |
| case PIX_FMTIN_YUV422IL: |
| var->bits_per_pixel = 16; |
| var->red.offset = 8; var->red.length = 16; |
| var->green.offset = 4; var->green.length = 16; |
| var->blue.offset = 0; var->blue.length = 16; |
| var->transp.offset = 0; var->transp.length = 0; |
| break; |
| case PIX_FMT_PSEUDOCOLOR: |
| var->bits_per_pixel = 8; |
| var->red.offset = 0; var->red.length = 8; |
| var->green.offset = 0; var->green.length = 8; |
| var->blue.offset = 0; var->blue.length = 8; |
| var->transp.offset = 0; var->transp.length = 0; |
| break; |
| } |
| } |
| |
| u32 lcdc_set_fr_addr(struct pxa95xfb_info *fbi) |
| { |
| DisplayControllerFrameDescriptor *dmadesc_cpu; |
| u32 addr = fbi->user_addr ? fbi->user_addr: fbi->fb_start_dma; |
| u32 pixel_offset = fbi->user_addr ? 0 : fbi->pixel_offset; |
| |
| if (format_is_yuv_planar(fbi->pix_fmt)) { |
| /* set three channels: 456 for plannar YUV channels: assert fbi->window = 4*/ |
| /* y size is for YUV plannar only: when yres_virtual > yres, still use yres to make sure YUV is continous*/ |
| u32 y_size = fbi->user_addr? (fbi->surface.viewPortInfo.yPitch |
| * fbi->surface.viewPortInfo.srcHeight) : (fbi->mode.xres * fbi->mode.yres); |
| dmadesc_cpu = (DisplayControllerFrameDescriptor *)((u32)fbi->fb_start - 16*5); |
| dmadesc_cpu->LCD_FR_ADDRx = addr + pixel_offset; |
| |
| if (fbi->pix_fmt == PIX_FMTIN_YUV420){ |
| dmadesc_cpu = (DisplayControllerFrameDescriptor *)((u32)fbi->fb_start - 16*6); |
| dmadesc_cpu->LCD_FR_ADDRx = addr + y_size + pixel_offset /4; |
| dmadesc_cpu = (DisplayControllerFrameDescriptor *)((u32)fbi->fb_start - 16*7); |
| dmadesc_cpu->LCD_FR_ADDRx = addr + y_size * 5/4 + pixel_offset /4 ; |
| } |
| /* for YVU420, format is same but UV channel swapped */ |
| if (fbi->pix_fmt == PIX_FMTIN_YVU420){ |
| dmadesc_cpu = (DisplayControllerFrameDescriptor *)((u32)fbi->fb_start - 16*7); |
| dmadesc_cpu->LCD_FR_ADDRx = addr + y_size + pixel_offset /4; |
| dmadesc_cpu = (DisplayControllerFrameDescriptor *)((u32)fbi->fb_start - 16*6); |
| dmadesc_cpu->LCD_FR_ADDRx = addr + y_size * 5/4 + pixel_offset /4 ; |
| } |
| if (fbi->pix_fmt == PIX_FMTIN_YUV422){ |
| dmadesc_cpu = (DisplayControllerFrameDescriptor *)((u32)fbi->fb_start - 16*6); |
| dmadesc_cpu->LCD_FR_ADDRx = addr + y_size + pixel_offset /2; |
| dmadesc_cpu = (DisplayControllerFrameDescriptor *)((u32)fbi->fb_start - 16*7); |
| dmadesc_cpu->LCD_FR_ADDRx = addr + y_size * 3/2 + pixel_offset /2 ; |
| } |
| if (fbi->pix_fmt == PIX_FMTIN_YUV444){ |
| dmadesc_cpu = (DisplayControllerFrameDescriptor *)((u32)fbi->fb_start - 16*6); |
| dmadesc_cpu->LCD_FR_ADDRx = addr + y_size + pixel_offset; |
| dmadesc_cpu = (DisplayControllerFrameDescriptor *)((u32)fbi->fb_start - 16*7); |
| dmadesc_cpu->LCD_FR_ADDRx = addr + y_size * 2 + pixel_offset; |
| } |
| }else{ |
| dmadesc_cpu = (DisplayControllerFrameDescriptor *)((u32)fbi->fb_start- 16*(fbi->window + 1)); |
| dmadesc_cpu->LCD_FR_ADDRx = addr + pixel_offset * pix_fmt_to_bpp(fbi->pix_fmt); |
| } |
| dmadesc_cpu = (DisplayControllerFrameDescriptor *)((u32)fbi->fb_start- 16*(fbi->window + 1)); |
| return dmadesc_cpu->LCD_FR_ADDRx; |
| } |
| |
| u32 lcdc_get_fr_addr(struct pxa95xfb_info *fbi) |
| { |
| /*according to spec, fr_adr is 4-31 bit, but actually it's 0-31bit */ |
| return (readl(fbi->reg_base + LCD_FR_ADDR0 + 0x40*fbi->window)); |
| } |
| |
| /* lcdc set lcd controller only do update, if mixer on/off happens, no update would be done here */ |
| void lcdc_set_lcd_controller(struct pxa95xfb_info *fbi) |
| { |
| mutex_lock(&mutex_mixer_update[fbi->mixer_id]); |
| /* set fetch registers */ |
| set_fetch(fbi); |
| |
| lcdc_set_colorkeyalpha(fbi); |
| |
| /* set window registers */ |
| set_window(fbi); |
| |
| /* set mixer registers */ |
| set_mixer(fbi); |
| mutex_unlock(&mutex_mixer_update[fbi->mixer_id]); |
| } |
| |
| /* workaround here for para: id: converter_id 1, converter_id 2*/ |
| void lcdc_wait_for_vsync(u32 conv_id1, u32 conv_id2) |
| { |
| u32 ret = 0; |
| |
| if (conv_id1 && conv_id1 <= LCD_M2HDMI) |
| atomic_set(&pxa95xfb_conv[conv_id1 - 1].w_intr, 0); |
| else { |
| printk(KERN_ERR "warning %s: para error: conv_id: %d %d\n", __func__, conv_id1, conv_id2); |
| return; |
| } |
| |
| if (conv_id2 && conv_id2 <= LCD_M2HDMI) { |
| atomic_set(&pxa95xfb_conv[conv_id2 - 1].w_intr, 0); |
| converter_unmask_eof(&pxa95xfb_conv[conv_id1 - 1]); |
| converter_unmask_eof(&pxa95xfb_conv[conv_id2 - 1]); |
| ret = wait_event_interruptible_timeout(g_vsync_wq, |
| atomic_read(&pxa95xfb_conv[conv_id1 - 1].w_intr) && atomic_read(&pxa95xfb_conv[conv_id2 - 1].w_intr), |
| 60 * HZ / 1000); |
| if(!ret) |
| printk(KERN_DEBUG "warning %s: waiting vsync failed: conv_id: %s: %d %s: %d\n", |
| __func__, pxa95xfb_conv[conv_id1 - 1].name, |
| atomic_read(&pxa95xfb_conv[conv_id1 - 1].w_intr), |
| pxa95xfb_conv[conv_id2 - 1].name, |
| atomic_read(&pxa95xfb_conv[conv_id2 - 1].w_intr)); |
| } else { |
| converter_unmask_eof(&pxa95xfb_conv[conv_id1 - 1]); |
| ret = wait_event_interruptible_timeout(g_vsync_wq, |
| atomic_read(&pxa95xfb_conv[conv_id1 - 1].w_intr), |
| 60 * HZ / 1000); |
| if(!ret) |
| printk(KERN_DEBUG "warning %s: waiting vsync failed: conv_id: %s: %d\n", |
| __func__, pxa95xfb_conv[conv_id1 - 1].name, |
| atomic_read(&pxa95xfb_conv[conv_id1 - 1].w_intr)); |
| } |
| |
| return; |
| } |
| |
| void *lcdc_alloc_framebuffer(size_t size, dma_addr_t *dma) |
| { |
| int nr, i = 0; |
| struct page **pages; |
| void * start; |
| |
| nr = size >> PAGE_SHIFT; |
| start = alloc_pages_exact(size, GFP_KERNEL | __GFP_ZERO); |
| if (start == NULL) |
| return NULL; |
| |
| *dma = virt_to_phys(start); |
| pages = vmalloc(sizeof(struct page *) * nr); |
| if (pages == NULL) |
| return NULL; |
| |
| while (i < nr) { |
| pages[i] = phys_to_page(*dma + (i << PAGE_SHIFT)); |
| i++; |
| } |
| start = vmap(pages, nr, 0, pgprot_writecombine(pgprot_kernel)); |
| |
| vfree(pages); |
| return start; |
| } |
| |
| int pxa95xfb_check_var(struct fb_var_screeninfo *var, |
| struct fb_info *info) |
| { |
| struct pxa95xfb_info *fbi = info->par; |
| |
| if (var->bits_per_pixel == 8) |
| return -EINVAL; |
| /* |
| * Basic geometry sanity checks. |
| */ |
| if (var->xoffset + var->xres > var->xres_virtual) |
| return -EINVAL; |
| if (var->yoffset + var->yres > var->yres_virtual) |
| return -EINVAL; |
| /* |
| * Check size of framebuffer. |
| */ |
| if (var->xres_virtual * var->yres_virtual * |
| (var->bits_per_pixel >> 3) > fbi->fb_size) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) |
| { |
| return ((chan & 0xffff) >> (16 - bf->length)) << bf->offset; |
| } |
| |
| static u32 to_rgb(u16 red, u16 green, u16 blue) |
| { |
| red >>= 8; |
| green >>= 8; |
| blue >>= 8; |
| |
| return (red << 16) | (green << 8) | blue; |
| } |
| |
| int pxa95xfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, |
| unsigned int blue, unsigned int trans, struct fb_info *info) |
| { |
| struct pxa95xfb_info *fbi = info->par; |
| u32 val; |
| |
| if (info->fix.visual == FB_VISUAL_TRUECOLOR && regno < 16) { |
| val = chan_to_field(red, &info->var.red); |
| val |= chan_to_field(green, &info->var.green); |
| val |= chan_to_field(blue , &info->var.blue); |
| fbi->pseudo_palette[regno] = val; |
| } |
| |
| if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR && regno < 256) { |
| val = to_rgb(red, green, blue); |
| /* TODO */ |
| } |
| |
| return 0; |
| } |
| |
| int pxa95xfb_pan_display(struct fb_var_screeninfo *var, |
| struct fb_info *info) |
| { |
| struct pxa95xfb_info *fbi = (struct pxa95xfb_info *)info->par; |
| |
| fbi->pixel_offset = var->yoffset * var->xres_virtual + var->xoffset; |
| |
| if (fbi->dump) |
| dump_buffer(fbi, var->yoffset); |
| |
| lcdc_set_fr_addr(fbi); |
| if (fbi->vsync_en) |
| lcdc_wait_for_vsync(fbi->converter, 0); |
| |
| return 0; |
| } |
| |
| int var_update(struct fb_info *info) |
| { |
| struct pxa95xfb_info *fbi = info->par; |
| struct fb_var_screeninfo *var = &info->var; |
| struct fb_videomode * m; |
| int pix_fmt; |
| |
| /* set pix_fmt */ |
| pix_fmt = determine_best_pix_fmt(var); |
| if (pix_fmt < 0) |
| return -EINVAL; |
| lcdc_set_pix_fmt(var, pix_fmt); |
| fbi->pix_fmt = pix_fmt; |
| |
| /* set var according to best video mode*/ |
| m = (struct fb_videomode *)fb_match_mode(var, &info->modelist); |
| if(!m){ |
| printk(KERN_WARNING "set par: no match mode, turn to best mode\n"); |
| m = (struct fb_videomode *)fb_find_best_mode(var,&info->modelist); |
| fb_videomode_to_var(var, m); |
| } |
| memcpy(&fbi->mode, m, sizeof(struct fb_videomode)); |
| /* update videomode of fb in same path */ |
| memcpy(&(fb_in_same_path(fbi)->mode), m, sizeof(struct fb_videomode)); |
| /* fix to 2* yres */ |
| var->yres_virtual = var->yres * 2; |
| info->fix.visual = (pix_fmt == PIX_FMT_PSEUDOCOLOR)? |
| FB_VISUAL_PSEUDOCOLOR: FB_VISUAL_TRUECOLOR; |
| info->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8; |
| info->fix.ypanstep = var->yres; |
| return 0; |
| } |
| |
| int pxa95xfb_set_par(struct fb_info *info) |
| { |
| struct pxa95xfb_info *fbi = info->par; |
| struct fb_var_screeninfo *var = &info->var; |
| int ret = var_update(info); |
| if (ret != 0) |
| return ret; |
| |
| /* configure graphics dma always*/ |
| fbi->pixel_offset = var->yoffset * var->xres_virtual + var->xoffset; |
| lcdc_set_fr_addr(fbi); |
| |
| lcdc_set_lcd_controller(fbi); |
| |
| return 0; |
| } |
| |
| static void pxa95xfb_gfx_power(struct pxa95xfb_info *fbi, int on) |
| { |
| int i, en = 0; |
| mutex_lock(&fbi->access_ok); |
| if(on == (!fbi->suspend)){ |
| printk(KERN_INFO "LCD power already %s\n", on?"up":"down"); |
| } else if(!on){ |
| fbi->suspend = 1; |
| converter_onoff(fbi, 0); |
| |
| /* removed due to SI issue. |
| * mixer disable used instead of controller disable |
| * also, clock is not disabled |
| controller_enable_disable(fbi, LCD_Controller_Quick_Disable); |
| clk_disable(fbi->clk_lcd); |
| */ |
| display_enabled = 0; |
| |
| /*if hdmi fbi2/3 has not turn off, should not release the power constrain*/ |
| for (i = 2; i < PXA95xFB_FB_NUM; i ++) { |
| if (pxa95xfbi[i] && pxa95xfbi[i]->controller_on) { |
| en = 1; |
| break; |
| } |
| } |
| if (0 == en) |
| unset_dvfm_constraint(); |
| |
| printk(KERN_INFO "LCD power down\n"); |
| }else{ |
| /* to turn on the display */ |
| set_dvfm_constraint(); |
| |
| /* removed due to SI issue. |
| * mixer disable used instead of controller disable |
| * also, clock is not disabled |
| clk_enable(fbi->clk_lcd); |
| */ |
| display_enabled = 1; |
| |
| lcdc_set_lcd_controller(fbi); |
| converter_onoff(fbi, 1); |
| |
| fbi->suspend = 0; |
| printk(KERN_INFO "LCD power up\n"); |
| } |
| mutex_unlock(&fbi->access_ok); |
| |
| } |
| |
| #ifdef CONFIG_EARLYSUSPEND |
| static void pxa95xfb_gfx_earlysuspend_handler(struct early_suspend *h) |
| { |
| pxa95xfb_gfx_power(pxa95xfbi[0], 0); |
| } |
| |
| static void pxa95xfb_gfx_lateresume_handler(struct early_suspend *h) |
| { |
| pxa95xfb_gfx_power(pxa95xfbi[0], 1); |
| } |
| |
| static struct early_suspend pxa95xfb_gfx_earlysuspend = { |
| .level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN, |
| .suspend = pxa95xfb_gfx_earlysuspend_handler, |
| .resume = pxa95xfb_gfx_lateresume_handler, |
| }; |
| |
| #endif |
| |
| static int pxa95xfb_gfx_blank(int blank, struct fb_info *info) |
| { |
| struct pxa95xfb_info *fbi = info->par; |
| |
| fbi->is_blanked = (blank == FB_BLANK_UNBLANK) ? 0 : 1; |
| pxa95xfb_gfx_power(fbi, (blank == FB_BLANK_UNBLANK)); |
| |
| return 0; |
| } |
| |
| static int get_valid_fetchid_for_ihdmi(void) |
| { |
| int i; |
| for (i = 0; i < PXA95xFB_FB_NUM; i++) |
| if (pxa95xfbi[i] && pxa95xfbi[i]->converter == LCD_M2HDMI |
| && pxa95xfbi[i]->controller_on) |
| return pxa95xfbi[i]->window; |
| return -1; |
| } |
| |
| static irqreturn_t pxa95xfb_gfx_handle_irq_ctl(int irq, void *dev_id) |
| { |
| struct pxa95xfb_info *fbi = (struct pxa95xfb_info *)dev_id; |
| u32 g, x; |
| int i; |
| |
| g = readl(fbi->reg_base + LCD_CONV2_INT_STS); |
| if (g & 0x1) |
| printk(KERN_ALERT "HDMI converter underrun observed!!!\n"); |
| writel(g, fbi->reg_base + LCD_CONV2_INT_STS); |
| |
| g = readl(fbi->reg_base + LCD_CTL_INT_STS); |
| /*do nothing with LCD EN/DIS/Q_DIS: we don't enable these intr*/ |
| |
| /* fetch intr: wait fetch eof*/ |
| if(g & LCD_CTL_INT_STS_GFETCH_INT_STS){ |
| writel(LCD_CTL_INT_STS_GFETCH_INT_STS, fbi->reg_base + LCD_CTL_INT_STS); |
| |
| x = readl(fbi->reg_base + LCD_FETCH_INT_STS1); |
| writel(x, fbi->reg_base + LCD_FETCH_INT_STS1); |
| for(i = 0; i < PXA95xFB_FB_NUM; i++){ |
| if(pxa95xfbi[i] && |
| (x & LCD_FETCH_INT_STS1_BUS_ERRx(pxa95xfbi[i]->window)) ) |
| printk(KERN_ALERT "%s: fetch %d intr Bus Err happen? %x\n", |
| __func__, pxa95xfbi[i]->window, x); |
| } |
| |
| x = readl(fbi->reg_base + LCD_FETCH_INT_STS0); |
| writel(x, fbi->reg_base + LCD_FETCH_INT_STS0); |
| |
| for(i = 0; i < PXA95xFB_FB_NUM; i++){ |
| if(pxa95xfbi[i] && pxa95xfbi[i]->on && |
| (x & LCD_FETCH_INT_STS0_END_FRx(pxa95xfbi[i]->window))) { |
| /*printk(KERN_INFO "%s: fetch %d EOF intr: fr= %x\n",__func__, pxa95xfbi[i]->window, lcdc_get_fr_addr(pxa95xfbi[i]));*/ |
| if (pxa95xfbi[i]->eof_handler) |
| pxa95xfbi[i]->eof_handler(pxa95xfbi[i]); |
| } |
| } |
| |
| /* FIXME: for HDMI, as converter irq is lost, what we could do is using FETCH EOF of HDMI(fbi2/3) */ |
| if (!atomic_read(&pxa95xfb_conv[LCD_M2HDMI - 1].w_intr)) { |
| int ihdmi_fetchid = get_valid_fetchid_for_ihdmi(); |
| if (ihdmi_fetchid > 0 && (x & LCD_FETCH_INT_STS0_END_FRx(ihdmi_fetchid))) { |
| atomic_set(&pxa95xfb_conv[LCD_M2HDMI - 1].w_intr, 1); |
| wake_up(&g_vsync_wq); |
| } |
| } |
| } |
| |
| if(g & LCD_CTL_INT_STS_GWIN_INT_STS){ |
| writel(LCD_CTL_INT_STS_GWIN_INT_STS, fbi->reg_base + LCD_CTL_INT_STS); |
| printk(KERN_WARNING "%s: windows intr happen?\n", __func__); |
| } |
| |
| if(g & LCD_CTL_INT_STS_GMIX_INT_STS){ |
| writel(LCD_CTL_INT_STS_GMIX_INT_STS, fbi->reg_base + LCD_CTL_INT_STS); |
| for(i = 0; i < MIXER_NUM; i++){ |
| x = readl(fbi->reg_base + LCD_MIXER0_INT_STS + i*0x100); |
| writel(x, fbi->reg_base + LCD_MIXER0_INT_STS + i*0x100); |
| if (b_mixer_update[i] && |
| (x & (LCD_MIXERx_CTL1_DISP_UPDATE_INT_EN | LCD_MIXERx_CTL1_DISP_EN_INT_EN))) { |
| b_mixer_update[i] = 0; |
| wake_up(&wq_mixer_update[i]); |
| } |
| } |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static struct fb_ops pxa95xfb_gfx_ops = { |
| .owner = THIS_MODULE, |
| .fb_blank = pxa95xfb_gfx_blank, |
| .fb_ioctl = pxa95xfb_ioctl, |
| .fb_check_var = pxa95xfb_check_var, |
| .fb_set_par = pxa95xfb_set_par, |
| .fb_setcolreg = pxa95xfb_setcolreg, /* TODO */ |
| .fb_pan_display = pxa95xfb_pan_display, |
| .fb_fillrect = cfb_fillrect, |
| .fb_copyarea = cfb_copyarea, |
| .fb_imageblit = cfb_imageblit, |
| }; |
| |
| static int __devinit pxa95xfb_gfx_probe(struct platform_device *pdev) |
| { |
| struct pxa95xfb_mach_info *mi; |
| struct fb_info *info = 0; |
| struct pxa95xfb_info *fbi = 0; |
| struct pxa95xfb_conv_info *conv; |
| struct resource *res; |
| struct clk *clk_lcd; |
| int irq_ctl, irq_conv, ret; |
| int i; |
| |
| mi = pdev->dev.platform_data; |
| if (mi == NULL) { |
| dev_err(&pdev->dev, "no platform data defined\n"); |
| return -EINVAL; |
| } |
| |
| clk_lcd = clk_get(&pdev->dev, "PXA95x_LCDCLK"); |
| if (IS_ERR(clk_lcd)) { |
| dev_err(&pdev->dev, "unable to get LCDCLK"); |
| } |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (res == NULL) { |
| dev_err(&pdev->dev, "no IO memory defined\n"); |
| return -ENOENT; |
| } |
| |
| irq_ctl = platform_get_irq(pdev, 0); |
| if (irq_ctl < 0) { |
| dev_err(&pdev->dev, "no IRQ defined\n"); |
| return -ENOENT; |
| } |
| |
| if(mi->converter != LCD_MIXER_DISABLE){ |
| irq_conv = platform_get_irq(pdev, mi->converter); |
| if (irq_conv < 0) { |
| dev_err(&pdev->dev, "no IRQ defined\n"); |
| return -ENOENT; |
| } |
| }else{ |
| dev_err(&pdev->dev, "no converter defined\n"); |
| return -ENOENT; |
| } |
| |
| info = framebuffer_alloc(sizeof(struct pxa95xfb_info), &pdev->dev); |
| if (info == NULL) |
| return -ENOMEM; |
| |
| /* Initialize private data */ |
| fbi = info->par; |
| if(!fbi) { |
| ret = -EINVAL; |
| goto failed_free_clk; |
| } |
| memset(fbi, 0, sizeof(fbi)); |
| |
| fbi->fb_info = info; |
| platform_set_drvdata(pdev, fbi); |
| fbi->clk_lcd = clk_lcd; |
| fbi->dev = &pdev->dev; |
| fbi->on = 1; /* must! */ |
| fbi->open_count = fbi->on;/*if fbi on as default, open count +1 as default*/ |
| fbi->window = mi->window; |
| fbi->zorder = mi->zorder; |
| fbi->mixer_id = mi->mixer_id; |
| fbi->converter = mi->converter; |
| fbi->pix_fmt = mi->pix_fmt_in; |
| fbi->controller_on = 1; |
| |
| fbi->vsync_en = 0; |
| spin_lock_init(&fbi->buf_lock); |
| mutex_init(&fbi->access_ok); |
| init_waitqueue_head(&g_vsync_wq); |
| for (i = 0; i < MIXER_NUM; i++) { |
| mutex_init(&mutex_mixer_update[i]); |
| init_waitqueue_head(&wq_mixer_update[i]); |
| } |
| |
| /* Map registers.*/ |
| fbi->reg_base = ioremap_nocache(res->start, res->end - res->start); |
| if (fbi->reg_base == NULL) { |
| ret = -ENOMEM; |
| goto failed_free_clk; |
| } |
| |
| /* Allocate framebuffer memory: size = modes xy *4 .*/ |
| fbi->fb_size = PAGE_ALIGN(mi->modes[0].xres * mi->modes[0].yres * 8 + PAGE_SIZE); |
| fbi->fb_start = lcdc_alloc_framebuffer(fbi->fb_size + PAGE_SIZE, |
| &fbi->fb_start_dma); |
| if (fbi->fb_start == NULL) { |
| ret = -ENOMEM; |
| goto failed; |
| } |
| /* the buffer may be very large, s do not depends on CONSISTENT_DMA_SIZE */ |
| memset(fbi->fb_start, 0, fbi->fb_size); |
| fbi->fb_start = fbi->fb_start + PAGE_SIZE; |
| fbi->fb_start_dma = fbi->fb_start_dma + PAGE_SIZE; |
| memset(fbi->fb_start, 0xf8, fbi->fb_size); |
| |
| |
| /* Set video mode and init var*/ |
| for(i = 0; i < mi->num_modes; i++) |
| lcdc_correct_pixclock(&mi->modes[i]); |
| fb_videomode_to_modelist(mi->modes, mi->num_modes, &info->modelist); |
| |
| memcpy(&fbi->mode, &mi->modes[mi->init_mode], sizeof(struct fb_videomode)); |
| lcdc_set_pix_fmt(&info->var, fbi->pix_fmt); |
| fb_videomode_to_var(&info->var, &fbi->mode); |
| /* fix to 2* yres */ |
| info->var.yres_virtual = info->var.yres * 2; |
| |
| /* Initialise static fb parameters.*/ |
| info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK | |
| FBINFO_HWACCEL_XPAN | FBINFO_HWACCEL_YPAN; |
| info->node = -1; |
| strcpy(info->fix.id, mi->id); |
| info->fix.type = FB_TYPE_PACKED_PIXELS; |
| info->fix.type_aux = 0; |
| info->fix.xpanstep = 0; |
| info->fix.ypanstep = info->var.yres; |
| info->fix.ywrapstep = 0; |
| info->fix.mmio_start = res->start; |
| info->fix.mmio_len = res->end - res->start + 1; |
| info->fix.accel = FB_ACCEL_NONE; |
| info->fix.smem_start = fbi->fb_start_dma; |
| info->fix.smem_len = fbi->fb_size; |
| info->fix.visual = (fbi->pix_fmt == PIX_FMT_PSEUDOCOLOR)? |
| FB_VISUAL_PSEUDOCOLOR: FB_VISUAL_TRUECOLOR; |
| info->fix.line_length = info->var.xres_virtual * pix_fmt_to_bpp(fbi->pix_fmt); |
| |
| info->fbops = &pxa95xfb_gfx_ops; |
| info->pseudo_palette = fbi->pseudo_palette; |
| info->screen_base = fbi->fb_start; |
| info->screen_size = fbi->fb_size; |
| |
| /* set global var at last*/ |
| pxa95xfbi[0] = fbi; |
| |
| /* Register irq handler. */ |
| ret = request_irq(irq_ctl, pxa95xfb_gfx_handle_irq_ctl, IRQF_SHARED, mi->id, fbi); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "unable to request IRQ\n"); |
| ret = -ENXIO; |
| goto failed_free_clk; |
| } |
| |
| set_dvfm_constraint(); |
| |
| clk_enable(fbi->clk_lcd); |
| clk_set_rate(fbi->clk_lcd, 104000000); |
| if (readl(fbi->reg_base + LCD_CTL) & LCD_CTL_AXI32_EN) { |
| /* lcd is already enable in uboot*/ |
| display_enabled = 1; |
| fb2conv(fbi).on = 1; |
| dev_info(&pdev->dev, "already on before probe, only update\n"); |
| } else { |
| /* Enable AXI32 before modifying the controller registers */ |
| writel(LCD_CTL_AXI32_EN, fbi->reg_base + LCD_CTL); |
| controller_enable_disable(fbi, LCD_Controller_Enable); |
| } |
| |
| /* set scale registers for fb1*/ |
| set_scale(fbi); |
| |
| if (mi->dither_en) { |
| int dither_mode = -1; |
| if (mi->pix_fmt_out == PIX_FMTOUT_16_RGB565) |
| dither_mode = 1; |
| else if (mi->pix_fmt_out == PIX_FMTOUT_18_RGB666) |
| dither_mode = 2; |
| else |
| printk(KERN_ERR "fb%d dither format %d unsupported\n", fbi->id, mi->pix_fmt_out); |
| if (dither_mode > 0) |
| set_dither(fbi, mi->dither_tbl_4x8, mi->dither_en, dither_mode); |
| } |
| |
| /*set ch4 as un-transparent for YVU use*/ |
| writel(0x800000ff, fbi->reg_base + LCD_CH4_ALPHA); |
| |
| /*init converter*/ |
| conv = &pxa95xfb_conv[fbi->converter -1]; |
| if(!conv->inited){ |
| conv->inited = 1; |
| conv->output = mi->output; |
| conv->irq = irq_conv; |
| conv->pix_fmt_out = mi->pix_fmt_out; |
| conv->active = mi->active; |
| conv->invert_pixclock = mi->invert_pixclock; |
| conv->panel_rbswap = mi->panel_rbswap; |
| conv->panel_type = mi->panel_type; |
| conv->power = mi->panel_power; |
| conv->reset = mi->reset; |
| conv->dsi_init_cmds = mi->dsi_init_cmds; |
| conv->dsi_clock_val = mi->dsi_clock_val; |
| conv->dsi_sleep_cmds = mi->dsi_sleep_cmds; |
| conv->conf_dsi_video_mode = mi->dsi_mode; |
| conv->dsi_lanes = mi->dsi_lane_nr; |
| |
| converter_init(fbi); |
| } |
| |
| lcdc_set_fr_addr(fbi); |
| lcdc_set_lcd_controller(fbi); |
| converter_onoff(fbi, 1); |
| conv_ref_inc(fbi); |
| |
| /* For FB framework: Allocate color map and Register framebuffer*/ |
| if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) { |
| ret = -ENOMEM; |
| goto failed_free_irq_conv; |
| } |
| ret = register_framebuffer(info); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Failed to register pxa95x-fb: %d\n", ret); |
| ret = -ENXIO; |
| goto failed_free_cmap; |
| } |
| printk(KERN_INFO "pxa95xfb: frame buffer device %s was loaded" |
| " to /dev/fb%d <%s>.\n", conv->output?"HDMI":"PANEL", info->node, info->fix.id); |
| |
| ret = device_create_file(&pdev->dev, &dev_attr_vsync); |
| if (ret < 0) { |
| printk(KERN_INFO "%s, device attr create fail: %d\n", __func__, ret); |
| goto failed_free_cmap; |
| } |
| INIT_WORK(&fbi->uevent_work, vsync_uevent_worker); |
| mvdisp_debug_init(&pdev->dev); |
| |
| #ifdef CONFIG_EARLYSUSPEND |
| register_early_suspend(&pxa95xfb_gfx_earlysuspend); |
| #endif |
| |
| return 0; |
| |
| failed_free_cmap: |
| fb_dealloc_cmap(&info->cmap); |
| failed_free_irq_conv: |
| free_irq(irq_conv, fbi); |
| free_irq(irq_ctl, fbi); |
| failed_free_clk: |
| clk_disable(fbi->clk_lcd); |
| clk_put(fbi->clk_lcd); |
| failed: |
| pr_err("pxa95x-fb: frame buffer device init failed\n"); |
| platform_set_drvdata(pdev, NULL); |
| |
| if (fbi && fbi->reg_base) { |
| iounmap(fbi->reg_base); |
| kfree(fbi); |
| } |
| |
| return ret; |
| } |
| |
| static struct platform_driver pxa95xfb_gfx_driver = { |
| .driver = { |
| .name = "pxa95x-fb", |
| .owner = THIS_MODULE, |
| }, |
| .probe = pxa95xfb_gfx_probe, |
| }; |
| |
| static int __devinit pxa95xfb_gfx_init(void) |
| { |
| dvfm_register("pxa95x-fb", &dvfm_dev_idx); |
| return platform_driver_register(&pxa95xfb_gfx_driver); |
| } |
| device_initcall_sync(pxa95xfb_gfx_init); |
| |
| MODULE_AUTHOR("Lennert Buytenhek <buytenh@marvell.com>"); |
| MODULE_DESCRIPTION("Framebuffer driver for PXA95x"); |
| MODULE_LICENSE("GPL"); |