sunxi: video: add LCD support to DE2 driver
authorVasily Khoruzhick <anarsoul@gmail.com>
Fri, 27 Oct 2017 04:51:52 +0000 (21:51 -0700)
committerAnatolij Gustschin <agust@denx.de>
Fri, 27 Oct 2017 07:44:48 +0000 (09:44 +0200)
Extend DE2 driver with LCD support. Tested on Pinebook which is based
on A64 and has ANX6345 eDP bridge with eDP panel connected to it.

Signed-off-by: Vasily Khoruzhick <anarsoul@gmail.com>
[agust: rebased v5 on u-boot-video/master]
Signed-off-by: Anatolij Gustschin <agust@denx.de>
arch/arm/mach-sunxi/Kconfig
drivers/video/sunxi/Makefile
drivers/video/sunxi/sunxi_de2.c
drivers/video/sunxi/sunxi_lcd.c [new file with mode: 0644]

index bb57d4ff81230035cd8a11524c89b6f6ab475288..09cfec6f573375da975269e33bfae1afdd8f128c 100644 (file)
@@ -682,7 +682,7 @@ config VIDEO_LCD_MODE
 
 config VIDEO_LCD_DCLK_PHASE
        int "LCD panel display clock phase"
-       depends on VIDEO_SUNXI
+       depends on VIDEO_SUNXI || DM_VIDEO
        default 1
        ---help---
        Select LCD panel display clock phase shift, range 0-3.
index aec32b79b9123f5ce134d300739da2154b3acbb9..fa12d43029927b9c0eeeeacbf99f8c0e104d88e5 100644 (file)
@@ -6,4 +6,4 @@
 #
 
 obj-$(CONFIG_VIDEO_SUNXI) += sunxi_display.o simplefb_common.o lcdc.o tve_common.o ../videomodes.o
-obj-$(CONFIG_VIDEO_DE2) += sunxi_de2.o sunxi_dw_hdmi.o simplefb_common.o lcdc.o ../dw_hdmi.o
+obj-$(CONFIG_VIDEO_DE2) += sunxi_de2.o sunxi_dw_hdmi.o simplefb_common.o lcdc.o ../dw_hdmi.o sunxi_lcd.o
index 67b937098c337344cddc33a0bf74528544f6d9a7..e8903400ec7e3a327d2f895d49b8b10e50f44e10 100644 (file)
@@ -235,6 +235,23 @@ static int sunxi_de2_probe(struct udevice *dev)
        if (!(gd->flags & GD_FLG_RELOC))
                return 0;
 
