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