From 2dae800f1e6193b64ba587f511c4878c89409cbe Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 21 Dec 2014 16:28:32 +0100 Subject: sunxi: video: Add lcd output support Add lcd output support, see the new Kconfig entries and doc/README.video for how to enable / configure this. Signed-off-by: Hans de Goede Acked-by: Ian Campbell --- drivers/video/sunxi_display.c | 180 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 171 insertions(+), 9 deletions(-) (limited to 'drivers/video/sunxi_display.c') diff --git a/drivers/video/sunxi_display.c b/drivers/video/sunxi_display.c index ad38f16..46346ee 100644 --- a/drivers/video/sunxi_display.c +++ b/drivers/video/sunxi_display.c @@ -11,7 +11,9 @@ #include #include +#include #include +#include #include #include #include @@ -34,6 +36,7 @@ struct sunxi_display { GraphicDevice graphic_device; bool enabled; enum sunxi_monitor monitor; + unsigned int depth; } sunxi_display; /* @@ -435,6 +438,134 @@ static void sunxi_lcdc_enable(void) setbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_TCON_ENABLE); } +static void sunxi_lcdc_panel_enable(void) +{ + int pin; + + /* + * Start with backlight disabled to avoid the screen flashing to + * white while the lcd inits. + */ + pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_EN); + if (pin != -1) { + gpio_request(pin, "lcd_backlight_enable"); + gpio_direction_output(pin, 0); + } + + pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_PWM); + if (pin != -1) { + gpio_request(pin, "lcd_backlight_pwm"); + /* backlight pwm is inverted, set to 1 to disable backlight */ + gpio_direction_output(pin, 1); + } + + /* Give the backlight some time to turn off and power up the panel. */ + mdelay(40); + pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_POWER); + if (pin != -1) { + gpio_request(pin, "lcd_power"); + gpio_direction_output(pin, 1); + } +} + +static void sunxi_lcdc_backlight_enable(void) +{ + int pin; + + /* + * We want to have scanned out at least one frame before enabling the + * backlight to avoid the screen flashing to white when we enable it. + */ + mdelay(40); + + pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_EN); + if (pin != -1) + gpio_direction_output(pin, 1); + + pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_PWM); + if (pin != -1) { + /* backlight pwm is inverted, set to 0 to enable backlight */ + gpio_direction_output(pin, 0); + } +} + +static int sunxi_lcdc_get_clk_delay(const struct ctfb_res_modes *mode) +{ + int delay; + + delay = mode->lower_margin + mode->vsync_len + mode->upper_margin - 2; + return (delay > 30) ? 30 : delay; +} + +static void sunxi_lcdc_tcon0_mode_set(const struct ctfb_res_modes *mode) +{ + struct sunxi_lcdc_reg * const lcdc = + (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE; + int bp, clk_delay, clk_div, clk_double, pin, total, val; + + for (pin = SUNXI_GPD(0); pin <= SUNXI_GPD(27); pin++) + sunxi_gpio_set_cfgpin(pin, SUNXI_GPD0_LCD0); + + sunxi_lcdc_pll_set(0, mode->pixclock_khz, &clk_div, &clk_double); + + /* Use tcon0 */ + clrsetbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_IO_MAP_MASK, + SUNXI_LCDC_CTRL_IO_MAP_TCON0); + + clk_delay = sunxi_lcdc_get_clk_delay(mode); + writel(SUNXI_LCDC_TCON0_CTRL_ENABLE | + SUNXI_LCDC_TCON0_CTRL_CLK_DELAY(clk_delay), &lcdc->tcon0_ctrl); + + writel(SUNXI_LCDC_TCON0_DCLK_ENABLE | + SUNXI_LCDC_TCON0_DCLK_DIV(clk_div), &lcdc->tcon0_dclk); + + writel(SUNXI_LCDC_X(mode->xres) | SUNXI_LCDC_Y(mode->yres), + &lcdc->tcon0_timing_active); + + bp = mode->hsync_len + mode->left_margin; + total = mode->xres + mode->right_margin + bp; + writel(SUNXI_LCDC_TCON0_TIMING_H_TOTAL(total) | + SUNXI_LCDC_TCON0_TIMING_H_BP(bp), &lcdc->tcon0_timing_h); + + bp = mode->vsync_len + mode->upper_margin; + total = mode->yres + mode->lower_margin + bp; + writel(SUNXI_LCDC_TCON0_TIMING_V_TOTAL(total) | + SUNXI_LCDC_TCON0_TIMING_V_BP(bp), &lcdc->tcon0_timing_v); + + writel(SUNXI_LCDC_X(mode->hsync_len) | SUNXI_LCDC_Y(mode->vsync_len), + &lcdc->tcon0_timing_sync); + + /* We only support hv-sync parallel lcd-s for now */ + writel(0, &lcdc->tcon0_hv_intf); + writel(0, &lcdc->tcon0_cpu_intf); + + if (sunxi_display.depth == 18 || sunxi_display.depth == 16) { + writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[0]); + writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[1]); + writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[2]); + writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[3]); + writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[4]); + writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[5]); + writel(SUNXI_LCDC_TCON0_FRM_TAB0, &lcdc->tcon0_frm_table[0]); + writel(SUNXI_LCDC_TCON0_FRM_TAB1, &lcdc->tcon0_frm_table[1]); + writel(SUNXI_LCDC_TCON0_FRM_TAB2, &lcdc->tcon0_frm_table[2]); + writel(SUNXI_LCDC_TCON0_FRM_TAB3, &lcdc->tcon0_frm_table[3]); + writel(((sunxi_display.depth == 18) ? + SUNXI_LCDC_TCON0_FRM_CTRL_RGB666 : + SUNXI_LCDC_TCON0_FRM_CTRL_RGB565), + &lcdc->tcon0_frm_ctrl); + } + + val = 0; + if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT)) + val |= SUNXI_LCDC_TCON_HSYNC_MASK; + if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT)) + val |= SUNXI_LCDC_TCON_VSYNC_MASK; + writel(val, &lcdc->tcon0_io_polarity); + + writel(0, &lcdc->tcon0_io_tristate); +} + static void sunxi_lcdc_tcon1_mode_set(const struct ctfb_res_modes *mode, int *clk_div, int *clk_double) { @@ -618,7 +749,12 @@ static void sunxi_mode_set(const struct ctfb_res_modes *mode, } break; case sunxi_monitor_lcd: - /* TODO */ + sunxi_lcdc_panel_enable(); + sunxi_composer_mode_set(mode, address); + sunxi_lcdc_tcon0_mode_set(mode); + sunxi_composer_enable(); + sunxi_lcdc_enable(); + sunxi_lcdc_backlight_enable(); break; case sunxi_monitor_vga: break; @@ -641,11 +777,11 @@ void *video_hw_init(void) { static GraphicDevice *graphic_device = &sunxi_display.graphic_device; const struct ctfb_res_modes *mode; - struct ctfb_res_modes edid_mode; + struct ctfb_res_modes custom; const char *options; - unsigned int depth; int i, ret, hpd, edid; char mon[16]; + char *lcd_mode = CONFIG_VIDEO_LCD_MODE; memset(&sunxi_display, 0, sizeof(struct sunxi_display)); @@ -653,7 +789,8 @@ void *video_hw_init(void) CONFIG_SUNXI_FB_SIZE >> 10); gd->fb_base = gd->ram_top; - video_get_ctfb_res_modes(RES_MODE_1024x768, 24, &mode, &depth, &options); + video_get_ctfb_res_modes(RES_MODE_1024x768, 24, &mode, + &sunxi_display.depth, &options); hpd = video_get_option_int(options, "hpd", 1); edid = video_get_option_int(options, "edid", 1); sunxi_display.monitor = sunxi_monitor_dvi; @@ -678,16 +815,26 @@ void *video_hw_init(void) ret = sunxi_hdmi_hpd_detect(); if (ret) { printf("HDMI connected: "); - if (edid && sunxi_hdmi_edid_get_mode(&edid_mode) == 0) - mode = &edid_mode; + if (edid && sunxi_hdmi_edid_get_mode(&custom) == 0) + mode = &custom; break; } if (!hpd) break; /* User has requested to ignore hpd */ sunxi_hdmi_shutdown(); - return NULL; + + if (lcd_mode[0] == 0) + return NULL; /* No LCD, bail */ + + /* Fall back / through to LCD */ + sunxi_display.monitor = sunxi_monitor_lcd; case sunxi_monitor_lcd: + if (lcd_mode[0]) { + sunxi_display.depth = video_get_params(&custom, lcd_mode); + mode = &custom; + break; + } printf("LCD not supported on this board\n"); return NULL; case sunxi_monitor_vga: @@ -729,16 +876,31 @@ int sunxi_simplefb_setup(void *blob) { static GraphicDevice *graphic_device = &sunxi_display.graphic_device; int offset, ret; + const char *pipeline = NULL; if (!sunxi_display.enabled) return 0; - /* Find a framebuffer node, with pipeline == "de_be0-lcd0-hdmi" */ + switch (sunxi_display.monitor) { + case sunxi_monitor_none: + return 0; + case sunxi_monitor_dvi: + case sunxi_monitor_hdmi: + pipeline = "de_be0-lcd0-hdmi"; + break; + case sunxi_monitor_lcd: + pipeline = "de_be0-lcd0"; + break; + case sunxi_monitor_vga: + break; + } + + /* Find a prefilled simpefb node, matching out pipeline config */ offset = fdt_node_offset_by_compatible(blob, -1, "allwinner,simple-framebuffer"); while (offset >= 0) { ret = fdt_find_string(blob, offset, "allwinner,pipeline", - "de_be0-lcd0-hdmi"); + pipeline); if (ret == 0) break; offset = fdt_node_offset_by_compatible(blob, offset, -- cgit v1.1