+       ret = uclass_find_device_by_name(UCLASS_DISPLAY,
+                                        "sunxi_lcd", &disp);
+       if (!ret) {
+               int mux;
+
+               mux = 0;
+
+               ret = sunxi_de2_init(dev, plat->base, VIDEO_BPP32, disp, mux,
+                                    false);
+               if (!ret) {
+                       video_set_flush_dcache(dev, 1);
+                       return 0;
+               }
+       }
+
+       debug("%s: lcd display not found (ret=%d)\n", __func__, ret);
+
        ret = uclass_find_device_by_name(UCLASS_DISPLAY,
                                         "sunxi_dw_hdmi", &disp);
        if (!ret) {
diff --git a/drivers/video/sunxi/sunxi_lcd.c b/drivers/video/sunxi/sunxi_lcd.c
new file mode 100644 (file)
index 0000000..2f51aeb
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * Allwinner LCD driver
+ *
+ * (C) Copyright 2017 Vasily Khoruzhick <anarsoul@gmail.com>
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ */
+
+#include <common.h>
+#include <display.h>
+#include <video_bridge.h>
+#include <backlight.h>
+#include <dm.h>
+#include <edid.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/lcdc.h>
+#include <asm/arch/gpio.h>
+#include <asm/gpio.h>
+
+struct sunxi_lcd_priv {
+       struct display_timing timing;
+       int panel_bpp;
+};
+
+static void sunxi_lcdc_config_pinmux(void)
+{
+#ifdef CONFIG_MACH_SUN50I
+       int pin;
+
+       for (pin = SUNXI_GPD(0); pin <= SUNXI_GPD(21); pin++) {
+               sunxi_gpio_set_cfgpin(pin, SUNXI_GPD_LCD0);
+               sunxi_gpio_set_drv(pin, 3);
+       }
+#endif
+}
+
+static int sunxi_lcd_enable(struct udevice *dev, int bpp,
+                           const struct display_timing *edid)
+{
+       struct sunxi_ccm_reg * const ccm =
+              (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+       struct sunxi_lcdc_reg * const lcdc =
+              (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
+       struct sunxi_lcd_priv *priv = dev_get_priv(dev);
+       struct udevice *backlight;
+       int clk_div, clk_double, ret;
+
+       /* Reset off */
+       setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_LCD0);
+       /* Clock on */
+       setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_LCD0);
+
+       lcdc_init(lcdc);
+       sunxi_lcdc_config_pinmux();
+       lcdc_pll_set(ccm, 0, edid->pixelclock.typ / 1000,
+                    &clk_div, &clk_double, false);
+       lcdc_tcon0_mode_set(lcdc, edid, clk_div, false,
+                           priv->panel_bpp, CONFIG_VIDEO_LCD_DCLK_PHASE);
+       lcdc_enable(lcdc, priv->panel_bpp);
+
+       ret = uclass_get_device(UCLASS_PANEL_BACKLIGHT, 0, &backlight);
+       if (!ret)
+               backlight_enable(backlight);
+
+       return 0;
+}
+
+static int sunxi_lcd_read_timing(struct udevice *dev,
+                                struct display_timing *timing)
+{
+       struct sunxi_lcd_priv *priv = dev_get_priv(dev);
+
+       memcpy(timing, &priv->timing, sizeof(struct display_timing));
+
+       return 0;
+}
+
+static int sunxi_lcd_probe(struct udevice *dev)
+{
+       struct udevice *cdev;
+       struct sunxi_lcd_priv *priv = dev_get_priv(dev);
+       int ret;
+       int node, timing_node, val;
+
+#ifdef CONFIG_VIDEO_BRIDGE
+       /* Try to get timings from bridge first */
+       ret = uclass_get_device(UCLASS_VIDEO_BRIDGE, 0, &cdev);
+       if (!ret) {
+               u8 edid[EDID_SIZE];
+               int channel_bpp;
+
+               ret = video_bridge_attach(cdev);
+               if (ret) {
+                       debug("video bridge attach failed: %d\n", ret);
+                       return ret;
+               }
+               ret = video_bridge_read_edid(cdev, edid, EDID_SIZE);
+               if (ret > 0) {
+                       ret = edid_get_timing(edid, ret,
+                                             &priv->timing, &channel_bpp);
+                       priv->panel_bpp = channel_bpp * 3;
+                       if (!ret)
+                               return ret;
+               }
+       }
+#endif
+
+       /* Fallback to timings from DT if there's no bridge or
+        * if reading EDID failed
+        */
+       ret = uclass_get_device(UCLASS_PANEL, 0, &cdev);
+       if (ret) {
+               debug("video panel not found: %d\n", ret);
+               return ret;
+       }
+
+       if (fdtdec_decode_display_timing(gd->fdt_blob, dev_of_offset(cdev),
+                                        0, &priv->timing)) {
+               debug("%s: Failed to decode display timing\n", __func__);
+               return -EINVAL;
+       }
+       timing_node = fdt_subnode_offset(gd->fdt_blob, dev_of_offset(cdev),
+                                        "display-timings");
+       node = fdt_first_subnode(gd->fdt_blob, timing_node);
+       val = fdtdec_get_int(gd->fdt_blob, node, "bits-per-pixel", -1);
+       if (val != -1)
+               priv->panel_bpp = val;
+       else
+               priv->panel_bpp = 18;
+
+       return 0;
+}
+
+static const struct dm_display_ops sunxi_lcd_ops = {
+       .read_timing = sunxi_lcd_read_timing,
+       .enable = sunxi_lcd_enable,
+};
+
+U_BOOT_DRIVER(sunxi_lcd) = {
+       .name   = "sunxi_lcd",
+       .id     = UCLASS_DISPLAY,
+       .ops    = &sunxi_lcd_ops,
+       .probe  = sunxi_lcd_probe,
+       .priv_auto_alloc_size = sizeof(struct sunxi_lcd_priv),
+};
+
+#ifdef CONFIG_MACH_SUN50I
+U_BOOT_DEVICE(sunxi_lcd) = {
+       .name = "sunxi_lcd"
+};
+#endif