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