blob: 2f51aebe272c9984f5da137981d1d5d88ec942cb [file] [log] [blame]
Vasily Khoruzhick2f0b6e52017-10-26 21:51:52 -07001/*
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
21struct sunxi_lcd_priv {
22 struct display_timing timing;
23 int panel_bpp;
24};
25
26static 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
38static 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
69static 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
79static 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
135static const struct dm_display_ops sunxi_lcd_ops = {
136 .read_timing = sunxi_lcd_read_timing,
137 .enable = sunxi_lcd_enable,
138};
139
140U_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
149U_BOOT_DEVICE(sunxi_lcd) = {
150 .name = "sunxi_lcd"
151};
152#endif