--- /dev/null
+/*
+ * 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