From cfea9745d72b16ac9184ea5ac8beab1733d70792 Mon Sep 17 00:00:00 2001 From: Jari Helaakoski Date: Mon, 7 Jan 2013 20:56:31 +0200 Subject: [PATCH] video:sunxi:disp:Dynamic mode switching This patch improves Hans De Goede's EDID patch by allowing user to set new mode at runtime. Example: echo D:1280x720p-60 > /sys/devices/platform/disp/graphics/fb0/mode But precondition is that EDID support must be activated like before. Ie. by using following modprobe configuration: options disp screen0_output_mode=EDID:1280x720p60 Signed-off-by: Jari Helaakoski Acked-by: Hans de Goede --- drivers/video/sunxi/disp/bsp_display.h | 4 ++ drivers/video/sunxi/disp/dev_fb.c | 29 ++++++++- drivers/video/sunxi/disp/disp_hdmi.c | 81 ++++++++++++++++++++++++++ drivers/video/sunxi/disp/disp_lcd.c | 73 +++++++++++++++++++++++ drivers/video/sunxi/hdmi/drv_hdmi.c | 22 +++++++ include/video/sunxi_disp_ioctl.h | 1 + 6 files changed, 209 insertions(+), 1 deletion(-) diff --git a/drivers/video/sunxi/disp/bsp_display.h b/drivers/video/sunxi/disp/bsp_display.h index 3bbc0305cb2378..1c895f9a7abf86 100644 --- a/drivers/video/sunxi/disp/bsp_display.h +++ b/drivers/video/sunxi/disp/bsp_display.h @@ -77,6 +77,7 @@ typedef struct { void (*tve_interrup) (__u32 sel); __s32(*hdmi_set_mode) (__disp_tv_mode_t mode); + __s32(*hdmi_set_videomode) (const struct __disp_video_timing *mode); __s32(*hdmi_wait_edid) (void); __s32(*Hdmi_open) (void); __s32(*Hdmi_close) (void); @@ -235,6 +236,7 @@ extern __s32 LCD_PWM_EN(__u32 sel, __bool b_en); extern __s32 LCD_BL_EN(__u32 sel, __bool b_en); extern __s32 BSP_disp_lcd_user_defined_func(__u32 sel, __u32 para1, __u32 para2, __u32 para3); +extern __s32 BSP_disp_get_videomode(__u32 sel, struct fb_videomode *videomode); extern __s32 BSP_disp_get_timing(__u32 sel, __disp_tcon_timing_t *tt); extern __u32 BSP_disp_get_cur_line(__u32 sel); #ifdef CONFIG_ARCH_SUN5I @@ -257,6 +259,8 @@ extern __s32 BSP_disp_tv_get_dac_source(__u32 sel, __u32 index); extern __s32 BSP_disp_hdmi_open(__u32 sel); extern __s32 BSP_disp_hdmi_close(__u32 sel); extern __s32 BSP_disp_hdmi_set_mode(__u32 sel, __disp_tv_mode_t mode); +extern __s32 BSP_disp_set_videomode(__u32 sel, + const struct fb_videomode *mode); extern __s32 BSP_disp_hdmi_get_mode(__u32 sel); extern __s32 BSP_disp_hdmi_check_support_mode(__u32 sel, __u8 mode); extern __s32 BSP_disp_hdmi_get_hpd_status(__u32 sel); diff --git a/drivers/video/sunxi/disp/dev_fb.c b/drivers/video/sunxi/disp/dev_fb.c index 3ac43412f9176c..db7638ca23ebef 100644 --- a/drivers/video/sunxi/disp/dev_fb.c +++ b/drivers/video/sunxi/disp/dev_fb.c @@ -1052,9 +1052,20 @@ static int Fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) static int Fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { __disp_pixel_fmt_t fmt; + int dummy, sel; __inf("Fb_check_var: %dx%d %dbits\n", var->xres, var->yres, var->bits_per_pixel); + for (sel = 0; sel < 2; sel++) { + if (g_fbi.disp_init.output_type[sel] != DISP_OUTPUT_TYPE_HDMI) + continue; + + /* Check that pll is found */ + if (disp_get_pll_freq(PICOS2KHZ(var->pixclock) * + 1000, &dummy, &dummy)) + return -EINVAL; + } + switch (var->bits_per_pixel) { case 16: if (var->transp.length == 1 && var->transp.offset == 15) @@ -1104,11 +1115,26 @@ static int Fb_set_par(struct fb_info *info) (g_fbi.fb_mode[info->node] != FB_MODE_SCREEN0))) { struct fb_var_screeninfo *var = &info->var; struct fb_fix_screeninfo *fix = &info->fix; + bool mode_changed = false; __s32 layer_hdl = g_fbi.layer_hdl[info->node][sel]; __disp_layer_info_t layer_para; __u32 buffer_num = 1; __u32 y_offset = 0; + + if (g_fbi.disp_init.output_type[sel] == + DISP_OUTPUT_TYPE_HDMI) { + struct fb_videomode new_mode; + struct fb_videomode old_mode; + fb_var_to_videomode(&new_mode, var); + BSP_disp_get_videomode(sel, &old_mode); + if (!fb_mode_is_equal(&new_mode, &old_mode)) { + mode_changed = (BSP_disp_set_videomode( + sel, &new_mode) == 0); + + } + } + if (g_fbi.fb_mode[info->node] == FB_MODE_DUAL_SAME_SCREEN_TB) buffer_num = 2; @@ -1124,7 +1150,8 @@ static int Fb_set_par(struct fb_info *info) layer_para.src_win.y = var->yoffset + y_offset; layer_para.src_win.width = var->xres; layer_para.src_win.height = var->yres / buffer_num; - if (layer_para.mode != DISP_LAYER_WORK_MODE_SCALER) { + if (layer_para.mode != DISP_LAYER_WORK_MODE_SCALER || + mode_changed) { layer_para.scn_win.width = layer_para.src_win.width; layer_para.scn_win.height = diff --git a/drivers/video/sunxi/disp/disp_hdmi.c b/drivers/video/sunxi/disp/disp_hdmi.c index 9a84ac776edb0f..763218e55f8e16 100644 --- a/drivers/video/sunxi/disp/disp_hdmi.c +++ b/drivers/video/sunxi/disp/disp_hdmi.c @@ -191,6 +191,86 @@ __s32 BSP_disp_hdmi_set_mode(__u32 sel, __disp_tv_mode_t mode) return DIS_SUCCESS; } +void videomode_to_video_timing(struct __disp_video_timing *video_timing, + const struct fb_videomode *mode) +{ + memset(video_timing, 0, sizeof(struct __disp_video_timing)); + video_timing->VIC = 511; + video_timing->PCLK = (PICOS2KHZ(mode->pixclock) * 1000); + video_timing->AVI_PR = 0; + video_timing->INPUTX = mode->xres; + video_timing->INPUTY = mode->yres; + video_timing->HT = mode->xres + mode->left_margin + + mode->right_margin + mode->hsync_len; + video_timing->HBP = mode->left_margin + mode->hsync_len; + video_timing->HFP = mode->right_margin; + video_timing->HPSW = mode->hsync_len; + video_timing->VT = mode->yres + mode->upper_margin + + mode->lower_margin + mode->vsync_len; + video_timing->VBP = mode->upper_margin + mode->vsync_len; + video_timing->VFP = mode->lower_margin; + video_timing->VPSW = mode->vsync_len; + if (mode->vmode & FB_VMODE_INTERLACED) + video_timing->I = true; + + if (mode->sync & FB_SYNC_HOR_HIGH_ACT) + video_timing->HSYNC = true; + + if (mode->sync & FB_SYNC_VERT_HIGH_ACT) + video_timing->VSYNC = true; + +} + +__s32 BSP_disp_set_videomode(__u32 sel, const struct fb_videomode *mode) +{ + struct __disp_video_timing *old_video_timing = + kzalloc(sizeof(struct __disp_video_timing), GFP_KERNEL); + struct __disp_video_timing *new_video_timing = + kzalloc(sizeof(struct __disp_video_timing), GFP_KERNEL); + __disp_tv_mode_t hdmi_mode = gdisp.screen[sel].hdmi_mode; + + if (!old_video_timing && !new_video_timing) + return DIS_FAIL; + + if (!gdisp.init_para.hdmi_set_videomode) + return DIS_FAIL; + + if (gdisp.init_para.hdmi_get_video_timing(hdmi_mode, + old_video_timing) != 0) + return DIS_FAIL; + + videomode_to_video_timing(new_video_timing, mode); + + if (gdisp.init_para.hdmi_set_videomode(new_video_timing) != 0) + return DIS_FAIL; + + if (disp_clk_cfg(sel, DISP_OUTPUT_TYPE_HDMI, hdmi_mode) != 0) + goto failure; + + if (DE_BE_set_display_size(sel, new_video_timing->INPUTX, + new_video_timing->INPUTY) != 0) + goto failure; + + if (TCON1_set_hdmi_mode(sel, hdmi_mode) != 0) + goto failure; + + gdisp.screen[sel].b_out_interlace = new_video_timing->I; + + kfree(old_video_timing); + kfree(new_video_timing); + return DIS_SUCCESS; + +failure: + gdisp.init_para.hdmi_set_videomode(old_video_timing); + disp_clk_cfg(sel, DISP_OUTPUT_TYPE_HDMI, hdmi_mode); + DE_BE_set_display_size(sel, old_video_timing->INPUTX, + old_video_timing->INPUTY); + TCON1_set_hdmi_mode(sel, hdmi_mode); + kfree(old_video_timing); + kfree(new_video_timing); + return DIS_FAIL; +} + __s32 BSP_disp_hdmi_get_mode(__u32 sel) { return gdisp.screen[sel].hdmi_mode; @@ -253,6 +333,7 @@ __s32 BSP_disp_set_hdmi_func(__disp_hdmi_func *func) gdisp.init_para.Hdmi_open = func->Hdmi_open; gdisp.init_para.Hdmi_close = func->Hdmi_close; gdisp.init_para.hdmi_set_mode = func->hdmi_set_mode; + gdisp.init_para.hdmi_set_videomode = func->hdmi_set_videomode; gdisp.init_para.hdmi_mode_support = func->hdmi_mode_support; gdisp.init_para.hdmi_get_video_timing = func->hdmi_get_video_timing; gdisp.init_para.hdmi_get_HPD_status = func->hdmi_get_HPD_status; diff --git a/drivers/video/sunxi/disp/disp_lcd.c b/drivers/video/sunxi/disp/disp_lcd.c index 332c3366633fde..a25ec4abf584f7 100644 --- a/drivers/video/sunxi/disp/disp_lcd.c +++ b/drivers/video/sunxi/disp/disp_lcd.c @@ -1818,6 +1818,79 @@ void LCD_set_panel_funs(__lcd_panel_fun_t *lcd0_cfg, } EXPORT_SYMBOL(LCD_set_panel_funs); +__s32 BSP_disp_get_videomode(__u32 sel, struct fb_videomode *videomode) +{ + __disp_tcon_timing_t tt; + bool interlaced, hsync, vsync = false; + u32 pixclock, hfreq, htotal, vtotal; + memset(videomode, 0, sizeof(struct fb_videomode)); + + if (BSP_disp_get_timing(sel, &tt) != 0) + return DIS_FAIL; + + if (gdisp.screen[sel].status & LCD_ON) { + interlaced = false; + } else if ((gdisp.screen[sel].status & TV_ON)) { + interlaced = Disp_get_screen_scan_mode( + gdisp.screen[sel].tv_mode); + } else if (gdisp.screen[sel].status & HDMI_ON) { + struct __disp_video_timing video_timing; + __disp_tv_mode_t hdmi_mode = gdisp.screen[sel].hdmi_mode; + if (gdisp.init_para.hdmi_get_video_timing( + hdmi_mode, &video_timing) != 0) + return DIS_FAIL; + + interlaced = video_timing.I; + hsync = video_timing.HSYNC; + vsync = video_timing.VSYNC; + } else if (gdisp.screen[sel].status & VGA_ON) { + interlaced = Disp_get_screen_scan_mode( + gdisp.screen[sel].vga_mode); + } else { + DE_INF("get videomode fail because device is not output !\n"); + return DIS_FAIL; + } + + videomode->xres = BSP_disp_get_screen_width(sel); + videomode->yres = BSP_disp_get_screen_height(sel); + videomode->pixclock = KHZ2PICOS(tt.pixel_clk); + videomode->left_margin = tt.hor_back_porch; + videomode->right_margin = tt.hor_front_porch; + videomode->upper_margin = tt.ver_back_porch; + videomode->lower_margin = tt.ver_front_porch; + videomode->hsync_len = tt.hor_sync_time; + videomode->vsync_len = tt.ver_sync_time; + + + if (interlaced) + videomode->vmode = FB_VMODE_INTERLACED; + + if (vsync) + videomode->sync = FB_SYNC_VERT_HIGH_ACT; + + if (hsync) + videomode->sync |= FB_SYNC_HOR_HIGH_ACT; + + if (!videomode->pixclock) + return DIS_SUCCESS; + + pixclock = PICOS2KHZ(videomode->pixclock) * 1000; + + htotal = videomode->xres + videomode->right_margin + + videomode->hsync_len + videomode->left_margin; + vtotal = videomode->yres + videomode->lower_margin + + videomode->vsync_len + videomode->upper_margin; + + if (videomode->vmode & FB_VMODE_INTERLACED) + vtotal /= 2; + if (videomode->vmode & FB_VMODE_DOUBLE) + vtotal *= 2; + + hfreq = pixclock/htotal; + videomode->refresh = hfreq/vtotal; + return DIS_SUCCESS; +} + __s32 BSP_disp_get_timing(__u32 sel, __disp_tcon_timing_t *tt) { memset(tt, 0, sizeof(__disp_tcon_timing_t)); diff --git a/drivers/video/sunxi/hdmi/drv_hdmi.c b/drivers/video/sunxi/hdmi/drv_hdmi.c index e8673770cb5bc5..7eb0987c8e2fb1 100644 --- a/drivers/video/sunxi/hdmi/drv_hdmi.c +++ b/drivers/video/sunxi/hdmi/drv_hdmi.c @@ -163,6 +163,27 @@ __s32 Hdmi_set_display_mode(__disp_tv_mode_t mode) return 0; } +__s32 Hdmi_set_display_videomode(const struct __disp_video_timing *mode) +{ + __inf("[Hdmi_set_display_videomode]\n"); + + if (video_mode != HDMI_EDID) + return -1; + + if (memcmp(mode, &video_timing[video_timing_edid], + sizeof(struct __disp_video_timing)) != 0) { + + if (hdmi_state >= HDMI_State_Video_config) + hdmi_state = HDMI_State_Video_config; + + memcpy(&video_timing[video_timing_edid], mode, + sizeof(struct __disp_video_timing)); + + } + + return 0; +} + __s32 Hdmi_Audio_Enable(__u8 mode, __u8 channel) { __inf("[Hdmi_Audio_Enable],ch:%d\n", channel); @@ -316,6 +337,7 @@ __s32 Hdmi_init(void) disp_func.Hdmi_open = Hdmi_open; disp_func.Hdmi_close = Hdmi_close; disp_func.hdmi_set_mode = Hdmi_set_display_mode; + disp_func.hdmi_set_videomode = Hdmi_set_display_videomode; disp_func.hdmi_mode_support = Hdmi_mode_support; disp_func.hdmi_get_video_timing = hdmi_get_video_timing; disp_func.hdmi_get_HPD_status = Hdmi_get_HPD_status; diff --git a/include/video/sunxi_disp_ioctl.h b/include/video/sunxi_disp_ioctl.h index 295b8d853fa6e9..36fc7d3f0015c5 100644 --- a/include/video/sunxi_disp_ioctl.h +++ b/include/video/sunxi_disp_ioctl.h @@ -452,6 +452,7 @@ typedef struct { __s32(*Hdmi_open) (void); __s32(*Hdmi_close) (void); __s32(*hdmi_set_mode) (__disp_tv_mode_t mode); + __s32(*hdmi_set_videomode) (const struct __disp_video_timing *mode); __s32(*hdmi_mode_support) (__disp_tv_mode_t mode); __s32(*hdmi_get_video_timing) (__disp_tv_mode_t mode, struct __disp_video_timing *video_timing);