Blame view

drivers/video/tegra.c 10.8 KB
0be8f203f   Simon Glass   tegra: Add LCD dr...
1
2
  /*
   * Copyright (c) 2011 The Chromium OS Authors.
1a4596601   Wolfgang Denk   Add GPL-2.0+ SPDX...
3
   * SPDX-License-Identifier:	GPL-2.0+
0be8f203f   Simon Glass   tegra: Add LCD dr...
4
   */
f20b2c067   Simon Glass   tegra: video: Rem...
5

0be8f203f   Simon Glass   tegra: Add LCD dr...
6
  #include <common.h>
9e6866d3b   Simon Glass   tegra: video: Con...
7
  #include <dm.h>
0be8f203f   Simon Glass   tegra: Add LCD dr...
8
  #include <fdtdec.h>
ec5507707   Simon Glass   video: tegra: Mov...
9
  #include <panel.h>
91c08afe6   Simon Glass   tegra: video: Mov...
10
  #include <pwm.h>
9e6866d3b   Simon Glass   tegra: video: Con...
11
  #include <video.h>
0be8f203f   Simon Glass   tegra: Add LCD dr...
12
13
  #include <asm/system.h>
  #include <asm/gpio.h>
71cafc3fb   Simon Glass   tegra: video: Mer...
14
  #include <asm/io.h>
0be8f203f   Simon Glass   tegra: Add LCD dr...
15
16
17
18
19
20
21
22
23
  
  #include <asm/arch/clock.h>
  #include <asm/arch/funcmux.h>
  #include <asm/arch/pinmux.h>
  #include <asm/arch/pwm.h>
  #include <asm/arch/display.h>
  #include <asm/arch-tegra/timer.h>
  
  DECLARE_GLOBAL_DATA_PTR;
ce0c474a7   Simon Glass   tegra: video: Mer...
24
25
  /* Information about the display controller */
  struct tegra_lcd_priv {
ce0c474a7   Simon Glass   tegra: video: Mer...
26
27
  	int width;			/* width in pixels */
  	int height;			/* height in pixels */
ec5507707   Simon Glass   video: tegra: Mov...
28
29
30
  	enum video_log2_bpp log2_bpp;	/* colour depth */
  	struct display_timing timing;
  	struct udevice *panel;
ce0c474a7   Simon Glass   tegra: video: Mer...
31
32
33
  	struct disp_ctlr *disp;		/* Display controller to use */
  	fdt_addr_t frame_buffer;	/* Address of frame buffer */
  	unsigned pixel_clock;		/* Pixel clock in Hz */
ce0c474a7   Simon Glass   tegra: video: Mer...
34
  };
0be8f203f   Simon Glass   tegra: Add LCD dr...
35
36
37
38
  enum {
  	/* Maximum LCD size we support */
  	LCD_MAX_WIDTH		= 1366,
  	LCD_MAX_HEIGHT		= 768,
9e6866d3b   Simon Glass   tegra: video: Con...
39
  	LCD_MAX_LOG2_BPP	= VIDEO_BPP16,
0be8f203f   Simon Glass   tegra: Add LCD dr...
40
  };
