]>
Commit | Line | Data |
---|---|---|
e7e8823c SG |
1 | /* |
2 | * Copyright 2014 Google Inc. | |
3 | * | |
4 | * SPDX-License-Identifier: GPL-2.0+ | |
5 | * | |
6 | * Extracted from Chromium coreboot commit 3f59b13d | |
7 | */ | |
8 | ||
9 | #include <common.h> | |
10 | #include <dm.h> | |
11 | #include <edid.h> | |
12 | #include <errno.h> | |
2dcf1433 | 13 | #include <display.h> |
e7e8823c SG |
14 | #include <edid.h> |
15 | #include <fdtdec.h> | |
16 | #include <lcd.h> | |
d7659212 | 17 | #include <video.h> |
e7e8823c SG |
18 | #include <asm/gpio.h> |
19 | #include <asm/io.h> | |
20 | #include <asm/arch/clock.h> | |
21 | #include <asm/arch/pwm.h> | |
22 | #include <asm/arch-tegra/dc.h> | |
d7659212 | 23 | #include <dm/uclass-internal.h> |
e7e8823c SG |
24 | #include "displayport.h" |
25 | ||
26 | DECLARE_GLOBAL_DATA_PTR; | |
27 | ||
28 | /* return in 1000ths of a Hertz */ | |
29 | static int tegra_dc_calc_refresh(const struct display_timing *timing) | |
30 | { | |
31 | int h_total, v_total, refresh; | |
32 | int pclk = timing->pixelclock.typ; | |
33 | ||
34 | h_total = timing->hactive.typ + timing->hfront_porch.typ + | |
35 | timing->hback_porch.typ + timing->hsync_len.typ; | |
36 | v_total = timing->vactive.typ + timing->vfront_porch.typ + | |
37 | timing->vback_porch.typ + timing->vsync_len.typ; | |
38 | if (!pclk || !h_total || !v_total) | |
39 | return 0; | |
40 | refresh = pclk / h_total; | |
41 | refresh *= 1000; | |
42 | refresh /= v_total; | |
43 | ||
44 | return refresh; | |
45 | } | |
46 | ||
47 | static void print_mode(const struct display_timing *timing) | |
48 | { | |
49 | int refresh = tegra_dc_calc_refresh(timing); | |
50 | ||
51 | debug("MODE:%dx%d@%d.%03uHz pclk=%d\n", | |
52 | timing->hactive.typ, timing->vactive.typ, refresh / 1000, | |
53 | refresh % 1000, timing->pixelclock.typ); | |
54 | } | |
55 | ||
56 | static int update_display_mode(struct dc_ctlr *disp_ctrl, | |
57 | const struct display_timing *timing, | |
58 | int href_to_sync, int vref_to_sync) | |
59 | { | |
60 | print_mode(timing); | |
61 | ||
62 | writel(0x1, &disp_ctrl->disp.disp_timing_opt); | |
63 | ||
64 | writel(vref_to_sync << 16 | href_to_sync, | |
65 | &disp_ctrl->disp.ref_to_sync); | |
66 | ||
67 | writel(timing->vsync_len.typ << 16 | timing->hsync_len.typ, | |
68 | &disp_ctrl->disp.sync_width); | |
69 | ||
70 | writel(((timing->vback_porch.typ - vref_to_sync) << 16) | | |
71 | timing->hback_porch.typ, &disp_ctrl->disp.back_porch); | |
72 | ||
73 | writel(((timing->vfront_porch.typ + vref_to_sync) << 16) | | |
74 | timing->hfront_porch.typ, &disp_ctrl->disp.front_porch); | |
75 | ||
76 | writel(timing->hactive.typ | (timing->vactive.typ << 16), | |
77 | &disp_ctrl->disp.disp_active); | |
78 | ||
79 | /** | |
80 | * We want to use PLLD_out0, which is PLLD / 2: | |
81 | * PixelClock = (PLLD / 2) / ShiftClockDiv / PixelClockDiv. | |
82 | * | |
83 | * Currently most panels work inside clock range 50MHz~100MHz, and PLLD | |
84 | * has some requirements to have VCO in range 500MHz~1000MHz (see | |
85 | * clock.c for more detail). To simplify calculation, we set | |
86 | * PixelClockDiv to 1 and ShiftClockDiv to 1. In future these values | |
87 | * may be calculated by clock_display, to allow wider frequency range. | |
88 | * | |
89 | * Note ShiftClockDiv is a 7.1 format value. | |
90 | */ | |
91 | const u32 shift_clock_div = 1; | |
92 | writel((PIXEL_CLK_DIVIDER_PCD1 << PIXEL_CLK_DIVIDER_SHIFT) | | |
93 | ((shift_clock_div - 1) * 2) << SHIFT_CLK_DIVIDER_SHIFT, | |
94 | &disp_ctrl->disp.disp_clk_ctrl); | |
95 | debug("%s: PixelClock=%u, ShiftClockDiv=%u\n", __func__, | |
96 | timing->pixelclock.typ, shift_clock_div); | |
97 | return 0; | |
98 | } | |
99 | ||
dedc44b4 SG |
100 | static u32 tegra_dc_poll_register(void *reg, |
101 | u32 mask, u32 exp_val, u32 poll_interval_us, u32 timeout_us) | |
102 | { | |
103 | u32 temp = timeout_us; | |
104 | u32 reg_val = 0; | |
105 | ||
106 | do { | |
107 | udelay(poll_interval_us); | |
108 | reg_val = readl(reg); | |
109 | if (timeout_us > poll_interval_us) | |
110 | timeout_us -= poll_interval_us; | |
111 | else | |
112 | break; | |
113 | } while ((reg_val & mask) != exp_val); | |
114 | ||
115 | if ((reg_val & mask) == exp_val) | |
116 | return 0; /* success */ | |
117 | ||
118 | return temp; | |
119 | } | |
120 | ||
121 | int tegra_dc_sor_general_act(struct dc_ctlr *disp_ctrl) | |
122 | { | |
123 | writel(GENERAL_ACT_REQ, &disp_ctrl->cmd.state_ctrl); | |
124 | ||
125 | if (tegra_dc_poll_register(&disp_ctrl->cmd.state_ctrl, | |
126 | GENERAL_ACT_REQ, 0, 100, | |
127 | DC_POLL_TIMEOUT_MS * 1000)) { | |
128 | debug("dc timeout waiting for DC to stop\n"); | |
129 | return -ETIMEDOUT; | |
130 | } | |
131 | ||
132 | return 0; | |
133 | } | |
134 | ||
135 | static struct display_timing min_mode = { | |
136 | .hsync_len = { .typ = 1 }, | |
137 | .vsync_len = { .typ = 1 }, | |
138 | .hback_porch = { .typ = 20 }, | |
139 | .vback_porch = { .typ = 0 }, | |
140 | .hactive = { .typ = 16 }, | |
141 | .vactive = { .typ = 16 }, | |
142 | .hfront_porch = { .typ = 1 }, | |
143 | .vfront_porch = { .typ = 2 }, | |
144 | }; | |
145 | ||
146 | /* Disable windows and set minimum raster timings */ | |
147 | void tegra_dc_sor_disable_win_short_raster(struct dc_ctlr *disp_ctrl, | |
148 | int *dc_reg_ctx) | |
149 | { | |
150 | const int href_to_sync = 0, vref_to_sync = 1; | |
151 | int selected_windows, i; | |
152 | ||
153 | selected_windows = readl(&disp_ctrl->cmd.disp_win_header); | |
154 | ||
155 | /* Store and clear window options */ | |
156 | for (i = 0; i < DC_N_WINDOWS; ++i) { | |
157 | writel(WINDOW_A_SELECT << i, &disp_ctrl->cmd.disp_win_header); | |
158 | dc_reg_ctx[i] = readl(&disp_ctrl->win.win_opt); | |
159 | writel(0, &disp_ctrl->win.win_opt); | |
160 | writel(WIN_A_ACT_REQ << i, &disp_ctrl->cmd.state_ctrl); | |
161 | } | |
162 | ||
163 | writel(selected_windows, &disp_ctrl->cmd.disp_win_header); | |
164 | ||
165 | /* Store current raster timings and set minimum timings */ | |
166 | dc_reg_ctx[i++] = readl(&disp_ctrl->disp.ref_to_sync); | |
167 | writel(href_to_sync | (vref_to_sync << 16), | |
168 | &disp_ctrl->disp.ref_to_sync); | |
169 | ||
170 | dc_reg_ctx[i++] = readl(&disp_ctrl->disp.sync_width); | |
171 | writel(min_mode.hsync_len.typ | (min_mode.vsync_len.typ << 16), | |
172 | &disp_ctrl->disp.sync_width); | |
173 | ||
174 | dc_reg_ctx[i++] = readl(&disp_ctrl->disp.back_porch); | |
175 | writel(min_mode.hback_porch.typ | (min_mode.vback_porch.typ << 16), | |
176 | &disp_ctrl->disp.back_porch); | |
177 | ||
178 | dc_reg_ctx[i++] = readl(&disp_ctrl->disp.front_porch); | |
179 | writel(min_mode.hfront_porch.typ | (min_mode.vfront_porch.typ << 16), | |
180 | &disp_ctrl->disp.front_porch); | |
181 | ||
182 | dc_reg_ctx[i++] = readl(&disp_ctrl->disp.disp_active); | |
183 | writel(min_mode.hactive.typ | (min_mode.vactive.typ << 16), | |
184 | &disp_ctrl->disp.disp_active); | |
185 | ||
186 | writel(GENERAL_ACT_REQ, &disp_ctrl->cmd.state_ctrl); | |
187 | } | |
188 | ||
189 | /* Restore previous windows status and raster timings */ | |
190 | void tegra_dc_sor_restore_win_and_raster(struct dc_ctlr *disp_ctrl, | |
191 | int *dc_reg_ctx) | |
192 | { | |
193 | int selected_windows, i; | |
194 | ||
195 | selected_windows = readl(&disp_ctrl->cmd.disp_win_header); | |
196 | ||
197 | for (i = 0; i < DC_N_WINDOWS; ++i) { | |
198 | writel(WINDOW_A_SELECT << i, &disp_ctrl->cmd.disp_win_header); | |
199 | writel(dc_reg_ctx[i], &disp_ctrl->win.win_opt); | |
200 | writel(WIN_A_ACT_REQ << i, &disp_ctrl->cmd.state_ctrl); | |
201 | } | |
202 | ||
203 | writel(selected_windows, &disp_ctrl->cmd.disp_win_header); | |
204 | ||
205 | writel(dc_reg_ctx[i++], &disp_ctrl->disp.ref_to_sync); | |
206 | writel(dc_reg_ctx[i++], &disp_ctrl->disp.sync_width); | |
207 | writel(dc_reg_ctx[i++], &disp_ctrl->disp.back_porch); | |
208 | writel(dc_reg_ctx[i++], &disp_ctrl->disp.front_porch); | |
209 | writel(dc_reg_ctx[i++], &disp_ctrl->disp.disp_active); | |
210 | ||
211 | writel(GENERAL_UPDATE, &disp_ctrl->cmd.state_ctrl); | |
212 | } | |
213 | ||
e7e8823c SG |
214 | static int tegra_depth_for_bpp(int bpp) |
215 | { | |
216 | switch (bpp) { | |
217 | case 32: | |
218 | return COLOR_DEPTH_R8G8B8A8; | |
219 | case 16: | |
220 | return COLOR_DEPTH_B5G6R5; | |
221 | default: | |
222 | debug("Unsupported LCD bit depth"); | |
223 | return -1; | |
224 | } | |
225 | } | |
226 | ||
227 | static int update_window(struct dc_ctlr *disp_ctrl, | |
228 | u32 frame_buffer, int fb_bits_per_pixel, | |
229 | const struct display_timing *timing) | |
230 | { | |
231 | const u32 colour_white = 0xffffff; | |
232 | int colour_depth; | |
233 | u32 val; | |
234 | ||
235 | writel(WINDOW_A_SELECT, &disp_ctrl->cmd.disp_win_header); | |
236 | ||
237 | writel(((timing->vactive.typ << 16) | timing->hactive.typ), | |
238 | &disp_ctrl->win.size); | |
239 | writel(((timing->vactive.typ << 16) | | |
240 | (timing->hactive.typ * fb_bits_per_pixel / 8)), | |
241 | &disp_ctrl->win.prescaled_size); | |
242 | writel(((timing->hactive.typ * fb_bits_per_pixel / 8 + 31) / | |
243 | 32 * 32), &disp_ctrl->win.line_stride); | |
244 | ||
245 | colour_depth = tegra_depth_for_bpp(fb_bits_per_pixel); | |
246 | if (colour_depth == -1) | |
247 | return -EINVAL; | |
248 | ||
249 | writel(colour_depth, &disp_ctrl->win.color_depth); | |
250 | ||
251 | writel(frame_buffer, &disp_ctrl->winbuf.start_addr); | |
252 | writel(0x1000 << V_DDA_INC_SHIFT | 0x1000 << H_DDA_INC_SHIFT, | |
253 | &disp_ctrl->win.dda_increment); | |
254 | ||
255 | writel(colour_white, &disp_ctrl->disp.blend_background_color); | |
256 | writel(CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT, | |
257 | &disp_ctrl->cmd.disp_cmd); | |
258 | ||
259 | writel(WRITE_MUX_ACTIVE, &disp_ctrl->cmd.state_access); | |
260 | ||
261 | val = GENERAL_ACT_REQ | WIN_A_ACT_REQ; | |
262 | val |= GENERAL_UPDATE | WIN_A_UPDATE; | |
263 | writel(val, &disp_ctrl->cmd.state_ctrl); | |
264 | ||
265 | /* Enable win_a */ | |
266 | val = readl(&disp_ctrl->win.win_opt); | |
267 | writel(val | WIN_ENABLE, &disp_ctrl->win.win_opt); | |
268 | ||
269 | return 0; | |
270 | } | |
271 | ||
272 | static int tegra_dc_init(struct dc_ctlr *disp_ctrl) | |
273 | { | |
274 | /* do not accept interrupts during initialization */ | |
275 | writel(0x00000000, &disp_ctrl->cmd.int_mask); | |
276 | writel(WRITE_MUX_ASSEMBLY | READ_MUX_ASSEMBLY, | |
277 | &disp_ctrl->cmd.state_access); | |
278 | writel(WINDOW_A_SELECT, &disp_ctrl->cmd.disp_win_header); | |
279 | writel(0x00000000, &disp_ctrl->win.win_opt); | |
280 | writel(0x00000000, &disp_ctrl->win.byte_swap); | |
281 | writel(0x00000000, &disp_ctrl->win.buffer_ctrl); | |
282 | ||
283 | writel(0x00000000, &disp_ctrl->win.pos); | |
284 | writel(0x00000000, &disp_ctrl->win.h_initial_dda); | |
285 | writel(0x00000000, &disp_ctrl->win.v_initial_dda); | |
286 | writel(0x00000000, &disp_ctrl->win.dda_increment); | |
287 | writel(0x00000000, &disp_ctrl->win.dv_ctrl); | |
288 | ||
289 | writel(0x01000000, &disp_ctrl->win.blend_layer_ctrl); | |
290 | writel(0x00000000, &disp_ctrl->win.blend_match_select); | |
291 | writel(0x00000000, &disp_ctrl->win.blend_nomatch_select); | |
292 | writel(0x00000000, &disp_ctrl->win.blend_alpha_1bit); | |
293 | ||
294 | writel(0x00000000, &disp_ctrl->winbuf.start_addr_hi); | |
295 | writel(0x00000000, &disp_ctrl->winbuf.addr_h_offset); | |
296 | writel(0x00000000, &disp_ctrl->winbuf.addr_v_offset); | |
297 | ||
298 | writel(0x00000000, &disp_ctrl->com.crc_checksum); | |
299 | writel(0x00000000, &disp_ctrl->com.pin_output_enb[0]); | |
300 | writel(0x00000000, &disp_ctrl->com.pin_output_enb[1]); | |
301 | writel(0x00000000, &disp_ctrl->com.pin_output_enb[2]); | |
302 | writel(0x00000000, &disp_ctrl->com.pin_output_enb[3]); | |
303 | writel(0x00000000, &disp_ctrl->disp.disp_signal_opt0); | |
304 | ||
305 | return 0; | |
306 | } | |
307 | ||
308 | static void dump_config(int panel_bpp, struct display_timing *timing) | |
309 | { | |
310 | printf("timing->hactive.typ = %d\n", timing->hactive.typ); | |
311 | printf("timing->vactive.typ = %d\n", timing->vactive.typ); | |
312 | printf("timing->pixelclock.typ = %d\n", timing->pixelclock.typ); | |
313 | ||
314 | printf("timing->hfront_porch.typ = %d\n", timing->hfront_porch.typ); | |
315 | printf("timing->hsync_len.typ = %d\n", timing->hsync_len.typ); | |
316 | printf("timing->hback_porch.typ = %d\n", timing->hback_porch.typ); | |
317 | ||
318 | printf("timing->vfront_porch.typ %d\n", timing->vfront_porch.typ); | |
319 | printf("timing->vsync_len.typ = %d\n", timing->vsync_len.typ); | |
320 | printf("timing->vback_porch.typ = %d\n", timing->vback_porch.typ); | |
321 | ||
322 | printf("panel_bits_per_pixel = %d\n", panel_bpp); | |
323 | } | |
324 | ||
325 | static int display_update_config_from_edid(struct udevice *dp_dev, | |
326 | int *panel_bppp, | |
327 | struct display_timing *timing) | |
328 | { | |
720873bf | 329 | return display_read_timing(dp_dev, timing); |
e7e8823c SG |
330 | } |
331 | ||
d7659212 SG |
332 | static int display_init(struct udevice *dev, void *lcdbase, |
333 | int fb_bits_per_pixel, struct display_timing *timing) | |
e7e8823c | 334 | { |
d7659212 | 335 | struct display_plat *disp_uc_plat; |
e7e8823c SG |
336 | struct dc_ctlr *dc_ctlr; |
337 | const void *blob = gd->fdt_blob; | |
338 | struct udevice *dp_dev; | |
339 | const int href_to_sync = 1, vref_to_sync = 1; | |
340 | int panel_bpp = 18; /* default 18 bits per pixel */ | |
341 | u32 plld_rate; | |
e7e8823c SG |
342 | int ret; |
343 | ||
d7659212 SG |
344 | /* |
345 | * Before we probe the display device (eDP), tell it that this device | |
d5c453ab | 346 | * is the source of the display data. |
d7659212 SG |
347 | */ |
348 | ret = uclass_find_first_device(UCLASS_DISPLAY, &dp_dev); | |
349 | if (ret) { | |
350 | debug("%s: device '%s' display not found (ret=%d)\n", __func__, | |
351 | dev->name, ret); | |
352 | return ret; | |
353 | } | |
354 | ||
355 | disp_uc_plat = dev_get_uclass_platdata(dp_dev); | |
356 | debug("Found device '%s', disp_uc_priv=%p\n", dp_dev->name, | |
357 | disp_uc_plat); | |
358 | disp_uc_plat->src_dev = dev; | |
359 | ||
2dcf1433 | 360 | ret = uclass_get_device(UCLASS_DISPLAY, 0, &dp_dev); |
d7659212 SG |
361 | if (ret) { |
362 | debug("%s: Failed to probe eDP, ret=%d\n", __func__, ret); | |
e7e8823c | 363 | return ret; |
d7659212 | 364 | } |
e7e8823c | 365 | |
e160f7d4 | 366 | dc_ctlr = (struct dc_ctlr *)fdtdec_get_addr(blob, dev_of_offset(dev), |
d7659212 | 367 | "reg"); |
e160f7d4 | 368 | if (fdtdec_decode_display_timing(blob, dev_of_offset(dev), 0, timing)) { |
d7659212 | 369 | debug("%s: Failed to decode display timing\n", __func__); |
e7e8823c | 370 | return -EINVAL; |
d7659212 | 371 | } |
e7e8823c SG |
372 | |
373 | ret = display_update_config_from_edid(dp_dev, &panel_bpp, timing); | |
374 | if (ret) { | |
375 | debug("%s: Failed to decode EDID, using defaults\n", __func__); | |
376 | dump_config(panel_bpp, timing); | |
377 | } | |
378 | ||
e7e8823c SG |
379 | /* |
380 | * The plld is programmed with the assumption of the SHIFT_CLK_DIVIDER | |
381 | * and PIXEL_CLK_DIVIDER are zero (divide by 1). See the | |
382 | * update_display_mode() for detail. | |
383 | */ | |
384 | plld_rate = clock_set_display_rate(timing->pixelclock.typ * 2); | |
385 | if (plld_rate == 0) { | |
386 | printf("dc: clock init failed\n"); | |
387 | return -EIO; | |
388 | } else if (plld_rate != timing->pixelclock.typ * 2) { | |
389 | debug("dc: plld rounded to %u\n", plld_rate); | |
390 | timing->pixelclock.typ = plld_rate / 2; | |
391 | } | |
392 | ||
393 | /* Init dc */ | |
394 | ret = tegra_dc_init(dc_ctlr); | |
395 | if (ret) { | |
396 | debug("dc: init failed\n"); | |
397 | return ret; | |
398 | } | |
399 | ||
400 | /* Configure dc mode */ | |
401 | ret = update_display_mode(dc_ctlr, timing, href_to_sync, vref_to_sync); | |
402 | if (ret) { | |
403 | debug("dc: failed to configure display mode\n"); | |
404 | return ret; | |
405 | } | |
406 | ||
407 | /* Enable dp */ | |
2dcf1433 | 408 | ret = display_enable(dp_dev, panel_bpp, timing); |
d7659212 SG |
409 | if (ret) { |
410 | debug("dc: failed to enable display: ret=%d\n", ret); | |
e7e8823c | 411 | return ret; |
d7659212 | 412 | } |
e7e8823c SG |
413 | |
414 | ret = update_window(dc_ctlr, (ulong)lcdbase, fb_bits_per_pixel, timing); | |
d7659212 SG |
415 | if (ret) { |
416 | debug("dc: failed to update window\n"); | |
e7e8823c | 417 | return ret; |
e7e8823c SG |
418 | } |
419 | ||
420 | return 0; | |
421 | } | |
4dd81158 SG |
422 | |
423 | enum { | |
424 | /* Maximum LCD size we support */ | |
425 | LCD_MAX_WIDTH = 1920, | |
426 | LCD_MAX_HEIGHT = 1200, | |
427 | LCD_MAX_LOG2_BPP = 4, /* 2^4 = 16 bpp */ | |
428 | }; | |
429 | ||
d7659212 SG |
430 | static int tegra124_lcd_init(struct udevice *dev, void *lcdbase, |
431 | enum video_log2_bpp l2bpp) | |
4dd81158 | 432 | { |
d7659212 | 433 | struct video_priv *uc_priv = dev_get_uclass_priv(dev); |
4dd81158 SG |
434 | struct display_timing timing; |
435 | int ret; | |
436 | ||
437 | clock_set_up_plldp(); | |
438 | clock_start_periph_pll(PERIPH_ID_HOST1X, CLOCK_ID_PERIPH, 408000000); | |
439 | ||
440 | clock_enable(PERIPH_ID_HOST1X); | |
441 | clock_enable(PERIPH_ID_DISP1); | |
442 | clock_enable(PERIPH_ID_PWM); | |
443 | clock_enable(PERIPH_ID_DPAUX); | |
444 | clock_enable(PERIPH_ID_SOR0); | |
445 | udelay(2); | |
446 | ||
447 | reset_set_enable(PERIPH_ID_HOST1X, 0); | |
448 | reset_set_enable(PERIPH_ID_DISP1, 0); | |
449 | reset_set_enable(PERIPH_ID_PWM, 0); | |
450 | reset_set_enable(PERIPH_ID_DPAUX, 0); | |
451 | reset_set_enable(PERIPH_ID_SOR0, 0); | |
452 | ||
d7659212 | 453 | ret = display_init(dev, lcdbase, 1 << l2bpp, &timing); |
4dd81158 SG |
454 | if (ret) |
455 | return ret; | |
456 | ||
d7659212 SG |
457 | uc_priv->xsize = roundup(timing.hactive.typ, 16); |
458 | uc_priv->ysize = timing.vactive.typ; | |
459 | uc_priv->bpix = l2bpp; | |
4dd81158 | 460 | |
d7659212 SG |
461 | video_set_flush_dcache(dev, 1); |
462 | debug("%s: done\n", __func__); | |
4dd81158 SG |
463 | |
464 | return 0; | |
465 | } | |
466 | ||
d7659212 | 467 | static int tegra124_lcd_probe(struct udevice *dev) |
4dd81158 | 468 | { |
d7659212 | 469 | struct video_uc_platdata *plat = dev_get_uclass_platdata(dev); |
4dd81158 SG |
470 | ulong start; |
471 | int ret; | |
472 | ||
473 | start = get_timer(0); | |
d7659212 | 474 | ret = tegra124_lcd_init(dev, (void *)plat->base, VIDEO_BPP16); |
4dd81158 SG |
475 | debug("LCD init took %lu ms\n", get_timer(start)); |
476 | if (ret) | |
477 | printf("%s: Error %d\n", __func__, ret); | |
d7659212 SG |
478 | |
479 | return 0; | |
4dd81158 SG |
480 | } |
481 | ||
d7659212 | 482 | static int tegra124_lcd_bind(struct udevice *dev) |
4dd81158 | 483 | { |
d7659212 SG |
484 | struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev); |
485 | ||
486 | uc_plat->size = LCD_MAX_WIDTH * LCD_MAX_HEIGHT * | |
487 | (1 << VIDEO_BPP16) / 8; | |
488 | debug("%s: Frame buffer size %x\n", __func__, uc_plat->size); | |
489 | ||
490 | return 0; | |
4dd81158 | 491 | } |
d7659212 SG |
492 | |
493 | static const struct udevice_id tegra124_lcd_ids[] = { | |
494 | { .compatible = "nvidia,tegra124-dc" }, | |
495 | { } | |
496 | }; | |
497 | ||
498 | U_BOOT_DRIVER(tegra124_dc) = { | |
499 | .name = "tegra124-dc", | |
500 | .id = UCLASS_VIDEO, | |
501 | .of_match = tegra124_lcd_ids, | |
502 | .bind = tegra124_lcd_bind, | |
503 | .probe = tegra124_lcd_probe, | |
504 | }; |