1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Allwinner LCD driver 4 * 5 * (C) Copyright 2017 Vasily Khoruzhick <anarsoul@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