71cafc3fb   Simon Glass   tegra: video: Mer...
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
  static void update_window(struct dc_ctlr *dc, struct disp_ctl_win *win)
  {
  	unsigned h_dda, v_dda;
  	unsigned long val;
  
  	val = readl(&dc->cmd.disp_win_header);
  	val |= WINDOW_A_SELECT;
  	writel(val, &dc->cmd.disp_win_header);
  
  	writel(win->fmt, &dc->win.color_depth);
  
  	clrsetbits_le32(&dc->win.byte_swap, BYTE_SWAP_MASK,
  			BYTE_SWAP_NOSWAP << BYTE_SWAP_SHIFT);
  
  	val = win->out_x << H_POSITION_SHIFT;
  	val |= win->out_y << V_POSITION_SHIFT;
  	writel(val, &dc->win.pos);
  
  	val = win->out_w << H_SIZE_SHIFT;
  	val |= win->out_h << V_SIZE_SHIFT;
  	writel(val, &dc->win.size);
  
  	val = (win->w * win->bpp / 8) << H_PRESCALED_SIZE_SHIFT;
  	val |= win->h << V_PRESCALED_SIZE_SHIFT;
  	writel(val, &dc->win.prescaled_size);
  
  	writel(0, &dc->win.h_initial_dda);
  	writel(0, &dc->win.v_initial_dda);
  
  	h_dda = (win->w * 0x1000) / max(win->out_w - 1, 1U);
  	v_dda = (win->h * 0x1000) / max(win->out_h - 1, 1U);
  
  	val = h_dda << H_DDA_INC_SHIFT;
  	val |= v_dda << V_DDA_INC_SHIFT;
  	writel(val, &dc->win.dda_increment);
  
  	writel(win->stride, &dc->win.line_stride);
  	writel(0, &dc->win.buf_stride);
  
  	val = WIN_ENABLE;
  	if (win->bpp < 24)
  		val |= COLOR_EXPAND;
  	writel(val, &dc->win.win_opt);
  
  	writel((unsigned long)win->phys_addr, &dc->winbuf.start_addr);
  	writel(win->x, &dc->winbuf.addr_h_offset);
  	writel(win->y, &dc->winbuf.addr_v_offset);
  
  	writel(0xff00, &dc->win.blend_nokey);
  	writel(0xff00, &dc->win.blend_1win);
  
  	val = GENERAL_ACT_REQ | WIN_A_ACT_REQ;
  	val |= GENERAL_UPDATE | WIN_A_UPDATE;
  	writel(val, &dc->cmd.state_ctrl);
  }
71cafc3fb   Simon Glass   tegra: video: Mer...
96
  static int update_display_mode(struct dc_disp_reg *disp,
9e6866d3b   Simon Glass   tegra: video: Con...
97
  			       struct tegra_lcd_priv *priv)
