[U-Boot] [PATCH 09/17] sunxi: video: Add lcd output support

Hans de Goede
2014-12-24T19:06:21Z

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>
---
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 | 180 ++++++++++++++++++++++++++++--
5 files changed, 274 insertions(+), 26 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))
+#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 5a88ba0..35c59e9 100644
--- a/board/sunxi/Kconfig
+++ b/board/sunxi/Kconfig
@@ -239,17 +239,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 at tin.it
+
Example: video-mode=fslfb:1280x1024-32 at 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 at 60Hz,
+use: video-mode=sunxi:1024x768-24 at 60,monitor=dvi,hpd=0,edid=1 .
diff --git a/drivers/video/sunxi_display.c b/drivers/video/sunxi_display.c
index ea7548b..bfcfd3f 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>
@@ -33,6 +35,7 @@ struct sunxi_display {
GraphicDevice graphic_device;
bool enabled;
enum sunxi_monitor monitor;
+ unsigned int depth;
} sunxi_display;

/*
@@ -434,6 +437,136 @@ 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 atleast 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_request(pin, "lcd_backlight_enable");
+ gpio_direction_output(pin, 1);
+ }
+
+ 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 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)
{
@@ -617,7 +750,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;
@@ -628,12 +766,12 @@ 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];
const char *mon_desc[] = { "none", "dvi", "hdmi", "lcd", "vga" };
+ char *lcd_mode = CONFIG_VIDEO_LCD_MODE;

memset(&sunxi_display, 0, sizeof(struct sunxi_display));

@@ -641,7 +779,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;
@@ -666,16 +805,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:
@@ -717,16 +866,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;

+ 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 framebuffer node, with pipeline == "de_be0-lcd0-hdmi" */
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,
--
2.1.0