blob: 7a01cc343cac3734a62aa5ca5f2d3a3e49865709 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0+
Vasily Khoruzhick1d7eef32017-10-26 21:51:52 -07002/*
3 * Allwinner LCD driver
4 *
5 * (C) Copyright 2017 Vasily Khoruzhick <anarsoul@gmail.com>
Vasily Khoruzhick1d7eef32017-10-26 21:51:52 -07006 */
7
8#include <common.h>
9#include <display.h>
Simon Glassf7ae49f2020-05-10 11:40:05 -060010#include <log.h>
Vasily Khoruzhick1d7eef32017-10-26 21:51:52 -070011#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>
Simon Glass401d1c42020-10-30 21:38:53 -060018#include <asm/global_data.h>
Vasily Khoruzhick1d7eef32017-10-26 21:51:52 -070019#include <asm/gpio.h>
Andre Przywara207ed0a2022-09-06 10:36:38 +010020#include <sunxi_gpio.h>
Vasily Khoruzhick1d7eef32017-10-26 21:51:52 -070021
22struct sunxi_lcd_priv {
23 struct display_timing timing;
24 int panel_bpp;
25};
26
27static 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
39static 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
70static 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
80static 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
136static const struct dm_display_ops sunxi_lcd_ops = {
137 .read_timing = sunxi_lcd_read_timing,
138 .enable = sunxi_lcd_enable,
139};
140
141U_BOOT_DRIVER(sunxi_lcd) = {
142 .name = "sunxi_lcd",
143 .id = UCLASS_DISPLAY,
144 .ops = &sunxi_lcd_ops,
145 .probe = sunxi_lcd_probe,
Simon Glass41575d82020-12-03 16:55:17 -0700146 .priv_auto = sizeof(struct sunxi_lcd_priv),
Vasily Khoruzhick1d7eef32017-10-26 21:51:52 -0700147};
148
149#ifdef CONFIG_MACH_SUN50I
Simon Glass20e442a2020-12-28 20:34:54 -0700150U_BOOT_DRVINFO(sunxi_lcd) = {
Vasily Khoruzhick1d7eef32017-10-26 21:51:52 -0700151 .name = "sunxi_lcd"
152};
153#endif