diff mbox

[U-Boot,v2,1/2] sunxi: video: Add lcd output support

Message ID 1420027641-11613-1-git-send-email-hdegoede@redhat.com
State Accepted
Delegated to: Ian Campbell
Headers show

Commit Message

Hans de Goede Dec. 31, 2014, 12:07 p.m. UTC
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 <hdegoede@redhat.com>
---
Changes in v2:
-Do not request backlight gpio twices
-Fix some spelling errors in comments
-Fix some no longer accurate comments
---
 arch/arm/include/asm/arch-sunxi/display.h |  25 ++++-
 arch/arm/include/asm/arch-sunxi/gpio.h    |   2 +
 board/sunxi/Kconfig                       |  43 ++++++-
 doc/README.video                          |  50 +++++++--
 drivers/video/sunxi_display.c             | 179 ++++++++++++++++++++++++++++--
 5 files changed, 272 insertions(+), 27 deletions(-)

Comments

Siarhei Siamashka Jan. 1, 2015, 8:21 p.m. UTC | #1
On Wed, 31 Dec 2014 13:07:20 +0100
Hans de Goede <hdegoede@redhat.com> wrote:

> 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 <hdegoede@redhat.com>
> ---
> Changes in v2:
> -Do not request backlight gpio twices
> -Fix some spelling errors in comments
> -Fix some no longer accurate comments
> ---
>  arch/arm/include/asm/arch-sunxi/display.h |  25 ++++-
>  arch/arm/include/asm/arch-sunxi/gpio.h    |   2 +
>  board/sunxi/Kconfig                       |  43 ++++++-
>  doc/README.video                          |  50 +++++++--
>  drivers/video/sunxi_display.c             | 179 ++++++++++++++++++++++++++++--
>  5 files changed, 272 insertions(+), 27 deletions(-)
> 
> diff --git a/arch/arm/include/asm/arch-sunxi/display.h b/arch/arm/include/asm/arch-sunxi/display.h
> index 00e3466..dcb2fe4 100644
> --- a/arch/arm/include/asm/arch-sunxi/display.h
> +++ b/arch/arm/include/asm/arch-sunxi/display.h
> @@ -57,14 +57,13 @@ struct sunxi_lcdc_reg {
>  	u32 int0;			/* 0x04 */
>  	u32 int1;			/* 0x08 */
>  	u8 res0[0x04];			/* 0x0c */
> -	u32 frame_ctrl;			/* 0x10 */
> -	u8 res1[0x2c];			/* 0x14 */
> +	u32 frame_ctrl[12];		/* 0x10 */
>  	u32 tcon0_ctrl;			/* 0x40 */
>  	u32 tcon0_dclk;			/* 0x44 */
> -	u32 tcon0_basic_timing0;	/* 0x48 */
> -	u32 tcon0_basic_timing1;	/* 0x4c */
> -	u32 tcon0_basic_timing2;	/* 0x50 */
> -	u32 tcon0_basic_timing3;	/* 0x54 */
> +	u32 tcon0_timing_active;	/* 0x48 */
> +	u32 tcon0_timing_h;		/* 0x4c */
> +	u32 tcon0_timing_v;		/* 0x50 */
> +	u32 tcon0_timing_sync;		/* 0x54 */
>  	u32 tcon0_hv_intf;		/* 0x58 */
>  	u8 res2[0x04];			/* 0x5c */
>  	u32 tcon0_cpu_intf;		/* 0x60 */
> @@ -179,7 +178,21 @@ struct sunxi_hdmi_reg {
>  #define SUNXI_LCDC_CTRL_IO_MAP_TCON0		(0 << 0)
>  #define SUNXI_LCDC_CTRL_IO_MAP_TCON1		(1 << 0)
>  #define SUNXI_LCDC_CTRL_TCON_ENABLE		(1 << 31)
> +#define SUNXI_LCDC_FRAME_CTRL0_RGB666		((1 << 31) | (0 << 4))
> +#define SUNXI_LCDC_FRAME_CTRL0_RGB656		((1 << 31) | (5 << 4))

This would be probably SUNXI_LCDC_FRAME_CTRL0_RGB565 according to
the Allwinner documentation of the TCON0_FRM_CTL_REG register:

 0: 6bit frm output
 1: 5bit frm output

Since we have 5 there (101 bit pattern), it simply means RGB565.

> +#define SUNXI_LCDC_FRAME_CTRL_DITHER0		0x11111111
> +#define SUNXI_LCDC_FRAME_CTRL_DITHER1		0x01010000
> +#define SUNXI_LCDC_FRAME_CTRL_DITHER2		0x15151111
> +#define SUNXI_LCDC_FRAME_CTRL_DITHER3		0x57575555
> +#define SUNXI_LCDC_FRAME_CTRL_DITHER4		0x7f7f7777
> +#define SUNXI_LCDC_TCON0_CTRL_CLK_DELAY(n)	(((n) & 0x1f) << 4)
> +#define SUNXI_LCDC_TCON0_CTRL_ENABLE		(1 << 31)
> +#define SUNXI_LCDC_TCON0_DCLK_DIV(n)		((n) << 0)
>  #define SUNXI_LCDC_TCON0_DCLK_ENABLE		(0xf << 28)
> +#define SUNXI_LCDC_TCON0_TIMING_H_BP(n)		(((n) - 1) << 0)
> +#define SUNXI_LCDC_TCON0_TIMING_H_TOTAL(n)	(((n) - 1) << 16)
> +#define SUNXI_LCDC_TCON0_TIMING_V_BP(n)		(((n) - 1) << 0)
> +#define SUNXI_LCDC_TCON0_TIMING_V_TOTAL(n)	(((n) * 2) << 16)
>  #define SUNXI_LCDC_TCON1_CTRL_CLK_DELAY(n)	(((n) & 0x1f) << 4)
>  #define SUNXI_LCDC_TCON1_CTRL_ENABLE		(1 << 31)
>  #define SUNXI_LCDC_TCON1_TIMING_H_BP(n)		(((n) - 1) << 0)
> diff --git a/arch/arm/include/asm/arch-sunxi/gpio.h b/arch/arm/include/asm/arch-sunxi/gpio.h
> index 32941cb..9438f5a 100644
> --- a/arch/arm/include/asm/arch-sunxi/gpio.h
> +++ b/arch/arm/include/asm/arch-sunxi/gpio.h
> @@ -150,6 +150,8 @@ enum sunxi_gpio_number {
>  
>  #define SUNXI_GPC6_SDC2		3
>  
> +#define SUNXI_GPD0_LCD0		2
> +
>  #define SUNXI_GPF0_SDC0		2
>  
>  #define SUNXI_GPF2_SDC0		2
> diff --git a/board/sunxi/Kconfig b/board/sunxi/Kconfig
> index 72c0165..6f2377d 100644
> --- a/board/sunxi/Kconfig
> +++ b/board/sunxi/Kconfig
> @@ -281,17 +281,52 @@ config USB2_VBUS_PIN
>  	See USB1_VBUS_PIN help text.
>  
>  config VIDEO
> -	boolean "Enable graphical uboot console on HDMI"
> +	boolean "Enable graphical uboot console on HDMI, LCD or VGA"
>  	default y
>  	---help---
> -	Say Y here to add support for using a cfb console on the HDMI output
> -	found on most sunxi devices.
> +	Say Y here to add support for using a cfb console on the HDMI, LCD
> +	or VGA output found on most sunxi devices. See doc/README.video for
> +	info on how to select the video output and mode.
> +
> +config VIDEO_LCD_MODE
> +	string "LCD panel timing details"
> +	depends on VIDEO
> +	default ""
> +	---help---
> +	LCD panel timing details string, leave empty if there is no LCD panel.
> +	This is in drivers/video/videomodes.c: video_get_params() format, e.g.
> +	x:800,y:480,depth:18,pclk_khz:33000,le:16,ri:209,up:22,lo:22,hs:30,vs:1,sync:0,vmode:0
> +
> +config VIDEO_LCD_POWER
> +	string "LCD panel power enable pin"
> +	depends on VIDEO
> +	default ""
> +	---help---
> +	Set the power enable pin for the LCD panel. This takes a string in the
> +	format understood by sunxi_name_to_gpio, e.g. PH1 for pin 1 of port H.
> +
> +config VIDEO_LCD_BL_EN
> +	string "LCD panel backlight enable pin"
> +	depends on VIDEO
> +	default ""
> +	---help---
> +	Set the backlight enable pin for the LCD panel. This takes a string in the
> +	the format understood by sunxi_name_to_gpio, e.g. PH1 for pin 1 of
> +	port H.
> +
> +config VIDEO_LCD_BL_PWM
> +	string "LCD panel backlight pwm pin"
> +	depends on VIDEO
> +	default ""
> +	---help---
> +	Set the backlight pwm pin for the LCD panel. This takes a string in the
> +	format understood by sunxi_name_to_gpio, e.g. PH1 for pin 1 of port H.
>  
>  config USB_KEYBOARD
>  	boolean "Enable USB keyboard support"
>  	default y
>  	---help---
>  	Say Y here to add support for using a USB keyboard (typically used
> -	in combination with a graphical console on HDMI).
> +	in combination with a graphical console).
>  
>  endif
> diff --git a/doc/README.video b/doc/README.video
> index dadbfcd..cfe6318 100644
> --- a/doc/README.video
> +++ b/doc/README.video
> @@ -5,15 +5,8 @@
>   * SPDX-License-Identifier:	GPL-2.0+
>   */
>  
> -U-Boot MPC8xx video controller driver
> -======================================
> -
> -The driver has been tested with the following configurations:
> -
> -- MPC823FADS with AD7176 on a PAL TV (YCbYCr)	- arsenio@tin.it
> -
>  "video-mode" environment variable
> -===============================
> +=================================
>  
>  The 'video-mode' environment variable can be used to enable and configure
>  some video drivers.  The format matches the video= command-line option used
> @@ -28,4 +21,45 @@ for Linux:
>  	<freq>		The frequency (in Hz) to use.
>  	<options>	A comma-separated list of device-specific options
>  
> +
> +U-Boot MPC8xx video controller driver
> +=====================================
> +
> +The driver has been tested with the following configurations:
> +
> +- MPC823FADS with AD7176 on a PAL TV (YCbYCr)	- arsenio@tin.it
> +
>  Example: video-mode=fslfb:1280x1024-32@60,monitor=dvi
> +
> +
> +U-boot sunxi video controller driver
> +====================================
> +
> +U-boot supports hdmi and lcd output on Allwinner sunxi SoCs, lcd output
> +requires the CONFIG_VIDEO_LCD_MODE Kconfig value to be set.
> +
> +The sunxi u-boot driver supports the following video-mode options:
> +
> +- monitor=[none|dvi|hdmi|lcd] - Select the video output to use
> + none:     Disable video output.
> + dvi/hdmi: Selects output over the hdmi connector with dvi resp. hdmi output
> +           format, if edid is used the format is automatically selected.
> + lcd:      Selects video output to a LCD screen.
> + vga:      Selects bideo output over the VGA connector.
> + Defaults to monitor=dvi.
> +
> +- hpd=[0|1] - Enable use of the hdmi HotPlug Detect feature
> + 0: Disabled. Configure dvi/hdmi output even if no cable is detected
> + 1: Enabled. If a LCD has been configured fallback to the LCD when no cable is
> +    detected, if no LCD is configured, disable video ouput.
> + Defaults to hpd=1.
> +
> +- edid=[0|1] - Enable use of DDC + EDID to get monitor info
> + 0: Disabled.
> + 1: Enabled. If valid EDID info was read from the monitor the EDID info will
> +    overrides the xres, yres and refresh from the video-mode env. variable.
> + Defaults to edid=1.
> +
> +For example to always use the hdmi connector, even if no cable is inserted,
> +using edid info when available and otherwise initalizing it at 1024x768@60Hz,
> +use: video-mode=sunxi:1024x768-24@60,monitor=dvi,hpd=0,edid=1 .
> diff --git a/drivers/video/sunxi_display.c b/drivers/video/sunxi_display.c
> index ad38f16..fee1474 100644
> --- a/drivers/video/sunxi_display.c
> +++ b/drivers/video/sunxi_display.c
> @@ -11,7 +11,9 @@
>  
>  #include <asm/arch/clock.h>
>  #include <asm/arch/display.h>
> +#include <asm/arch/gpio.h>
>  #include <asm/global_data.h>
> +#include <asm/gpio.h>
>  #include <asm/io.h>
>  #include <errno.h>
>  #include <fdtdec.h>
> @@ -34,6 +36,7 @@ struct sunxi_display {
>  	GraphicDevice graphic_device;
>  	bool enabled;
>  	enum sunxi_monitor monitor;
> +	unsigned int depth;
>  } sunxi_display;
>  
>  /*
> @@ -435,6 +438,133 @@ 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;
> +
> +	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 == 17) {

Here 17 is not quite correct for RGB565.

Also these are dithering settings, and this code just unconditionally
enables the right dithering for 16-bit LCD displays (this seems to
be never used in any real device from the sunxi-boards repository) or
18-bit LCD displays, which are very common.

Because 32-bit framebuffers support more colors than the 18-bit LCD
hardware can show, dithering is needed and used:
    http://en.wikipedia.org/wiki/Frame_rate_control

Do we want to have a separate option to enable/disable dithering? Or
just keep it always enabled until somebody complains?

A simple program for testing dithering effects/usefulness can be found
here:
    http://lists.denx.de/pipermail/u-boot/2015-January/200031.html

> +		writel(SUNXI_LCDC_FRAME_CTRL_DITHER0, &lcdc->frame_ctrl[1]);
> +		writel(SUNXI_LCDC_FRAME_CTRL_DITHER0, &lcdc->frame_ctrl[2]);
> +		writel(SUNXI_LCDC_FRAME_CTRL_DITHER0, &lcdc->frame_ctrl[3]);
> +		writel(SUNXI_LCDC_FRAME_CTRL_DITHER0, &lcdc->frame_ctrl[4]);
> +		writel(SUNXI_LCDC_FRAME_CTRL_DITHER0, &lcdc->frame_ctrl[5]);
> +		writel(SUNXI_LCDC_FRAME_CTRL_DITHER0, &lcdc->frame_ctrl[6]);
> +		writel(SUNXI_LCDC_FRAME_CTRL_DITHER1, &lcdc->frame_ctrl[7]);
> +		writel(SUNXI_LCDC_FRAME_CTRL_DITHER2, &lcdc->frame_ctrl[8]);
> +		writel(SUNXI_LCDC_FRAME_CTRL_DITHER3, &lcdc->frame_ctrl[9]);
> +		writel(SUNXI_LCDC_FRAME_CTRL_DITHER4, &lcdc->frame_ctrl[10]);
> +		writel(((sunxi_display.depth == 18) ?
> +			SUNXI_LCDC_FRAME_CTRL0_RGB666 :
> +			SUNXI_LCDC_FRAME_CTRL0_RGB656),
> +		       &lcdc->frame_ctrl[0]);
> +	}
> +
> +	/*
> +	 * Bit 24 and 25 of tcon0_io_polarity can be used to invert hsync /
> +	 * vsync polarity, but this leads to noise problems, so we always
> +	 * keep the polarity positive.
> +	 */
> +	writel(0, &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 +748,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 +776,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 +788,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 +814,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 +875,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,
Hans de Goede Jan. 5, 2015, 4:21 p.m. UTC | #2
Hi,

On 01-01-15 21:21, Siarhei Siamashka wrote:
> On Wed, 31 Dec 2014 13:07:20 +0100
> Hans de Goede <hdegoede@redhat.com> wrote:
>
>> 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 <hdegoede@redhat.com>

<snip>

>> @@ -179,7 +178,21 @@ struct sunxi_hdmi_reg {
>>   #define SUNXI_LCDC_CTRL_IO_MAP_TCON0		(0 << 0)
>>   #define SUNXI_LCDC_CTRL_IO_MAP_TCON1		(1 << 0)
>>   #define SUNXI_LCDC_CTRL_TCON_ENABLE		(1 << 31)
>> +#define SUNXI_LCDC_FRAME_CTRL0_RGB666		((1 << 31) | (0 << 4))
>> +#define SUNXI_LCDC_FRAME_CTRL0_RGB656		((1 << 31) | (5 << 4))
>
> This would be probably SUNXI_LCDC_FRAME_CTRL0_RGB565 according to
> the Allwinner documentation of the TCON0_FRM_CTL_REG register:
>
>   0: 6bit frm output
>   1: 5bit frm output
>
> Since we have 5 there (101 bit pattern), it simply means RGB565.

You're completely right, I took over the weird RGB656 naming from the
allwinner kernel code without thinking things through properly.


<snip>

>> +	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 == 17) {
>
> Here 17 is not quite correct for RGB565.

Right, this should be 16 meaning plain old RGB565, I've fixed both in
my personal tree. Thanks for pointing this out!



>
> Also these are dithering settings, and this code just unconditionally
> enables the right dithering for 16-bit LCD displays (this seems to
> be never used in any real device from the sunxi-boards repository) or
> 18-bit LCD displays, which are very common.
>
> Because 32-bit framebuffers support more colors than the 18-bit LCD
> hardware can show, dithering is needed and used:
>      http://en.wikipedia.org/wiki/Frame_rate_control
>
> Do we want to have a separate option to enable/disable dithering? Or
> just keep it always enabled until somebody complains?
>
> A simple program for testing dithering effects/usefulness can be found
> here:
>      http://lists.denx.de/pipermail/u-boot/2015-January/200031.html

<snip>

Regards,

Hans
diff mbox

Patch

diff --git a/arch/arm/include/asm/arch-sunxi/display.h b/arch/arm/include/asm/arch-sunxi/display.h
index 00e3466..dcb2fe4 100644
--- a/arch/arm/include/asm/arch-sunxi/display.h
+++ b/arch/arm/include/asm/arch-sunxi/display.h
@@ -57,14 +57,13 @@  struct sunxi_lcdc_reg {
 	u32 int0;			/* 0x04 */
 	u32 int1;			/* 0x08 */
 	u8 res0[0x04];			/* 0x0c */
-	u32 frame_ctrl;			/* 0x10 */
-	u8 res1[0x2c];			/* 0x14 */
+	u32 frame_ctrl[12];		/* 0x10 */
 	u32 tcon0_ctrl;			/* 0x40 */
 	u32 tcon0_dclk;			/* 0x44 */
-	u32 tcon0_basic_timing0;	/* 0x48 */
-	u32 tcon0_basic_timing1;	/* 0x4c */
-	u32 tcon0_basic_timing2;	/* 0x50 */
-	u32 tcon0_basic_timing3;	/* 0x54 */
+	u32 tcon0_timing_active;	/* 0x48 */
+	u32 tcon0_timing_h;		/* 0x4c */
+	u32 tcon0_timing_v;		/* 0x50 */
+	u32 tcon0_timing_sync;		/* 0x54 */
 	u32 tcon0_hv_intf;		/* 0x58 */
 	u8 res2[0x04];			/* 0x5c */
 	u32 tcon0_cpu_intf;		/* 0x60 */
@@ -179,7 +178,21 @@  struct sunxi_hdmi_reg {
 #define SUNXI_LCDC_CTRL_IO_MAP_TCON0		(0 << 0)
 #define SUNXI_LCDC_CTRL_IO_MAP_TCON1		(1 << 0)
 #define SUNXI_LCDC_CTRL_TCON_ENABLE		(1 << 31)
+#define SUNXI_LCDC_FRAME_CTRL0_RGB666		((1 << 31) | (0 << 4))
+#define SUNXI_LCDC_FRAME_CTRL0_RGB656		((1 << 31) | (5 << 4))
+#define SUNXI_LCDC_FRAME_CTRL_DITHER0		0x11111111
+#define SUNXI_LCDC_FRAME_CTRL_DITHER1		0x01010000
+#define SUNXI_LCDC_FRAME_CTRL_DITHER2		0x15151111
+#define SUNXI_LCDC_FRAME_CTRL_DITHER3		0x57575555
+#define SUNXI_LCDC_FRAME_CTRL_DITHER4		0x7f7f7777
+#define SUNXI_LCDC_TCON0_CTRL_CLK_DELAY(n)	(((n) & 0x1f) << 4)
+#define SUNXI_LCDC_TCON0_CTRL_ENABLE		(1 << 31)
+#define SUNXI_LCDC_TCON0_DCLK_DIV(n)		((n) << 0)
 #define SUNXI_LCDC_TCON0_DCLK_ENABLE		(0xf << 28)
+#define SUNXI_LCDC_TCON0_TIMING_H_BP(n)		(((n) - 1) << 0)
+#define SUNXI_LCDC_TCON0_TIMING_H_TOTAL(n)	(((n) - 1) << 16)
+#define SUNXI_LCDC_TCON0_TIMING_V_BP(n)		(((n) - 1) << 0)
+#define SUNXI_LCDC_TCON0_TIMING_V_TOTAL(n)	(((n) * 2) << 16)
 #define SUNXI_LCDC_TCON1_CTRL_CLK_DELAY(n)	(((n) & 0x1f) << 4)
 #define SUNXI_LCDC_TCON1_CTRL_ENABLE		(1 << 31)
 #define SUNXI_LCDC_TCON1_TIMING_H_BP(n)		(((n) - 1) << 0)
diff --git a/arch/arm/include/asm/arch-sunxi/gpio.h b/arch/arm/include/asm/arch-sunxi/gpio.h
index 32941cb..9438f5a 100644
--- a/arch/arm/include/asm/arch-sunxi/gpio.h
+++ b/arch/arm/include/asm/arch-sunxi/gpio.h
@@ -150,6 +150,8 @@  enum sunxi_gpio_number {
 
 #define SUNXI_GPC6_SDC2		3
 
+#define SUNXI_GPD0_LCD0		2
+
 #define SUNXI_GPF0_SDC0		2
 
 #define SUNXI_GPF2_SDC0		2
diff --git a/board/sunxi/Kconfig b/board/sunxi/Kconfig
index 72c0165..6f2377d 100644
--- a/board/sunxi/Kconfig
+++ b/board/sunxi/Kconfig
@@ -281,17 +281,52 @@  config USB2_VBUS_PIN
 	See USB1_VBUS_PIN help text.
 
 config VIDEO
-	boolean "Enable graphical uboot console on HDMI"
+	boolean "Enable graphical uboot console on HDMI, LCD or VGA"
 	default y
 	---help---
-	Say Y here to add support for using a cfb console on the HDMI output
-	found on most sunxi devices.
+	Say Y here to add support for using a cfb console on the HDMI, LCD
+	or VGA output found on most sunxi devices. See doc/README.video for
+	info on how to select the video output and mode.
+
+config VIDEO_LCD_MODE
+	string "LCD panel timing details"
+	depends on VIDEO
+	default ""
+	---help---
+	LCD panel timing details string, leave empty if there is no LCD panel.
+	This is in drivers/video/videomodes.c: video_get_params() format, e.g.
+	x:800,y:480,depth:18,pclk_khz:33000,le:16,ri:209,up:22,lo:22,hs:30,vs:1,sync:0,vmode:0
+
+config VIDEO_LCD_POWER
+	string "LCD panel power enable pin"
+	depends on VIDEO
+	default ""
+	---help---
+	Set the power enable pin for the LCD panel. This takes a string in the
+	format understood by sunxi_name_to_gpio, e.g. PH1 for pin 1 of port H.
+
+config VIDEO_LCD_BL_EN
+	string "LCD panel backlight enable pin"
+	depends on VIDEO
+	default ""
+	---help---
+	Set the backlight enable pin for the LCD panel. This takes a string in the
+	the format understood by sunxi_name_to_gpio, e.g. PH1 for pin 1 of
+	port H.
+
+config VIDEO_LCD_BL_PWM
+	string "LCD panel backlight pwm pin"
+	depends on VIDEO
+	default ""
+	---help---
+	Set the backlight pwm pin for the LCD panel. This takes a string in the
+	format understood by sunxi_name_to_gpio, e.g. PH1 for pin 1 of port H.
 
 config USB_KEYBOARD
 	boolean "Enable USB keyboard support"
 	default y
 	---help---
 	Say Y here to add support for using a USB keyboard (typically used
-	in combination with a graphical console on HDMI).
+	in combination with a graphical console).
 
 endif
diff --git a/doc/README.video b/doc/README.video
index dadbfcd..cfe6318 100644
--- a/doc/README.video
+++ b/doc/README.video
@@ -5,15 +5,8 @@ 
  * SPDX-License-Identifier:	GPL-2.0+
  */
 
-U-Boot MPC8xx video controller driver
-======================================
-
-The driver has been tested with the following configurations:
-
-- MPC823FADS with AD7176 on a PAL TV (YCbYCr)	- arsenio@tin.it
-
 "video-mode" environment variable
-===============================
+=================================
 
 The 'video-mode' environment variable can be used to enable and configure
 some video drivers.  The format matches the video= command-line option used
@@ -28,4 +21,45 @@  for Linux:
 	<freq>		The frequency (in Hz) to use.
 	<options>	A comma-separated list of device-specific options
 
+
+U-Boot MPC8xx video controller driver
+=====================================
+
+The driver has been tested with the following configurations:
+
+- MPC823FADS with AD7176 on a PAL TV (YCbYCr)	- arsenio@tin.it
+
 Example: video-mode=fslfb:1280x1024-32@60,monitor=dvi
+
+
+U-boot sunxi video controller driver
+====================================
+
+U-boot supports hdmi and lcd output on Allwinner sunxi SoCs, lcd output
+requires the CONFIG_VIDEO_LCD_MODE Kconfig value to be set.
+
+The sunxi u-boot driver supports the following video-mode options:
+
+- monitor=[none|dvi|hdmi|lcd] - Select the video output to use
+ none:     Disable video output.
+ dvi/hdmi: Selects output over the hdmi connector with dvi resp. hdmi output
+           format, if edid is used the format is automatically selected.
+ lcd:      Selects video output to a LCD screen.
+ vga:      Selects bideo output over the VGA connector.
+ Defaults to monitor=dvi.
+
+- hpd=[0|1] - Enable use of the hdmi HotPlug Detect feature
+ 0: Disabled. Configure dvi/hdmi output even if no cable is detected
+ 1: Enabled. If a LCD has been configured fallback to the LCD when no cable is
+    detected, if no LCD is configured, disable video ouput.
+ Defaults to hpd=1.
+
+- edid=[0|1] - Enable use of DDC + EDID to get monitor info
+ 0: Disabled.
+ 1: Enabled. If valid EDID info was read from the monitor the EDID info will
+    overrides the xres, yres and refresh from the video-mode env. variable.
+ Defaults to edid=1.
+
+For example to always use the hdmi connector, even if no cable is inserted,
+using edid info when available and otherwise initalizing it at 1024x768@60Hz,
+use: video-mode=sunxi:1024x768-24@60,monitor=dvi,hpd=0,edid=1 .
diff --git a/drivers/video/sunxi_display.c b/drivers/video/sunxi_display.c
index ad38f16..fee1474 100644
--- a/drivers/video/sunxi_display.c
+++ b/drivers/video/sunxi_display.c
@@ -11,7 +11,9 @@ 
 
 #include <asm/arch/clock.h>
 #include <asm/arch/display.h>
+#include <asm/arch/gpio.h>
 #include <asm/global_data.h>
+#include <asm/gpio.h>
 #include <asm/io.h>
 #include <errno.h>
 #include <fdtdec.h>
@@ -34,6 +36,7 @@  struct sunxi_display {
 	GraphicDevice graphic_device;
 	bool enabled;
 	enum sunxi_monitor monitor;
+	unsigned int depth;
 } sunxi_display;
 
 /*
@@ -435,6 +438,133 @@  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;
+
+	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 == 17) {
+		writel(SUNXI_LCDC_FRAME_CTRL_DITHER0, &lcdc->frame_ctrl[1]);
+		writel(SUNXI_LCDC_FRAME_CTRL_DITHER0, &lcdc->frame_ctrl[2]);
+		writel(SUNXI_LCDC_FRAME_CTRL_DITHER0, &lcdc->frame_ctrl[3]);
+		writel(SUNXI_LCDC_FRAME_CTRL_DITHER0, &lcdc->frame_ctrl[4]);
+		writel(SUNXI_LCDC_FRAME_CTRL_DITHER0, &lcdc->frame_ctrl[5]);
+		writel(SUNXI_LCDC_FRAME_CTRL_DITHER0, &lcdc->frame_ctrl[6]);
+		writel(SUNXI_LCDC_FRAME_CTRL_DITHER1, &lcdc->frame_ctrl[7]);
+		writel(SUNXI_LCDC_FRAME_CTRL_DITHER2, &lcdc->frame_ctrl[8]);
+		writel(SUNXI_LCDC_FRAME_CTRL_DITHER3, &lcdc->frame_ctrl[9]);
+		writel(SUNXI_LCDC_FRAME_CTRL_DITHER4, &lcdc->frame_ctrl[10]);
+		writel(((sunxi_display.depth == 18) ?
+			SUNXI_LCDC_FRAME_CTRL0_RGB666 :
+			SUNXI_LCDC_FRAME_CTRL0_RGB656),
+		       &lcdc->frame_ctrl[0]);
+	}
+
+	/*
+	 * Bit 24 and 25 of tcon0_io_polarity can be used to invert hsync /
+	 * vsync polarity, but this leads to noise problems, so we always
+	 * keep the polarity positive.
+	 */
+	writel(0, &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 +748,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 +776,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 +788,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 +814,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 +875,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,