blob: f0e3d2c993fec6b22b2c29b9aa7c007f87e8e83c [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0+
Simon Glasse161ccf2012-10-17 13:24:51 +00002/*
3 * Copyright (c) 2011 The Chromium OS Authors.
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +02004 * Copyright (c) 2024 Svyatoslav Ryhel <clamor95@gmail.com>
Simon Glasse161ccf2012-10-17 13:24:51 +00005 */
Simon Glassb1c50fb2016-01-30 16:37:57 -07006
Svyatoslav Ryhel7673aba2023-03-27 11:11:46 +03007#include <backlight.h>
Jonas Schwöbelfa2e8382024-01-23 19:16:26 +02008#include <cpu_func.h>
Svyatoslav Ryhelebc0d402024-11-24 14:22:18 +02009#include <clk.h>
Simon Glasse865ef32016-01-30 16:37:56 -070010#include <dm.h>
Svyatoslav Ryhel8030d352025-02-15 19:48:44 +020011#include <dm/ofnode_graph.h>
Simon Glasse161ccf2012-10-17 13:24:51 +000012#include <fdtdec.h>
Simon Glass0f2af882020-05-10 11:40:05 -060013#include <log.h>
Simon Glass44fe9e42016-05-08 16:55:20 -060014#include <panel.h>
Simon Glasse865ef32016-01-30 16:37:56 -070015#include <video.h>
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +020016#include <video_bridge.h>
Simon Glasse161ccf2012-10-17 13:24:51 +000017#include <asm/system.h>
Simon Glassd8fc3c52016-01-30 16:37:53 -070018#include <asm/io.h>
Simon Glasse161ccf2012-10-17 13:24:51 +000019#include <asm/arch/clock.h>
Svyatoslav Ryhel1b0789b2024-01-23 19:16:22 +020020#include <asm/arch/powergate.h>
Svyatoslav Ryhel75fec412024-01-23 19:16:18 +020021
Svyatoslav Ryhelead1d512025-03-29 17:00:20 +020022#include "dc.h"
Simon Glasse161ccf2012-10-17 13:24:51 +000023
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +020024/* Holder of Tegra per-SOC DC differences */
25struct tegra_dc_soc_info {
26 bool has_timer;
27 bool has_rgb;
Svyatoslav Ryhel1b0789b2024-01-23 19:16:22 +020028 bool has_pgate;
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +020029};
30
Simon Glass923128f2016-01-30 16:37:55 -070031/* Information about the display controller */
32struct tegra_lcd_priv {
Simon Glass923128f2016-01-30 16:37:55 -070033 int width; /* width in pixels */
34 int height; /* height in pixels */
Simon Glass44fe9e42016-05-08 16:55:20 -060035 enum video_log2_bpp log2_bpp; /* colour depth */
36 struct display_timing timing;
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +020037 struct udevice *panel; /* Panels attached to RGB */
38 struct udevice *bridge; /* Bridge linked with DC */
Svyatoslav Ryhel9716fe52023-03-27 11:11:44 +030039 struct dc_ctlr *dc; /* Display controller regmap */
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +020040 const struct tegra_dc_soc_info *soc;
Simon Glass923128f2016-01-30 16:37:55 -070041 fdt_addr_t frame_buffer; /* Address of frame buffer */
42 unsigned pixel_clock; /* Pixel clock in Hz */
Svyatoslav Ryhelebc0d402024-11-24 14:22:18 +020043 struct clk *clk;
44 struct clk *clk_parent;
Svyatoslav Ryheld16c1052024-01-23 19:16:23 +020045 ulong scdiv; /* Clock divider used by disp_clk_ctrl */
Svyatoslav Ryhel4f5b79b2023-03-27 11:11:45 +030046 bool rotation; /* 180 degree panel turn */
Svyatoslav Ryhelbe908d62024-05-14 09:05:00 +030047 int pipe; /* DC controller: 0 for A, 1 for B */
Simon Glass923128f2016-01-30 16:37:55 -070048};
49
Simon Glasse161ccf2012-10-17 13:24:51 +000050enum {
51 /* Maximum LCD size we support */
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +020052 LCD_MAX_WIDTH = 2560,
53 LCD_MAX_HEIGHT = 1600,
Simon Glasse865ef32016-01-30 16:37:56 -070054 LCD_MAX_LOG2_BPP = VIDEO_BPP16,
Simon Glasse161ccf2012-10-17 13:24:51 +000055};
56
Svyatoslav Ryhel4f5b79b2023-03-27 11:11:45 +030057static void update_window(struct tegra_lcd_priv *priv,
58 struct disp_ctl_win *win)
Simon Glassd8fc3c52016-01-30 16:37:53 -070059{
Svyatoslav Ryhel4f5b79b2023-03-27 11:11:45 +030060 struct dc_ctlr *dc = priv->dc;
Simon Glassd8fc3c52016-01-30 16:37:53 -070061 unsigned h_dda, v_dda;
62 unsigned long val;
63
64 val = readl(&dc->cmd.disp_win_header);
65 val |= WINDOW_A_SELECT;
66 writel(val, &dc->cmd.disp_win_header);
67
68 writel(win->fmt, &dc->win.color_depth);
69
70 clrsetbits_le32(&dc->win.byte_swap, BYTE_SWAP_MASK,
71 BYTE_SWAP_NOSWAP << BYTE_SWAP_SHIFT);
72
73 val = win->out_x << H_POSITION_SHIFT;
74 val |= win->out_y << V_POSITION_SHIFT;
75 writel(val, &dc->win.pos);
76
77 val = win->out_w << H_SIZE_SHIFT;
78 val |= win->out_h << V_SIZE_SHIFT;
79 writel(val, &dc->win.size);
80
81 val = (win->w * win->bpp / 8) << H_PRESCALED_SIZE_SHIFT;
82 val |= win->h << V_PRESCALED_SIZE_SHIFT;
83 writel(val, &dc->win.prescaled_size);
84
85 writel(0, &dc->win.h_initial_dda);
86 writel(0, &dc->win.v_initial_dda);
87
88 h_dda = (win->w * 0x1000) / max(win->out_w - 1, 1U);
89 v_dda = (win->h * 0x1000) / max(win->out_h - 1, 1U);
90
91 val = h_dda << H_DDA_INC_SHIFT;
92 val |= v_dda << V_DDA_INC_SHIFT;
93 writel(val, &dc->win.dda_increment);
94
95 writel(win->stride, &dc->win.line_stride);
96 writel(0, &dc->win.buf_stride);
97
98 val = WIN_ENABLE;
99 if (win->bpp < 24)
100 val |= COLOR_EXPAND;
Svyatoslav Ryhel4f5b79b2023-03-27 11:11:45 +0300101
102 if (priv->rotation)
103 val |= H_DIRECTION | V_DIRECTION;
104
Simon Glassd8fc3c52016-01-30 16:37:53 -0700105 writel(val, &dc->win.win_opt);
106
107 writel((unsigned long)win->phys_addr, &dc->winbuf.start_addr);
108 writel(win->x, &dc->winbuf.addr_h_offset);
109 writel(win->y, &dc->winbuf.addr_v_offset);
110
111 writel(0xff00, &dc->win.blend_nokey);
112 writel(0xff00, &dc->win.blend_1win);
113
114 val = GENERAL_ACT_REQ | WIN_A_ACT_REQ;
115 val |= GENERAL_UPDATE | WIN_A_UPDATE;
116 writel(val, &dc->cmd.state_ctrl);
117}
118
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200119static int update_display_mode(struct tegra_lcd_priv *priv)
Simon Glassd8fc3c52016-01-30 16:37:53 -0700120{
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200121 struct dc_disp_reg *disp = &priv->dc->disp;
Simon Glass44fe9e42016-05-08 16:55:20 -0600122 struct display_timing *dt = &priv->timing;
Simon Glassd8fc3c52016-01-30 16:37:53 -0700123 unsigned long val;
Simon Glassd8fc3c52016-01-30 16:37:53 -0700124
125 writel(0x0, &disp->disp_timing_opt);
Simon Glassd8fc3c52016-01-30 16:37:53 -0700126
Simon Glass44fe9e42016-05-08 16:55:20 -0600127 writel(1 | 1 << 16, &disp->ref_to_sync);
128 writel(dt->hsync_len.typ | dt->vsync_len.typ << 16, &disp->sync_width);
129 writel(dt->hback_porch.typ | dt->vback_porch.typ << 16,
130 &disp->back_porch);
131 writel((dt->hfront_porch.typ - 1) | (dt->vfront_porch.typ - 1) << 16,
132 &disp->front_porch);
133 writel(dt->hactive.typ | (dt->vactive.typ << 16), &disp->disp_active);
Simon Glassd8fc3c52016-01-30 16:37:53 -0700134
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200135 if (priv->soc->has_rgb) {
136 val = DE_SELECT_ACTIVE << DE_SELECT_SHIFT;
137 val |= DE_CONTROL_NORMAL << DE_CONTROL_SHIFT;
138 writel(val, &disp->data_enable_opt);
Simon Glassd8fc3c52016-01-30 16:37:53 -0700139
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200140 val = DATA_FORMAT_DF1P1C << DATA_FORMAT_SHIFT;
141 val |= DATA_ALIGNMENT_MSB << DATA_ALIGNMENT_SHIFT;
142 val |= DATA_ORDER_RED_BLUE << DATA_ORDER_SHIFT;
143 writel(val, &disp->disp_interface_ctrl);
Simon Glassd8fc3c52016-01-30 16:37:53 -0700144
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200145 writel(0x00010001, &disp->shift_clk_opt);
Svyatoslav Ryhele0e64062024-11-27 13:57:05 +0200146 }
Simon Glassd8fc3c52016-01-30 16:37:53 -0700147
148 val = PIXEL_CLK_DIVIDER_PCD1 << PIXEL_CLK_DIVIDER_SHIFT;
Svyatoslav Ryheld16c1052024-01-23 19:16:23 +0200149 val |= priv->scdiv << SHIFT_CLK_DIVIDER_SHIFT;
Simon Glassd8fc3c52016-01-30 16:37:53 -0700150 writel(val, &disp->disp_clk_ctrl);
151
152 return 0;
153}
154
155/* Start up the display and turn on power to PWMs */
156static void basic_init(struct dc_cmd_reg *cmd)
157{
158 u32 val;
159
160 writel(0x00000100, &cmd->gen_incr_syncpt_ctrl);
161 writel(0x0000011a, &cmd->cont_syncpt_vsync);
162 writel(0x00000000, &cmd->int_type);
163 writel(0x00000000, &cmd->int_polarity);
164 writel(0x00000000, &cmd->int_mask);
165 writel(0x00000000, &cmd->int_enb);
166
167 val = PW0_ENABLE | PW1_ENABLE | PW2_ENABLE;
168 val |= PW3_ENABLE | PW4_ENABLE | PM0_ENABLE;
169 val |= PM1_ENABLE;
170 writel(val, &cmd->disp_pow_ctrl);
171
172 val = readl(&cmd->disp_cmd);
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200173 val &= ~CTRL_MODE_MASK;
Simon Glassd8fc3c52016-01-30 16:37:53 -0700174 val |= CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT;
175 writel(val, &cmd->disp_cmd);
176}
177
178static void basic_init_timer(struct dc_disp_reg *disp)
179{
180 writel(0x00000020, &disp->mem_high_pri);
181 writel(0x00000001, &disp->mem_high_pri_timer);
182}
183
184static const u32 rgb_enb_tab[PIN_REG_COUNT] = {
185 0x00000000,
186 0x00000000,
187 0x00000000,
188 0x00000000,
189};
190
191static const u32 rgb_polarity_tab[PIN_REG_COUNT] = {
192 0x00000000,
193 0x01000000,
194 0x00000000,
195 0x00000000,
196};
197
198static const u32 rgb_data_tab[PIN_REG_COUNT] = {
199 0x00000000,
200 0x00000000,
201 0x00000000,
202 0x00000000,
203};
204
205static const u32 rgb_sel_tab[PIN_OUTPUT_SEL_COUNT] = {
206 0x00000000,
207 0x00000000,
208 0x00000000,
209 0x00000000,
210 0x00210222,
211 0x00002200,
212 0x00020000,
213};
214
Svyatoslav Ryhel0e4a8552024-01-23 19:16:27 +0200215static void rgb_enable(struct tegra_lcd_priv *priv)
Simon Glassd8fc3c52016-01-30 16:37:53 -0700216{
Svyatoslav Ryhel0e4a8552024-01-23 19:16:27 +0200217 struct dc_com_reg *com = &priv->dc->com;
218 struct display_timing *dt = &priv->timing;
219 u32 value;
Simon Glassd8fc3c52016-01-30 16:37:53 -0700220 int i;
221
222 for (i = 0; i < PIN_REG_COUNT; i++) {
223 writel(rgb_enb_tab[i], &com->pin_output_enb[i]);
224 writel(rgb_polarity_tab[i], &com->pin_output_polarity[i]);
225 writel(rgb_data_tab[i], &com->pin_output_data[i]);
226 }
227
Svyatoslav Ryhel0e4a8552024-01-23 19:16:27 +0200228 /* configure H- and V-sync signal polarities */
229 value = readl(&com->pin_output_polarity[1]);
230
231 if (dt->flags & DISPLAY_FLAGS_HSYNC_LOW)
232 value |= LHS_OUTPUT_POLARITY_LOW;
233 else
234 value &= ~LHS_OUTPUT_POLARITY_LOW;
235
236 if (dt->flags & DISPLAY_FLAGS_VSYNC_LOW)
237 value |= LVS_OUTPUT_POLARITY_LOW;
238 else
239 value &= ~LVS_OUTPUT_POLARITY_LOW;
240
241 writel(value, &com->pin_output_polarity[1]);
242
Simon Glassd8fc3c52016-01-30 16:37:53 -0700243 for (i = 0; i < PIN_OUTPUT_SEL_COUNT; i++)
244 writel(rgb_sel_tab[i], &com->pin_output_sel[i]);
245}
246
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200247static int setup_window(struct tegra_lcd_priv *priv,
248 struct disp_ctl_win *win)
Simon Glassd8fc3c52016-01-30 16:37:53 -0700249{
Svyatoslav Ryhel4f5b79b2023-03-27 11:11:45 +0300250 if (priv->rotation) {
Svyatoslav Ryhel597eecb2024-01-23 19:16:17 +0200251 win->x = priv->width * 2 - 1;
252 win->y = priv->height - 1;
Svyatoslav Ryhel4f5b79b2023-03-27 11:11:45 +0300253 } else {
254 win->x = 0;
255 win->y = 0;
256 }
257
Simon Glasse865ef32016-01-30 16:37:56 -0700258 win->w = priv->width;
259 win->h = priv->height;
Simon Glassd8fc3c52016-01-30 16:37:53 -0700260 win->out_x = 0;
261 win->out_y = 0;
Simon Glasse865ef32016-01-30 16:37:56 -0700262 win->out_w = priv->width;
263 win->out_h = priv->height;
264 win->phys_addr = priv->frame_buffer;
265 win->stride = priv->width * (1 << priv->log2_bpp) / 8;
Svyatoslav Ryhele0e64062024-11-27 13:57:05 +0200266
267 log_debug("%s: depth = %d\n", __func__, priv->log2_bpp);
268
Simon Glasse865ef32016-01-30 16:37:56 -0700269 switch (priv->log2_bpp) {
Simon Glass44fe9e42016-05-08 16:55:20 -0600270 case VIDEO_BPP32:
Simon Glassd8fc3c52016-01-30 16:37:53 -0700271 win->fmt = COLOR_DEPTH_R8G8B8A8;
272 win->bpp = 32;
273 break;
Simon Glass44fe9e42016-05-08 16:55:20 -0600274 case VIDEO_BPP16:
Simon Glassd8fc3c52016-01-30 16:37:53 -0700275 win->fmt = COLOR_DEPTH_B5G6R5;
276 win->bpp = 16;
277 break;
278
279 default:
Svyatoslav Ryhele0e64062024-11-27 13:57:05 +0200280 log_debug("Unsupported LCD bit depth\n");
Simon Glassd8fc3c52016-01-30 16:37:53 -0700281 return -1;
282 }
283
284 return 0;
285}
286
Simon Glassd8fc3c52016-01-30 16:37:53 -0700287/**
Simon Glassd8fc3c52016-01-30 16:37:53 -0700288 * Register a new display based on device tree configuration.
289 *
Robert P. J. Day8d56db92016-07-15 13:44:45 -0400290 * The frame buffer can be positioned by U-Boot or overridden by the fdt.
Simon Glassd8fc3c52016-01-30 16:37:53 -0700291 * You should pass in the U-Boot address here, and check the contents of
Simon Glass923128f2016-01-30 16:37:55 -0700292 * struct tegra_lcd_priv to see what was actually chosen.
Simon Glassd8fc3c52016-01-30 16:37:53 -0700293 *
Simon Glasse865ef32016-01-30 16:37:56 -0700294 * @param priv Driver's private data
Simon Glassd8fc3c52016-01-30 16:37:53 -0700295 * @param default_lcd_base Default address of LCD frame buffer
Heinrich Schuchardt47b4c022022-01-19 18:05:50 +0100296 * Return: 0 if ok, -1 on error (unsupported bits per pixel)
Simon Glassd8fc3c52016-01-30 16:37:53 -0700297 */
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200298static int tegra_display_probe(struct tegra_lcd_priv *priv,
Simon Glasse865ef32016-01-30 16:37:56 -0700299 void *default_lcd_base)
Simon Glassd8fc3c52016-01-30 16:37:53 -0700300{
301 struct disp_ctl_win window;
Svyatoslav Ryhelebc0d402024-11-24 14:22:18 +0200302 unsigned long rate = clk_get_rate(priv->clk_parent);
Svyatoslav Ryhele0e64062024-11-27 13:57:05 +0200303 int ret;
Simon Glassd8fc3c52016-01-30 16:37:53 -0700304
Simon Glasse865ef32016-01-30 16:37:56 -0700305 priv->frame_buffer = (u32)default_lcd_base;
Simon Glassd8fc3c52016-01-30 16:37:53 -0700306
Simon Glassd8fc3c52016-01-30 16:37:53 -0700307 /*
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200308 * We halve the rate if DISP1 parent is PLLD, since actual parent
Svyatoslav Ryhelc1f260a2023-03-27 11:11:42 +0300309 * is plld_out0 which is PLLD divided by 2.
Simon Glassd8fc3c52016-01-30 16:37:53 -0700310 */
Svyatoslav Ryhel4589eed2024-11-27 13:35:10 +0200311 if (priv->clk_parent->id == CLOCK_ID_DISPLAY ||
312 priv->clk_parent->id == CLOCK_ID_DISPLAY2)
Svyatoslav Ryhelc1f260a2023-03-27 11:11:42 +0300313 rate /= 2;
314
315 /*
Svyatoslav Ryheld16c1052024-01-23 19:16:23 +0200316 * The pixel clock divider is in 7.1 format (where the bottom bit
317 * represents 0.5). Here we calculate the divider needed to get from
318 * the display clock (typically 600MHz) to the pixel clock. We round
319 * up or down as required.
320 */
321 if (!priv->scdiv)
322 priv->scdiv = ((rate * 2 + priv->pixel_clock / 2)
323 / priv->pixel_clock) - 2;
Svyatoslav Ryhele0e64062024-11-27 13:57:05 +0200324 log_debug("Display clock %lu, divider %lu\n", rate, priv->scdiv);
Svyatoslav Ryheld16c1052024-01-23 19:16:23 +0200325
Svyatoslav Ryhelebc0d402024-11-24 14:22:18 +0200326 clock_start_periph_pll(priv->clk->id, priv->clk_parent->id,
Svyatoslav Ryhelc1f260a2023-03-27 11:11:42 +0300327 rate);
328
Svyatoslav Ryhel9716fe52023-03-27 11:11:44 +0300329 basic_init(&priv->dc->cmd);
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200330
331 if (priv->soc->has_timer)
332 basic_init_timer(&priv->dc->disp);
333
334 if (priv->soc->has_rgb)
Svyatoslav Ryhel0e4a8552024-01-23 19:16:27 +0200335 rgb_enable(priv);
Simon Glassd8fc3c52016-01-30 16:37:53 -0700336
Simon Glasse865ef32016-01-30 16:37:56 -0700337 if (priv->pixel_clock)
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200338 update_display_mode(priv);
Simon Glassd8fc3c52016-01-30 16:37:53 -0700339
Svyatoslav Ryhele0e64062024-11-27 13:57:05 +0200340 ret = setup_window(priv, &window);
341 if (ret)
342 return ret;
Simon Glassd8fc3c52016-01-30 16:37:53 -0700343
Svyatoslav Ryhel4f5b79b2023-03-27 11:11:45 +0300344 update_window(priv, &window);
Simon Glassd8fc3c52016-01-30 16:37:53 -0700345
346 return 0;
347}
348
Simon Glasse865ef32016-01-30 16:37:56 -0700349static int tegra_lcd_probe(struct udevice *dev)
Simon Glasse161ccf2012-10-17 13:24:51 +0000350{
Simon Glassb75b15b2020-12-03 16:55:23 -0700351 struct video_uc_plat *plat = dev_get_uclass_plat(dev);
Simon Glasse865ef32016-01-30 16:37:56 -0700352 struct video_priv *uc_priv = dev_get_uclass_priv(dev);
353 struct tegra_lcd_priv *priv = dev_get_priv(dev);
Simon Glass44fe9e42016-05-08 16:55:20 -0600354 int ret;
Simon Glasse865ef32016-01-30 16:37:56 -0700355
Simon Glasse865ef32016-01-30 16:37:56 -0700356 /* Initialize the Tegra display controller */
Svyatoslav Ryhel1b0789b2024-01-23 19:16:22 +0200357 if (priv->soc->has_pgate) {
358 uint powergate;
359
360 if (priv->pipe)
361 powergate = TEGRA_POWERGATE_DISB;
362 else
363 powergate = TEGRA_POWERGATE_DIS;
364
365 ret = tegra_powergate_power_off(powergate);
366 if (ret < 0) {
Svyatoslav Ryhele0e64062024-11-27 13:57:05 +0200367 log_debug("failed to power off DISP gate: %d", ret);
Svyatoslav Ryhel1b0789b2024-01-23 19:16:22 +0200368 return ret;
369 }
370
371 ret = tegra_powergate_sequence_power_up(powergate,
Svyatoslav Ryhelebc0d402024-11-24 14:22:18 +0200372 priv->clk->id);
Svyatoslav Ryhel1b0789b2024-01-23 19:16:22 +0200373 if (ret < 0) {
Svyatoslav Ryhele0e64062024-11-27 13:57:05 +0200374 log_debug("failed to power up DISP gate: %d", ret);
Svyatoslav Ryhel1b0789b2024-01-23 19:16:22 +0200375 return ret;
376 }
377 }
378
Svyatoslav Ryheld16c1052024-01-23 19:16:23 +0200379 /* Get shift clock divider from Tegra DSI if used */
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +0200380 if (priv->bridge) {
381 if (!strcmp(priv->bridge->driver->name, "tegra_dsi")) {
382 struct tegra_dc_plat *dc_plat = dev_get_plat(priv->bridge);
Svyatoslav Ryheld16c1052024-01-23 19:16:23 +0200383
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +0200384 priv->scdiv = dc_plat->scdiv;
385 }
Svyatoslav Ryheld16c1052024-01-23 19:16:23 +0200386 }
387
Jonas Schwöbelfa2e8382024-01-23 19:16:26 +0200388 /* Clean the framebuffer area */
389 memset((u8 *)plat->base, 0, plat->size);
390 flush_dcache_all();
391
Svyatoslav Ryhele0e64062024-11-27 13:57:05 +0200392 ret = tegra_display_probe(priv, (void *)plat->base);
393 if (ret) {
394 log_debug("%s: Failed to probe display driver\n", __func__);
395 return ret;
Simon Glasse161ccf2012-10-17 13:24:51 +0000396 }
Simon Glasse865ef32016-01-30 16:37:56 -0700397
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +0200398 if (priv->panel) {
399 ret = panel_enable_backlight(priv->panel);
400 if (ret) {
401 log_debug("%s: Cannot enable backlight, ret=%d\n", __func__, ret);
402 return ret;
403 }
Simon Glass44fe9e42016-05-08 16:55:20 -0600404 }
Simon Glasse865ef32016-01-30 16:37:56 -0700405
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +0200406 if (priv->bridge) {
407 ret = video_bridge_attach(priv->bridge);
408 if (ret) {
409 log_debug("%s: Cannot attach bridge, ret=%d\n", __func__, ret);
410 return ret;
411 }
412 }
413
Simon Glassbbdae4b2016-05-08 16:55:21 -0600414 mmu_set_region_dcache_behaviour(priv->frame_buffer, plat->size,
415 DCACHE_WRITETHROUGH);
Simon Glasse865ef32016-01-30 16:37:56 -0700416
417 /* Enable flushing after LCD writes if requested */
Simon Glassbbdae4b2016-05-08 16:55:21 -0600418 video_set_flush_dcache(dev, true);
Simon Glasse865ef32016-01-30 16:37:56 -0700419
420 uc_priv->xsize = priv->width;
421 uc_priv->ysize = priv->height;
422 uc_priv->bpix = priv->log2_bpp;
Svyatoslav Ryhele0e64062024-11-27 13:57:05 +0200423 log_debug("LCD frame buffer at %08x, size %x\n", priv->frame_buffer,
424 plat->size);
Simon Glasse865ef32016-01-30 16:37:56 -0700425
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +0200426 if (priv->panel) {
427 ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT);
428 if (ret)
429 return ret;
430 }
431
432 if (priv->bridge) {
433 ret = video_bridge_set_backlight(priv->bridge, BACKLIGHT_DEFAULT);
434 if (ret)
435 return ret;
436 }
437
438 return 0;
439}
440
441static int tegra_lcd_configure_rgb(struct udevice *dev, ofnode rgb)
442{
443 struct tegra_lcd_priv *priv = dev_get_priv(dev);
444 ofnode remote;
445 int ret;
446
Svyatoslav Ryhel8030d352025-02-15 19:48:44 +0200447 /* DC can have only 1 port */
448 remote = ofnode_graph_get_remote_node(rgb, -1, -1);
449
450 ret = uclass_get_device_by_ofnode(UCLASS_PANEL, remote, &priv->panel);
451 if (!ret)
452 return 0;
453
454 ret = uclass_get_device_by_ofnode(UCLASS_VIDEO_BRIDGE, remote, &priv->bridge);
455 if (!ret)
456 return 0;
457
458 /* Try legacy method if graph did not work */
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +0200459 remote = ofnode_parse_phandle(rgb, "nvidia,panel", 0);
460 if (!ofnode_valid(remote))
461 return -EINVAL;
462
463 ret = uclass_get_device_by_ofnode(UCLASS_PANEL, remote, &priv->panel);
464 if (ret) {
465 log_debug("%s: Cannot find panel for '%s' (ret=%d)\n",
466 __func__, dev->name, ret);
467
468 ret = uclass_get_device_by_ofnode(UCLASS_VIDEO_BRIDGE, remote,
469 &priv->bridge);
470 if (ret) {
471 log_err("%s: Cannot find panel or bridge for '%s' (ret=%d)\n",
472 __func__, dev->name, ret);
473 return ret;
474 }
475 }
476
477 return 0;
478}
479
480static int tegra_lcd_configure_internal(struct udevice *dev)
481{
482 struct tegra_lcd_priv *priv = dev_get_priv(dev);
483 struct tegra_dc_plat *dc_plat;
484 ofnode host1x = ofnode_get_parent(dev_ofnode(dev));
485 ofnode node;
486 int ret;
487
488 switch (priv->pipe) {
489 case 0: /* DC0 is usually used for DSI */
490 /* Check for ganged DSI configuration */
491 ofnode_for_each_subnode(node, host1x)
492 if (ofnode_name_eq(node, "dsi") && ofnode_is_enabled(node) &&
493 ofnode_read_bool(node, "nvidia,ganged-mode"))
494 goto exit;
495
496 /* If no master DSI found loop for any active DSI */
497 ofnode_for_each_subnode(node, host1x)
498 if (ofnode_name_eq(node, "dsi") && ofnode_is_enabled(node))
499 goto exit;
500
501 log_err("%s: failed to find DSI device for '%s'\n",
502 __func__, dev->name);
503
504 return -ENODEV;
505 case 1: /* DC1 is usually used for HDMI */
506 ofnode_for_each_subnode(node, host1x)
507 if (ofnode_name_eq(node, "hdmi"))
508 goto exit;
509
510 log_err("%s: failed to find HDMI device for '%s'\n",
511 __func__, dev->name);
512
513 return -ENODEV;
514 default:
515 log_debug("Unsupported DC selection\n");
516 return -EINVAL;
517 }
518
519exit:
520 ret = uclass_get_device_by_ofnode(UCLASS_VIDEO_BRIDGE, node, &priv->bridge);
521 if (ret) {
522 log_err("%s: failed to get DSI/HDMI device for '%s' (ret %d)\n",
523 __func__, dev->name, ret);
524 return ret;
525 }
526
Svyatoslav Ryhel4f82d222025-02-23 11:27:09 +0200527 priv->clk_parent = devm_clk_get(priv->bridge, "parent");
528 if (IS_ERR(priv->clk_parent)) {
529 log_debug("%s: Could not get DC clock parent from DSI/HDMI: %ld\n",
530 __func__, PTR_ERR(priv->clk_parent));
531 return PTR_ERR(priv->clk_parent);
532 }
533
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +0200534 dc_plat = dev_get_plat(priv->bridge);
535
536 /* Fill the platform data for internal devices */
537 dc_plat->dev = dev;
538 dc_plat->dc = priv->dc;
539 dc_plat->pipe = priv->pipe;
540
541 return 0;
Simon Glasse865ef32016-01-30 16:37:56 -0700542}
543
Simon Glassaad29ae2020-12-03 16:55:21 -0700544static int tegra_lcd_of_to_plat(struct udevice *dev)
Simon Glass60740e72016-01-30 16:37:59 -0700545{
546 struct tegra_lcd_priv *priv = dev_get_priv(dev);
Simon Glass44fe9e42016-05-08 16:55:20 -0600547 struct display_timing *timing;
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +0200548 ofnode rgb;
Simon Glassd8af3c92016-01-30 16:38:01 -0700549 int ret;
Simon Glass60740e72016-01-30 16:37:59 -0700550
Svyatoslav Ryhel9716fe52023-03-27 11:11:44 +0300551 priv->dc = (struct dc_ctlr *)dev_read_addr_ptr(dev);
552 if (!priv->dc) {
Svyatoslav Ryhele0e64062024-11-27 13:57:05 +0200553 log_debug("%s: No display controller address\n", __func__);
Simon Glass60740e72016-01-30 16:37:59 -0700554 return -EINVAL;
555 }
556
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200557 priv->soc = (struct tegra_dc_soc_info *)dev_get_driver_data(dev);
558
Svyatoslav Ryhelebc0d402024-11-24 14:22:18 +0200559 priv->clk = devm_clk_get(dev, NULL);
560 if (IS_ERR(priv->clk)) {
561 log_debug("%s: Could not get DC clock: %ld\n",
562 __func__, PTR_ERR(priv->clk));
563 return PTR_ERR(priv->clk);
564 }
565
566 priv->clk_parent = devm_clk_get(dev, "parent");
567 if (IS_ERR(priv->clk_parent)) {
568 log_debug("%s: Could not get DC clock parent: %ld\n",
569 __func__, PTR_ERR(priv->clk_parent));
570 return PTR_ERR(priv->clk_parent);
Svyatoslav Ryhelc1f260a2023-03-27 11:11:42 +0300571 }
572
Svyatoslav Ryhel4f5b79b2023-03-27 11:11:45 +0300573 priv->rotation = dev_read_bool(dev, "nvidia,180-rotation");
Svyatoslav Ryhelbe908d62024-05-14 09:05:00 +0300574 priv->pipe = dev_read_u32_default(dev, "nvidia,head", 0);
Svyatoslav Ryhelbae46f32024-01-23 19:16:19 +0200575
Simon Glass44fe9e42016-05-08 16:55:20 -0600576 /*
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +0200577 * Usual logic of Tegra video routing should be next:
578 * 1. Check rgb subnode for RGB/LVDS panels or bridges
579 * 2. If none found, then iterate through bridges bound,
580 * looking for DSIA or DSIB for DC0 and HDMI for DC1.
581 * If none of above is valid, then configuration is not
582 * valid.
Simon Glass44fe9e42016-05-08 16:55:20 -0600583 */
Svyatoslav Ryheld8806292023-03-27 11:11:43 +0300584
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +0200585 rgb = dev_read_subnode(dev, "rgb");
586 if (ofnode_valid(rgb) && ofnode_is_enabled(rgb)) {
587 /* RGB is available, use it */
588 ret = tegra_lcd_configure_rgb(dev, rgb);
589 if (ret)
590 return ret;
591 } else {
592 /* RGB is not available, check for internal devices */
593 ret = tegra_lcd_configure_internal(dev);
594 if (ret)
595 return ret;
Simon Glassd8af3c92016-01-30 16:38:01 -0700596 }
Simon Glass60740e72016-01-30 16:37:59 -0700597
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +0200598 if (priv->panel) {
599 ret = panel_get_display_timing(priv->panel, &priv->timing);
600 if (ret) {
601 ret = ofnode_decode_display_timing(rgb, 0, &priv->timing);
602 if (ret) {
603 log_debug("%s: Cannot read display timing for '%s' (ret=%d)\n",
604 __func__, dev->name, ret);
605 return -EINVAL;
606 }
607 }
Svyatoslav Ryhel0c8aa5e2023-03-27 11:11:47 +0300608 }
609
Svyatoslav Ryhel3ed902e2025-02-14 15:13:01 +0200610 if (priv->bridge) {
611 ret = video_bridge_get_display_timing(priv->bridge, &priv->timing);
Svyatoslav Ryheld8806292023-03-27 11:11:43 +0300612 if (ret) {
Svyatoslav Ryhele0e64062024-11-27 13:57:05 +0200613 log_debug("%s: Cannot read display timing for '%s' (ret=%d)\n",
614 __func__, dev->name, ret);
Svyatoslav Ryheld8806292023-03-27 11:11:43 +0300615 return -EINVAL;
616 }
617 }
618
619 timing = &priv->timing;
620 priv->width = timing->hactive.typ;
621 priv->height = timing->vactive.typ;
622 priv->pixel_clock = timing->pixelclock.typ;
623 priv->log2_bpp = VIDEO_BPP16;
624
Simon Glass60740e72016-01-30 16:37:59 -0700625 return 0;
626}
627
Simon Glasse865ef32016-01-30 16:37:56 -0700628static int tegra_lcd_bind(struct udevice *dev)
629{
Simon Glassb75b15b2020-12-03 16:55:23 -0700630 struct video_uc_plat *plat = dev_get_uclass_plat(dev);
Simon Glasse865ef32016-01-30 16:37:56 -0700631
632 plat->size = LCD_MAX_WIDTH * LCD_MAX_HEIGHT *
633 (1 << LCD_MAX_LOG2_BPP) / 8;
634
Svyatoslav Ryhel38437ac2025-03-01 14:41:28 +0200635 return dm_scan_fdt_dev(dev);
Simon Glasse161ccf2012-10-17 13:24:51 +0000636}
Simon Glasse865ef32016-01-30 16:37:56 -0700637
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200638static const struct tegra_dc_soc_info tegra20_dc_soc_info = {
639 .has_timer = true,
640 .has_rgb = true,
Svyatoslav Ryhel1b0789b2024-01-23 19:16:22 +0200641 .has_pgate = false,
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200642};
643
644static const struct tegra_dc_soc_info tegra30_dc_soc_info = {
645 .has_timer = false,
646 .has_rgb = true,
Svyatoslav Ryhel1b0789b2024-01-23 19:16:22 +0200647 .has_pgate = false,
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200648};
649
650static const struct tegra_dc_soc_info tegra114_dc_soc_info = {
651 .has_timer = false,
652 .has_rgb = false,
Svyatoslav Ryhel1b0789b2024-01-23 19:16:22 +0200653 .has_pgate = true,
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200654};
655
Simon Glasse865ef32016-01-30 16:37:56 -0700656static const struct udevice_id tegra_lcd_ids[] = {
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200657 {
658 .compatible = "nvidia,tegra20-dc",
659 .data = (ulong)&tegra20_dc_soc_info
660 }, {
661 .compatible = "nvidia,tegra30-dc",
662 .data = (ulong)&tegra30_dc_soc_info
663 }, {
664 .compatible = "nvidia,tegra114-dc",
665 .data = (ulong)&tegra114_dc_soc_info
666 }, {
Svyatoslav Ryhel5fa06e72024-11-18 08:58:18 +0200667 .compatible = "nvidia,tegra124-dc",
668 .data = (ulong)&tegra114_dc_soc_info
669 }, {
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200670 /* sentinel */
671 }
Simon Glasse865ef32016-01-30 16:37:56 -0700672};
673
674U_BOOT_DRIVER(tegra_lcd) = {
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200675 .name = "tegra_lcd",
676 .id = UCLASS_VIDEO,
677 .of_match = tegra_lcd_ids,
Svyatoslav Ryhel9d53a7b2024-01-23 19:16:16 +0200678 .bind = tegra_lcd_bind,
679 .probe = tegra_lcd_probe,
Simon Glassaad29ae2020-12-03 16:55:21 -0700680 .of_to_plat = tegra_lcd_of_to_plat,
Simon Glass8a2b47f2020-12-03 16:55:17 -0700681 .priv_auto = sizeof(struct tegra_lcd_priv),
Simon Glasse865ef32016-01-30 16:37:56 -0700682};