71cafc3fb   Simon Glass   tegra: video: Mer...
98
  {
ec5507707   Simon Glass   video: tegra: Mov...
99
  	struct display_timing *dt = &priv->timing;
71cafc3fb   Simon Glass   tegra: video: Mer...
100
101
102
103
104
  	unsigned long val;
  	unsigned long rate;
  	unsigned long div;
  
  	writel(0x0, &disp->disp_timing_opt);
71cafc3fb   Simon Glass   tegra: video: Mer...
105

ec5507707   Simon Glass   video: tegra: Mov...
106
107
108
109
110
111
112
  	writel(1 | 1 << 16, &disp->ref_to_sync);
  	writel(dt->hsync_len.typ | dt->vsync_len.typ << 16, &disp->sync_width);
  	writel(dt->hback_porch.typ | dt->vback_porch.typ << 16,
  	       &disp->back_porch);
  	writel((dt->hfront_porch.typ - 1) | (dt->vfront_porch.typ - 1) << 16,
  	       &disp->front_porch);
  	writel(dt->hactive.typ | (dt->vactive.typ << 16), &disp->disp_active);
71cafc3fb   Simon Glass   tegra: video: Mer...
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
  
  	val = DE_SELECT_ACTIVE << DE_SELECT_SHIFT;
  	val |= DE_CONTROL_NORMAL << DE_CONTROL_SHIFT;
  	writel(val, &disp->data_enable_opt);
  
  	val = DATA_FORMAT_DF1P1C << DATA_FORMAT_SHIFT;
  	val |= DATA_ALIGNMENT_MSB << DATA_ALIGNMENT_SHIFT;
  	val |= DATA_ORDER_RED_BLUE << DATA_ORDER_SHIFT;
  	writel(val, &disp->disp_interface_ctrl);
  
  	/*
  	 * The pixel clock divider is in 7.1 format (where the bottom bit
  	 * represents 0.5). Here we calculate the divider needed to get from
  	 * the display clock (typically 600MHz) to the pixel clock. We round
  	 * up or down as requried.
  	 */
  	rate = clock_get_periph_rate(PERIPH_ID_DISP1, CLOCK_ID_CGENERAL);
9e6866d3b   Simon Glass   tegra: video: Con...
130
  	div = ((rate * 2 + priv->pixel_clock / 2) / priv->pixel_clock) - 2;
71cafc3fb   Simon Glass   tegra: video: Mer...
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
  	debug("Display clock %lu, divider %lu
  ", rate, div);
  
  	writel(0x00010001, &disp->shift_clk_opt);
  
  	val = PIXEL_CLK_DIVIDER_PCD1 << PIXEL_CLK_DIVIDER_SHIFT;
  	val |= div << SHIFT_CLK_DIVIDER_SHIFT;
  	writel(val, &disp->disp_clk_ctrl);
  
  	return 0;
  }
  
  /* Start up the display and turn on power to PWMs */
  static void basic_init(struct dc_cmd_reg *cmd)
  {
  	u32 val;
  
  	writel(0x00000100, &cmd->gen_incr_syncpt_ctrl);
  	writel(0x0000011a, &cmd->cont_syncpt_vsync);
  	writel(0x00000000, &cmd->int_type);
  	writel(0x00000000, &cmd->int_polarity);
  	writel(0x00000000, &cmd->int_mask);
  	writel(0x00000000, &cmd->int_enb);
  
  	val = PW0_ENABLE | PW1_ENABLE | PW2_ENABLE;
  	val |= PW3_ENABLE | PW4_ENABLE | PM0_ENABLE;
  	val |= PM1_ENABLE;
  	writel(val, &cmd->disp_pow_ctrl);
  
  	val = readl(&cmd->disp_cmd);
  	val |= CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT;
  	writel(val, &cmd->disp_cmd);
  }
  
  static void basic_init_timer(struct dc_disp_reg *disp)
  {
  	writel(0x00000020, &disp->mem_high_pri);
  	writel(0x00000001, &disp->mem_high_pri_timer);
  }
  
  static const u32 rgb_enb_tab[PIN_REG_COUNT] = {
  	0x00000000,
  	0x00000000,
  	0x00000000,
  	0x00000000,
  };
  
  static const u32 rgb_polarity_tab[PIN_REG_COUNT] = {
  	0x00000000,
  	0x01000000,
  	0x00000000,
  	0x00000000,
  };
  
  static const u32 rgb_data_tab[PIN_REG_COUNT] = {
  	0x00000000,
  	0x00000000,
  	0x00000000,
  	0x00000000,
  };
  
  static const u32 rgb_sel_tab[PIN_OUTPUT_SEL_COUNT] = {
  	0x00000000,
  	0x00000000,
  	0x00000000,
  	0x00000000,
  	0x00210222,
  	0x00002200,
  	0x00020000,
  };
  
  static void rgb_enable(struct dc_com_reg *com)
  {
  	int i;
  
  	for (i = 0; i < PIN_REG_COUNT; i++) {
  		writel(rgb_enb_tab[i], &com->pin_output_enb[i]);
  		writel(rgb_polarity_tab[i], &com->pin_output_polarity[i]);
  		writel(rgb_data_tab[i], &com->pin_output_data[i]);
  	}
  
  	for (i = 0; i < PIN_OUTPUT_SEL_COUNT; i++)
  		writel(rgb_sel_tab[i], &com->pin_output_sel[i]);
  }
  
  static int setup_window(struct disp_ctl_win *win,
9e6866d3b   Simon Glass   tegra: video: Con...
217
  			struct tegra_lcd_priv *priv)
71cafc3fb   Simon Glass   tegra: video: Mer...
218
219
220
  {
  	win->x = 0;
  	win->y = 0;
9e6866d3b   Simon Glass   tegra: video: Con...
221
222
  	win->w = priv->width;
  	win->h = priv->height;
71cafc3fb   Simon Glass   tegra: video: Mer...
223
224
  	win->out_x = 0;
  	win->out_y = 0;
9e6866d3b   Simon Glass   tegra: video: Con...
225
226
227
228
229
230
231
  	win->out_w = priv->width;
  	win->out_h = priv->height;
  	win->phys_addr = priv->frame_buffer;
  	win->stride = priv->width * (1 << priv->log2_bpp) / 8;
  	debug("%s: depth = %d
  ", __func__, priv->log2_bpp);
  	switch (priv->log2_bpp) {
ec5507707   Simon Glass   video: tegra: Mov...
232
  	case VIDEO_BPP32:
71cafc3fb   Simon Glass   tegra: video: Mer...
233
234
235
  		win->fmt = COLOR_DEPTH_R8G8B8A8;
  		win->bpp = 32;
  		break;
ec5507707   Simon Glass   video: tegra: Mov...
236
  	case VIDEO_BPP16:
71cafc3fb   Simon Glass   tegra: video: Mer...
237
238
239
240
241
242
243
244
245
246
247
  		win->fmt = COLOR_DEPTH_B5G6R5;
  		win->bpp = 16;
  		break;
  
  	default:
  		debug("Unsupported LCD bit depth");
  		return -1;
  	}
  
  	return 0;
  }
71cafc3fb   Simon Glass   tegra: video: Mer...
248
  /**
71cafc3fb   Simon Glass   tegra: video: Mer...
249
250
   * Register a new display based on device tree configuration.
   *
62a3b7dd0   Robert P. J. Day   Various, unrelate...
251
   * The frame buffer can be positioned by U-Boot or overridden by the fdt.
71cafc3fb   Simon Glass   tegra: video: Mer...
252
   * You should pass in the U-Boot address here, and check the contents of
ce0c474a7   Simon Glass   tegra: video: Mer...
253
   * struct tegra_lcd_priv to see what was actually chosen.
71cafc3fb   Simon Glass   tegra: video: Mer...
254
255
   *
   * @param blob			Device tree blob
9e6866d3b   Simon Glass   tegra: video: Con...
256
   * @param priv			Driver's private data
71cafc3fb   Simon Glass   tegra: video: Mer...
257
258
259
   * @param default_lcd_base	Default address of LCD frame buffer
   * @return 0 if ok, -1 on error (unsupported bits per pixel)
   */
9e6866d3b   Simon Glass   tegra: video: Con...
260
261
  static int tegra_display_probe(const void *blob, struct tegra_lcd_priv *priv,
  			       void *default_lcd_base)
71cafc3fb   Simon Glass   tegra: video: Mer...
262
263
264
  {
  	struct disp_ctl_win window;
  	struct dc_ctlr *dc;
9e6866d3b   Simon Glass   tegra: video: Con...
265
  	priv->frame_buffer = (u32)default_lcd_base;
71cafc3fb   Simon Glass   tegra: video: Mer...
266

9e6866d3b   Simon Glass   tegra: video: Con...
267
  	dc = (struct dc_ctlr *)priv->disp;
71cafc3fb   Simon Glass   tegra: video: Mer...
268
269
270
271
272
273
274
275
276
277
278
279
280
  
  	/*
  	 * A header file for clock constants was NAKed upstream.
  	 * TODO: Put this into the FDT and fdt_lcd struct when we have clock
  	 * support there
  	 */
  	clock_start_periph_pll(PERIPH_ID_HOST1X, CLOCK_ID_PERIPH,
  			       144 * 1000000);
  	clock_start_periph_pll(PERIPH_ID_DISP1, CLOCK_ID_CGENERAL,
  			       600 * 1000000);
  	basic_init(&dc->cmd);
  	basic_init_timer(&dc->disp);
  	rgb_enable(&dc->com);
9e6866d3b   Simon Glass   tegra: video: Con...
281
282
  	if (priv->pixel_clock)
  		update_display_mode(&dc->disp, priv);
71cafc3fb   Simon Glass   tegra: video: Mer...
283

9e6866d3b   Simon Glass   tegra: video: Con...
284
  	if (setup_window(&window, priv))
71cafc3fb   Simon Glass   tegra: video: Mer...
285
286
287
288
289
290
  		return -1;
  
  	update_window(dc, &window);
  
  	return 0;
  }
9e6866d3b   Simon Glass   tegra: video: Con...
291
  static int tegra_lcd_probe(struct udevice *dev)
0be8f203f   Simon Glass   tegra: Add LCD dr...
292
  {
9e6866d3b   Simon Glass   tegra: video: Con...
293
294
295
296
  	struct video_uc_platdata *plat = dev_get_uclass_platdata(dev);
  	struct video_priv *uc_priv = dev_get_uclass_priv(dev);
  	struct tegra_lcd_priv *priv = dev_get_priv(dev);
  	const void *blob = gd->fdt_blob;
ec5507707   Simon Glass   video: tegra: Mov...
297
  	int ret;
9e6866d3b   Simon Glass   tegra: video: Con...
298

9e6866d3b   Simon Glass   tegra: video: Con...
299
  	/* Initialize the Tegra display controller */
ec5507707   Simon Glass   video: tegra: Mov...
300
  	funcmux_select(PERIPH_ID_DISP1, FUNCMUX_DEFAULT);
9e6866d3b   Simon Glass   tegra: video: Con...
301
302
303
304
  	if (tegra_display_probe(blob, priv, (void *)plat->base)) {
  		printf("%s: Failed to probe display driver
  ", __func__);
  		return -1;
0be8f203f   Simon Glass   tegra: Add LCD dr...
305
  	}
9e6866d3b   Simon Glass   tegra: video: Con...
306

ec5507707   Simon Glass   video: tegra: Mov...
307
308
309
310
311
312
313
314
315
  	pinmux_set_func(PMUX_PINGRP_GPU, PMUX_FUNC_PWM);
  	pinmux_tristate_disable(PMUX_PINGRP_GPU);
  
  	ret = panel_enable_backlight(priv->panel);
  	if (ret) {
  		debug("%s: Cannot enable backlight, ret=%d
  ", __func__, ret);
  		return ret;
  	}
9e6866d3b   Simon Glass   tegra: video: Con...
316

8d37483e7   Simon Glass   tegra: video: Alw...
317
318
  	mmu_set_region_dcache_behaviour(priv->frame_buffer, plat->size,
  					DCACHE_WRITETHROUGH);
9e6866d3b   Simon Glass   tegra: video: Con...
319
320
  
  	/* Enable flushing after LCD writes if requested */
8d37483e7   Simon Glass   tegra: video: Alw...
321
  	video_set_flush_dcache(dev, true);
9e6866d3b   Simon Glass   tegra: video: Con...
322
323
324
325
326
327
328
329
330
331
  
  	uc_priv->xsize = priv->width;
  	uc_priv->ysize = priv->height;
  	uc_priv->bpix = priv->log2_bpp;
  	debug("LCD frame buffer at %pa, size %x
  ", &priv->frame_buffer,
  	      plat->size);
  
  	return 0;
  }
f5acf91f6   Simon Glass   tegra: video: Mov...
332
333
334
335
  static int tegra_lcd_ofdata_to_platdata(struct udevice *dev)
  {
  	struct tegra_lcd_priv *priv = dev_get_priv(dev);
  	const void *blob = gd->fdt_blob;
ec5507707   Simon Glass   video: tegra: Mov...
336
  	struct display_timing *timing;
e160f7d43   Simon Glass   dm: core: Replace...
337
  	int node = dev_of_offset(dev);
f5acf91f6   Simon Glass   tegra: video: Mov...
338
339
  	int panel_node;
  	int rgb;
91c08afe6   Simon Glass   tegra: video: Mov...
340
  	int ret;
f5acf91f6   Simon Glass   tegra: video: Mov...
341

a821c4af7   Simon Glass   dm: Rename dev_ad...
342
  	priv->disp = (struct disp_ctlr *)devfdt_get_addr(dev);
f5acf91f6   Simon Glass   tegra: video: Mov...
343
344
345
346
347
348
349
  	if (!priv->disp) {
  		debug("%s: No display controller address
  ", __func__);
  		return -EINVAL;
  	}
  
  	rgb = fdt_subnode_offset(blob, node, "rgb");
ec5507707   Simon Glass   video: tegra: Mov...
350
351
352
353
  	if (rgb < 0) {
  		debug("%s: Cannot find rgb subnode for '%s' (ret=%d)
  ",
  		      __func__, dev->name, rgb);
f5acf91f6   Simon Glass   tegra: video: Mov...
354
355
  		return -EINVAL;
  	}
ec5507707   Simon Glass   video: tegra: Mov...
356
357
358
359
360
  	ret = fdtdec_decode_display_timing(blob, rgb, 0, &priv->timing);
  	if (ret) {
  		debug("%s: Cannot read display timing for '%s' (ret=%d)
  ",
  		      __func__, dev->name, ret);
f5acf91f6   Simon Glass   tegra: video: Mov...
361
362
  		return -EINVAL;
  	}
ec5507707   Simon Glass   video: tegra: Mov...
363
364
365
366
367
  	timing = &priv->timing;
  	priv->width = timing->hactive.typ;
  	priv->height = timing->vactive.typ;
  	priv->pixel_clock = timing->pixelclock.typ;
  	priv->log2_bpp = VIDEO_BPP16;
f5acf91f6   Simon Glass   tegra: video: Mov...
368

ec5507707   Simon Glass   video: tegra: Mov...
369
370
371
372
373
374
375
376
  	/*
  	 * Sadly the panel phandle is in an rgb subnode so we cannot use
  	 * uclass_get_device_by_phandle().
  	 */
  	panel_node = fdtdec_lookup_phandle(blob, rgb, "nvidia,panel");
  	if (panel_node < 0) {
  		debug("%s: Cannot find panel information
  ", __func__);
f5acf91f6   Simon Glass   tegra: video: Mov...
377
378
  		return -EINVAL;
  	}
ec5507707   Simon Glass   video: tegra: Mov...
379
380
  	ret = uclass_get_device_by_of_offset(UCLASS_PANEL, panel_node,
  					     &priv->panel);
91c08afe6   Simon Glass   tegra: video: Mov...
381
  	if (ret) {
ec5507707   Simon Glass   video: tegra: Mov...
382
383
384
385
  		debug("%s: Cannot find panel for '%s' (ret=%d)
  ", __func__,
  		      dev->name, ret);
  		return ret;
91c08afe6   Simon Glass   tegra: video: Mov...
386
  	}
f5acf91f6   Simon Glass   tegra: video: Mov...
387
388
389
  
  	return 0;
  }
9e6866d3b   Simon Glass   tegra: video: Con...
390
391
392
  static int tegra_lcd_bind(struct udevice *dev)
  {
  	struct video_uc_platdata *plat = dev_get_uclass_platdata(dev);
54693cbdc   Stephen Warren   video: tegra: ref...
393
  	const void *blob = gd->fdt_blob;
e160f7d43   Simon Glass   dm: core: Replace...
394
  	int node = dev_of_offset(dev);
54693cbdc   Stephen Warren   video: tegra: ref...
395
396
397
398
399
  	int rgb;
  
  	rgb = fdt_subnode_offset(blob, node, "rgb");
  	if ((rgb < 0) || !fdtdec_get_is_enabled(blob, rgb))
  		return -ENODEV;
9e6866d3b   Simon Glass   tegra: video: Con...
400
401
402
403
404
  
  	plat->size = LCD_MAX_WIDTH * LCD_MAX_HEIGHT *
  		(1 << LCD_MAX_LOG2_BPP) / 8;
  
  	return 0;
0be8f203f   Simon Glass   tegra: Add LCD dr...
405
  }
9e6866d3b   Simon Glass   tegra: video: Con...
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
  
  static const struct video_ops tegra_lcd_ops = {
  };
  
  static const struct udevice_id tegra_lcd_ids[] = {
  	{ .compatible = "nvidia,tegra20-dc" },
  	{ }
  };
  
  U_BOOT_DRIVER(tegra_lcd) = {
  	.name	= "tegra_lcd",
  	.id	= UCLASS_VIDEO,
  	.of_match = tegra_lcd_ids,
  	.ops	= &tegra_lcd_ops,
  	.bind	= tegra_lcd_bind,
  	.probe	= tegra_lcd_probe,
f5acf91f6   Simon Glass   tegra: video: Mov...
422
  	.ofdata_to_platdata	= tegra_lcd_ofdata_to_platdata,
9e6866d3b   Simon Glass   tegra: video: Con...
423
424
  	.priv_auto_alloc_size	= sizeof(struct tegra_lcd_priv),
  };