Home | History | Annotate | Download | only in sunxi
      1 // SPDX-License-Identifier: GPL-2.0+
      2 /*
      3  * Allwinner LCD driver
      4  *
      5  * (C) Copyright 2017 Vasily Khoruzhick <anarsoul (at) gmail.com>
      6  */
      7 
      8 #include <common.h>
      9 #include <display.h>
     10 #include <video_bridge.h>
     11 #include <backlight.h>
     12 #include <dm.h>
     13 #include <edid.h>
     14 #include <asm/io.h>
     15 #include <asm/arch/clock.h>
     16 #include <asm/arch/lcdc.h>
     17 #include <asm/arch/gpio.h>
     18 #include <asm/gpio.h>
     19 
     20 struct sunxi_lcd_priv {
     21 	struct display_timing timing;
     22 	int panel_bpp;
     23 };
     24 
     25 static void sunxi_lcdc_config_pinmux(void)
     26 {
     27 #ifdef CONFIG_MACH_SUN50I
     28 	int pin;
     29 
     30 	for (pin = SUNXI_GPD(0); pin <= SUNXI_GPD(21); pin++) {
     31 		sunxi_gpio_set_cfgpin(pin, SUNXI_GPD_LCD0);
     32 		sunxi_gpio_set_drv(pin, 3);
     33 	}
     34 #endif
     35 }
     36 
     37 static int sunxi_lcd_enable(struct udevice *dev, int bpp,
     38 			    const struct display_timing *edid)
     39 {
     40 	struct sunxi_ccm_reg * const ccm =
     41 	       (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
     42 	struct sunxi_lcdc_reg * const lcdc =
     43 	       (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
     44 	struct sunxi_lcd_priv *priv = dev_get_priv(dev);
     45 	struct udevice *backlight;
     46 	int clk_div, clk_double, ret;
     47 
     48 	/* Reset off */
     49 	setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_LCD0);
     50 	/* Clock on */
     51 	setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_LCD0);
     52 
     53 	lcdc_init(lcdc);
     54 	sunxi_lcdc_config_pinmux();
     55 	lcdc_pll_set(ccm, 0, edid->pixelclock.typ / 1000,
     56 		     &clk_div, &clk_double, false);
     57 	lcdc_tcon0_mode_set(lcdc, edid, clk_div, false,
     58 			    priv->panel_bpp, CONFIG_VIDEO_LCD_DCLK_PHASE);
     59 	lcdc_enable(lcdc, priv->panel_bpp);
     60 
     61 	ret = uclass_get_device(UCLASS_PANEL_BACKLIGHT, 0, &backlight);
     62 	if (!ret)
     63 		backlight_enable(backlight);
     64 
     65 	return 0;
     66 }
     67 
     68 static int sunxi_lcd_read_timing(struct udevice *dev,
     69 				 struct display_timing *timing)
     70 {
     71 	struct sunxi_lcd_priv *priv = dev_get_priv(dev);
     72 
     73 	memcpy(timing, &priv->timing, sizeof(struct display_timing));
     74 
     75 	return 0;
     76 }
     77 
     78 static int sunxi_lcd_probe(struct udevice *dev)
     79 {
     80 	struct udevice *cdev;
     81 	struct sunxi_lcd_priv *priv = dev_get_priv(dev);
     82 	int ret;
     83 	int node, timing_node, val;
     84 
     85 #ifdef CONFIG_VIDEO_BRIDGE
     86 	/* Try to get timings from bridge first */
     87 	ret = uclass_get_device(UCLASS_VIDEO_BRIDGE, 0, &cdev);
     88 	if (!ret) {
     89 		u8 edid[EDID_SIZE];
     90 		int channel_bpp;
     91 
     92 		ret = video_bridge_attach(cdev);
     93 		if (ret) {
     94 			debug("video bridge attach failed: %d\n", ret);
     95 			return ret;
     96 		}
     97 		ret = video_bridge_read_edid(cdev, edid, EDID_SIZE);
     98 		if (ret > 0) {
     99 			ret = edid_get_timing(edid, ret,
    100 					      &priv->timing, &channel_bpp);
    101 			priv->panel_bpp = channel_bpp * 3;
    102 			if (!ret)
    103 				return ret;
    104 		}
    105 	}
    106 #endif
    107 
    108 	/* Fallback to timings from DT if there's no bridge or
    109 	 * if reading EDID failed
    110 	 */
    111 	ret = uclass_get_device(UCLASS_PANEL, 0, &cdev);
    112 	if (ret) {
    113 		debug("video panel not found: %d\n", ret);
    114 		return ret;
    115 	}
    116 
    117 	if (fdtdec_decode_display_timing(gd->fdt_blob, dev_of_offset(cdev),
    118 					 0, &priv->timing)) {
    119 		debug("%s: Failed to decode display timing\n", __func__);
    120 		return -EINVAL;
    121 	}
    122 	timing_node = fdt_subnode_offset(gd->fdt_blob, dev_of_offset(cdev),
    123 					 "display-timings");
    124 	node = fdt_first_subnode(gd->fdt_blob, timing_node);
    125 	val = fdtdec_get_int(gd->fdt_blob, node, "bits-per-pixel", -1);
    126 	if (val != -1)
    127 		priv->panel_bpp = val;
    128 	else
    129 		priv->panel_bpp = 18;
    130 
    131 	return 0;
    132 }
    133 
    134 static const struct dm_display_ops sunxi_lcd_ops = {
    135 	.read_timing = sunxi_lcd_read_timing,
    136 	.enable = sunxi_lcd_enable,
    137 };
    138 
    139 U_BOOT_DRIVER(sunxi_lcd) = {
    140 	.name   = "sunxi_lcd",
    141 	.id     = UCLASS_DISPLAY,
    142 	.ops    = &sunxi_lcd_ops,
    143 	.probe  = sunxi_lcd_probe,
    144 	.priv_auto_alloc_size = sizeof(struct sunxi_lcd_priv),
    145 };
    146 
    147 #ifdef CONFIG_MACH_SUN50I
    148 U_BOOT_DEVICE(sunxi_lcd) = {
    149 	.name = "sunxi_lcd"
    150 };
    151 #endif
    152