blob: 619f5892f5862ad24b18ec9bb01dc3434d0f4ebd [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0+
Vasily Khoruzhick2f0b6e52017-10-26 21:51:52 -07002/*
3 * Allwinner LCD driver
4 *
5 * (C) Copyright 2017 Vasily Khoruzhick <anarsoul@gmail.com>
Vasily Khoruzhick2f0b6e52017-10-26 21:51:52 -07006 */
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
20struct sunxi_lcd_priv {
21 struct display_timing timing;
22 int panel_bpp;
23};
24
25static 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
37static 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
68static 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
78static 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
134static const struct dm_display_ops sunxi_lcd_ops = {
135 .read_timing = sunxi_lcd_read_timing,
136 .enable = sunxi_lcd_enable,
137};
138
139U_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
148U_BOOT_DEVICE(sunxi_lcd) = {
149 .name = "sunxi_lcd"
150};
151#endif