]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
1d7eef3f VK |
2 | /* |
3 | * Allwinner LCD driver | |
4 | * | |
5 | * (C) Copyright 2017 Vasily Khoruzhick <[email protected]> | |
1d7eef3f VK |
6 | */ |
7 | ||
8 | #include <common.h> | |
9 | #include <display.h> | |
f7ae49fc | 10 | #include <log.h> |
1d7eef3f VK |
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> | |
401d1c4f | 18 | #include <asm/global_data.h> |
1d7eef3f VK |
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, | |
41575d8e | 145 | .priv_auto = sizeof(struct sunxi_lcd_priv), |
1d7eef3f VK |
146 | }; |
147 | ||
148 | #ifdef CONFIG_MACH_SUN50I | |
20e442ab | 149 | U_BOOT_DRVINFO(sunxi_lcd) = { |
1d7eef3f VK |
150 | .name = "sunxi_lcd" |
151 | }; | |
152 | #endif |