sunxi: video: Add lcd output support
authorHans de Goede <hdegoede@redhat.com>
Sun, 21 Dec 2014 15:28:32 +0000 (16:28 +0100)
committerHans de Goede <hdegoede@redhat.com>
Wed, 14 Jan 2015 13:56:39 +0000 (14:56 +0100)
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>
Acked-by: Ian Campbell <ijc@hellion.org.uk>
arch/arm/include/asm/arch-sunxi/display.h
arch/arm/include/asm/arch-sunxi/gpio.h
board/sunxi/Kconfig
doc/README.video
drivers/video/sunxi_display.c

index 00e34665da01bc87b991fc28cd9a1fd19037d379..1d4e9350176e0435efcc7a5151fcad9b8981a354 100644 (file)
@@ -57,14 +57,16 @@ struct sunxi_lcdc_reg {
        u32 int0;                       /* 0x04 */
        u32 int1;                       /* 0x08 */
        u8 res0[0x04];                  /* 0x0c */
-       u32 frame_ctrl;                 /* 0x10 */
-       u8 res1[0x2c];                  /* 0x14 */
+       u32 tcon0_frm_ctrl;             /* 0x10 */
+       u32 tcon0_frm_seed[6];          /* 0x14 */
+       u32 tcon0_frm_table[4];         /* 0x2c */
+       u8 res1[4];                     /* 0x3c */
        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 */
@@ -175,11 +177,27 @@ struct sunxi_hdmi_reg {
  */
 #define SUNXI_LCDC_X(x)                                (((x) - 1) << 16)
 #define SUNXI_LCDC_Y(y)                                (((y) - 1) << 0)
+#define SUNXI_LCDC_TCON_VSYNC_MASK             (1 << 24)
+#define SUNXI_LCDC_TCON_HSYNC_MASK             (1 << 25)
 #define SUNXI_LCDC_CTRL_IO_MAP_MASK            (1 << 0)
 #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_TCON0_FRM_CTRL_RGB666       ((1 << 31) | (0 << 4))
+#define SUNXI_LCDC_TCON0_FRM_CTRL_RGB565       ((1 << 31) | (5 << 4))
+#define SUNXI_LCDC_TCON0_FRM_SEED              0x11111111
+#define SUNXI_LCDC_TCON0_FRM_TAB0              0x01010000
+#define SUNXI_LCDC_TCON0_FRM_TAB1              0x15151111
+#define SUNXI_LCDC_TCON0_FRM_TAB2              0x57575555
+#define SUNXI_LCDC_TCON0_FRM_TAB3              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)
index 32941cb3b5f606f4a51fd0c5d575a4ab6a96c622..9438f5a2623baeea0b059e6129e86376cbfc85d4 100644 (file)
@@ -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
index 80dc3f96baef2c617966b004ecfbdce998516d84..b4d6107cd3dff302e77a0d0f48fff9aef088509c 100644 (file)
@@ -280,17 +280,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
index dadbfcd2fdc8934d8f09a7d1b17c66dc9863a06d..cfe6318e127ba8f6b6b830b17add92736b8b5481 100644 (file)
@@ -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 .
index ad38f162fde4853915dc25e335bba8aea4f33ed7..46346eec0f09e030a3d9b511692de0064061c010 100644 (file)
@@ -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,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,