]>
Commit | Line | Data |
---|---|---|
87540de3 WN |
1 | /* |
2 | * (C) Copyright 2010 | |
3 | * NVIDIA Corporation <www.nvidia.com> | |
4 | * | |
1a459660 | 5 | * SPDX-License-Identifier: GPL-2.0+ |
87540de3 WN |
6 | */ |
7 | ||
8 | #include <common.h> | |
9 | #include <asm/io.h> | |
10 | #include <asm/arch/clock.h> | |
11 | #include <asm/arch/tegra.h> | |
12 | #include <asm/arch/display.h> | |
13 | #include <asm/arch/dc.h> | |
14 | #include <asm/arch-tegra/clk_rst.h> | |
15 | #include <asm/arch-tegra/timer.h> | |
16 | ||
17 | static struct fdt_disp_config config; | |
18 | ||
19 | static void update_window(struct dc_ctlr *dc, struct disp_ctl_win *win) | |
20 | { | |
21 | unsigned h_dda, v_dda; | |
22 | unsigned long val; | |
23 | ||
24 | val = readl(&dc->cmd.disp_win_header); | |
25 | val |= WINDOW_A_SELECT; | |
26 | writel(val, &dc->cmd.disp_win_header); | |
27 | ||
28 | writel(win->fmt, &dc->win.color_depth); | |
29 | ||
30 | clrsetbits_le32(&dc->win.byte_swap, BYTE_SWAP_MASK, | |
31 | BYTE_SWAP_NOSWAP << BYTE_SWAP_SHIFT); | |
32 | ||
33 | val = win->out_x << H_POSITION_SHIFT; | |
34 | val |= win->out_y << V_POSITION_SHIFT; | |
35 | writel(val, &dc->win.pos); | |
36 | ||
37 | val = win->out_w << H_SIZE_SHIFT; | |
38 | val |= win->out_h << V_SIZE_SHIFT; | |
39 | writel(val, &dc->win.size); | |
40 | ||
41 | val = (win->w * win->bpp / 8) << H_PRESCALED_SIZE_SHIFT; | |
42 | val |= win->h << V_PRESCALED_SIZE_SHIFT; | |
43 | writel(val, &dc->win.prescaled_size); | |
44 | ||
45 | writel(0, &dc->win.h_initial_dda); | |
46 | writel(0, &dc->win.v_initial_dda); | |
47 | ||
b4141195 MY |
48 | h_dda = (win->w * 0x1000) / max(win->out_w - 1, 1U); |
49 | v_dda = (win->h * 0x1000) / max(win->out_h - 1, 1U); | |
87540de3 WN |
50 | |
51 | val = h_dda << H_DDA_INC_SHIFT; | |
52 | val |= v_dda << V_DDA_INC_SHIFT; | |
53 | writel(val, &dc->win.dda_increment); | |
54 | ||
55 | writel(win->stride, &dc->win.line_stride); | |
56 | writel(0, &dc->win.buf_stride); | |
57 | ||
58 | val = WIN_ENABLE; | |
59 | if (win->bpp < 24) | |
60 | val |= COLOR_EXPAND; | |
61 | writel(val, &dc->win.win_opt); | |
62 | ||
63 | writel((unsigned long)win->phys_addr, &dc->winbuf.start_addr); | |
64 | writel(win->x, &dc->winbuf.addr_h_offset); | |
65 | writel(win->y, &dc->winbuf.addr_v_offset); | |
66 | ||
67 | writel(0xff00, &dc->win.blend_nokey); | |
68 | writel(0xff00, &dc->win.blend_1win); | |
69 | ||
70 | val = GENERAL_ACT_REQ | WIN_A_ACT_REQ; | |
71 | val |= GENERAL_UPDATE | WIN_A_UPDATE; | |
72 | writel(val, &dc->cmd.state_ctrl); | |
73 | } | |
74 | ||
75 | static void write_pair(struct fdt_disp_config *config, int item, u32 *reg) | |
76 | { | |
77 | writel(config->horiz_timing[item] | | |
78 | (config->vert_timing[item] << 16), reg); | |
79 | } | |
80 | ||
81 | static int update_display_mode(struct dc_disp_reg *disp, | |
82 | struct fdt_disp_config *config) | |
83 | { | |
84 | unsigned long val; | |
85 | unsigned long rate; | |
86 | unsigned long div; | |
87 | ||
88 | writel(0x0, &disp->disp_timing_opt); | |
89 | write_pair(config, FDT_LCD_TIMING_REF_TO_SYNC, &disp->ref_to_sync); | |
90 | write_pair(config, FDT_LCD_TIMING_SYNC_WIDTH, &disp->sync_width); | |
91 | write_pair(config, FDT_LCD_TIMING_BACK_PORCH, &disp->back_porch); | |
92 | write_pair(config, FDT_LCD_TIMING_FRONT_PORCH, &disp->front_porch); | |
93 | ||
94 | writel(config->width | (config->height << 16), &disp->disp_active); | |
95 | ||
96 | val = DE_SELECT_ACTIVE << DE_SELECT_SHIFT; | |
97 | val |= DE_CONTROL_NORMAL << DE_CONTROL_SHIFT; | |
98 | writel(val, &disp->data_enable_opt); | |
99 | ||
100 | val = DATA_FORMAT_DF1P1C << DATA_FORMAT_SHIFT; | |
101 | val |= DATA_ALIGNMENT_MSB << DATA_ALIGNMENT_SHIFT; | |
102 | val |= DATA_ORDER_RED_BLUE << DATA_ORDER_SHIFT; | |
103 | writel(val, &disp->disp_interface_ctrl); | |
104 | ||
105 | /* | |
106 | * The pixel clock divider is in 7.1 format (where the bottom bit | |
107 | * represents 0.5). Here we calculate the divider needed to get from | |
108 | * the display clock (typically 600MHz) to the pixel clock. We round | |
109 | * up or down as requried. | |
110 | */ | |
111 | rate = clock_get_periph_rate(PERIPH_ID_DISP1, CLOCK_ID_CGENERAL); | |
112 | div = ((rate * 2 + config->pixel_clock / 2) / config->pixel_clock) - 2; | |
113 | debug("Display clock %lu, divider %lu\n", rate, div); | |
114 | ||
115 | writel(0x00010001, &disp->shift_clk_opt); | |
116 | ||
117 | val = PIXEL_CLK_DIVIDER_PCD1 << PIXEL_CLK_DIVIDER_SHIFT; | |
118 | val |= div << SHIFT_CLK_DIVIDER_SHIFT; | |
119 | writel(val, &disp->disp_clk_ctrl); | |
120 | ||
121 | return 0; | |
122 | } | |
123 | ||
124 | /* Start up the display and turn on power to PWMs */ | |
125 | static void basic_init(struct dc_cmd_reg *cmd) | |
126 | { | |
127 | u32 val; | |
128 | ||
129 | writel(0x00000100, &cmd->gen_incr_syncpt_ctrl); | |
130 | writel(0x0000011a, &cmd->cont_syncpt_vsync); | |
131 | writel(0x00000000, &cmd->int_type); | |
132 | writel(0x00000000, &cmd->int_polarity); | |
133 | writel(0x00000000, &cmd->int_mask); | |
134 | writel(0x00000000, &cmd->int_enb); | |
135 | ||
136 | val = PW0_ENABLE | PW1_ENABLE | PW2_ENABLE; | |
137 | val |= PW3_ENABLE | PW4_ENABLE | PM0_ENABLE; | |
138 | val |= PM1_ENABLE; | |
139 | writel(val, &cmd->disp_pow_ctrl); | |
140 | ||
141 | val = readl(&cmd->disp_cmd); | |
142 | val |= CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT; | |
143 | writel(val, &cmd->disp_cmd); | |
144 | } | |
145 | ||
146 | static void basic_init_timer(struct dc_disp_reg *disp) | |
147 | { | |
148 | writel(0x00000020, &disp->mem_high_pri); | |
149 | writel(0x00000001, &disp->mem_high_pri_timer); | |
150 | } | |
151 | ||
152 | static const u32 rgb_enb_tab[PIN_REG_COUNT] = { | |
153 | 0x00000000, | |
154 | 0x00000000, | |
155 | 0x00000000, | |
156 | 0x00000000, | |
157 | }; | |
158 | ||
159 | static const u32 rgb_polarity_tab[PIN_REG_COUNT] = { | |
160 | 0x00000000, | |
161 | 0x01000000, | |
162 | 0x00000000, | |
163 | 0x00000000, | |
164 | }; | |
165 | ||
166 | static const u32 rgb_data_tab[PIN_REG_COUNT] = { | |
167 | 0x00000000, | |
168 | 0x00000000, | |
169 | 0x00000000, | |
170 | 0x00000000, | |
171 | }; | |
172 | ||
173 | static const u32 rgb_sel_tab[PIN_OUTPUT_SEL_COUNT] = { | |
174 | 0x00000000, | |
175 | 0x00000000, | |
176 | 0x00000000, | |
177 | 0x00000000, | |
178 | 0x00210222, | |
179 | 0x00002200, | |
180 | 0x00020000, | |
181 | }; | |
182 | ||
183 | static void rgb_enable(struct dc_com_reg *com) | |
184 | { | |
185 | int i; | |
186 | ||
187 | for (i = 0; i < PIN_REG_COUNT; i++) { | |
188 | writel(rgb_enb_tab[i], &com->pin_output_enb[i]); | |
189 | writel(rgb_polarity_tab[i], &com->pin_output_polarity[i]); | |
190 | writel(rgb_data_tab[i], &com->pin_output_data[i]); | |
191 | } | |
192 | ||
193 | for (i = 0; i < PIN_OUTPUT_SEL_COUNT; i++) | |
194 | writel(rgb_sel_tab[i], &com->pin_output_sel[i]); | |
195 | } | |
196 | ||
19d7bf3d JH |
197 | static int setup_window(struct disp_ctl_win *win, |
198 | struct fdt_disp_config *config) | |
87540de3 WN |
199 | { |
200 | win->x = 0; | |
201 | win->y = 0; | |
202 | win->w = config->width; | |
203 | win->h = config->height; | |
204 | win->out_x = 0; | |
205 | win->out_y = 0; | |
206 | win->out_w = config->width; | |
207 | win->out_h = config->height; | |
208 | win->phys_addr = config->frame_buffer; | |
209 | win->stride = config->width * (1 << config->log2_bpp) / 8; | |
210 | debug("%s: depth = %d\n", __func__, config->log2_bpp); | |
211 | switch (config->log2_bpp) { | |
212 | case 5: | |
213 | case 24: | |
214 | win->fmt = COLOR_DEPTH_R8G8B8A8; | |
215 | win->bpp = 32; | |
216 | break; | |
217 | case 4: | |
218 | win->fmt = COLOR_DEPTH_B5G6R5; | |
219 | win->bpp = 16; | |
220 | break; | |
221 | ||
222 | default: | |
223 | debug("Unsupported LCD bit depth"); | |
224 | return -1; | |
225 | } | |
226 | ||
227 | return 0; | |
228 | } | |
229 | ||
230 | struct fdt_disp_config *tegra_display_get_config(void) | |
231 | { | |
232 | return config.valid ? &config : NULL; | |
233 | } | |
234 | ||
235 | static void debug_timing(const char *name, unsigned int timing[]) | |
236 | { | |
237 | #ifdef DEBUG | |
238 | int i; | |
239 | ||
240 | debug("%s timing: ", name); | |
241 | for (i = 0; i < FDT_LCD_TIMING_COUNT; i++) | |
242 | debug("%d ", timing[i]); | |
243 | debug("\n"); | |
244 | #endif | |
245 | } | |
246 | ||
247 | /** | |
248 | * Decode panel information from the fdt, according to a standard binding | |
249 | * | |
250 | * @param blob fdt blob | |
251 | * @param node offset of fdt node to read from | |
252 | * @param config structure to store fdt config into | |
253 | * @return 0 if ok, -ve on error | |
254 | */ | |
255 | static int tegra_decode_panel(const void *blob, int node, | |
256 | struct fdt_disp_config *config) | |
257 | { | |
258 | int front, back, ref; | |
259 | ||
260 | config->width = fdtdec_get_int(blob, node, "xres", -1); | |
261 | config->height = fdtdec_get_int(blob, node, "yres", -1); | |
262 | config->pixel_clock = fdtdec_get_int(blob, node, "clock", 0); | |
263 | if (!config->pixel_clock || config->width == -1 || | |
264 | config->height == -1) { | |
265 | debug("%s: Pixel parameters missing\n", __func__); | |
266 | return -FDT_ERR_NOTFOUND; | |
267 | } | |
268 | ||
269 | back = fdtdec_get_int(blob, node, "left-margin", -1); | |
270 | front = fdtdec_get_int(blob, node, "right-margin", -1); | |
271 | ref = fdtdec_get_int(blob, node, "hsync-len", -1); | |
272 | if ((back | front | ref) == -1) { | |
273 | debug("%s: Horizontal parameters missing\n", __func__); | |
274 | return -FDT_ERR_NOTFOUND; | |
275 | } | |
276 | ||
277 | /* Use a ref-to-sync of 1 always, and take this from the front porch */ | |
278 | config->horiz_timing[FDT_LCD_TIMING_REF_TO_SYNC] = 1; | |
279 | config->horiz_timing[FDT_LCD_TIMING_SYNC_WIDTH] = ref; | |
280 | config->horiz_timing[FDT_LCD_TIMING_BACK_PORCH] = back; | |
281 | config->horiz_timing[FDT_LCD_TIMING_FRONT_PORCH] = front - | |
282 | config->horiz_timing[FDT_LCD_TIMING_REF_TO_SYNC]; | |
283 | debug_timing("horiz", config->horiz_timing); | |
284 | ||
285 | back = fdtdec_get_int(blob, node, "upper-margin", -1); | |
286 | front = fdtdec_get_int(blob, node, "lower-margin", -1); | |
287 | ref = fdtdec_get_int(blob, node, "vsync-len", -1); | |
288 | if ((back | front | ref) == -1) { | |
289 | debug("%s: Vertical parameters missing\n", __func__); | |
290 | return -FDT_ERR_NOTFOUND; | |
291 | } | |
292 | ||
293 | config->vert_timing[FDT_LCD_TIMING_REF_TO_SYNC] = 1; | |
294 | config->vert_timing[FDT_LCD_TIMING_SYNC_WIDTH] = ref; | |
295 | config->vert_timing[FDT_LCD_TIMING_BACK_PORCH] = back; | |
296 | config->vert_timing[FDT_LCD_TIMING_FRONT_PORCH] = front - | |
297 | config->vert_timing[FDT_LCD_TIMING_REF_TO_SYNC]; | |
298 | debug_timing("vert", config->vert_timing); | |
299 | ||
300 | return 0; | |
301 | } | |
302 | ||
303 | /** | |
304 | * Decode the display controller information from the fdt. | |
305 | * | |
306 | * @param blob fdt blob | |
307 | * @param config structure to store fdt config into | |
308 | * @return 0 if ok, -ve on error | |
309 | */ | |
310 | static int tegra_display_decode_config(const void *blob, | |
311 | struct fdt_disp_config *config) | |
312 | { | |
313 | int node, rgb; | |
314 | int bpp, bit; | |
315 | ||
316 | /* TODO: Support multiple controllers */ | |
317 | node = fdtdec_next_compatible(blob, 0, COMPAT_NVIDIA_TEGRA20_DC); | |
318 | if (node < 0) { | |
319 | debug("%s: Cannot find display controller node in fdt\n", | |
320 | __func__); | |
321 | return node; | |
322 | } | |
323 | config->disp = (struct disp_ctlr *)fdtdec_get_addr(blob, node, "reg"); | |
324 | if (!config->disp) { | |
325 | debug("%s: No display controller address\n", __func__); | |
326 | return -1; | |
327 | } | |
328 | ||
329 | rgb = fdt_subnode_offset(blob, node, "rgb"); | |
330 | ||
331 | config->panel_node = fdtdec_lookup_phandle(blob, rgb, "nvidia,panel"); | |
c42ff090 | 332 | if (config->panel_node < 0) { |
87540de3 WN |
333 | debug("%s: Cannot find panel information\n", __func__); |
334 | return -1; | |
335 | } | |
336 | ||
337 | if (tegra_decode_panel(blob, config->panel_node, config)) { | |
338 | debug("%s: Failed to decode panel information\n", __func__); | |
339 | return -1; | |
340 | } | |
341 | ||
342 | bpp = fdtdec_get_int(blob, config->panel_node, "nvidia,bits-per-pixel", | |
343 | -1); | |
344 | bit = ffs(bpp) - 1; | |
345 | if (bpp == (1 << bit)) | |
346 | config->log2_bpp = bit; | |
347 | else | |
348 | config->log2_bpp = bpp; | |
349 | if (bpp == -1) { | |
350 | debug("%s: Pixel bpp parameters missing\n", __func__); | |
351 | return -FDT_ERR_NOTFOUND; | |
352 | } | |
353 | config->bpp = bpp; | |
354 | ||
355 | config->valid = 1; /* we have a valid configuration */ | |
356 | ||
357 | return 0; | |
358 | } | |
359 | ||
360 | int tegra_display_probe(const void *blob, void *default_lcd_base) | |
361 | { | |
362 | struct disp_ctl_win window; | |
363 | struct dc_ctlr *dc; | |
364 | ||
365 | if (tegra_display_decode_config(blob, &config)) | |
366 | return -1; | |
367 | ||
368 | config.frame_buffer = (u32)default_lcd_base; | |
369 | ||
370 | dc = (struct dc_ctlr *)config.disp; | |
371 | ||
372 | /* | |
373 | * A header file for clock constants was NAKed upstream. | |
374 | * TODO: Put this into the FDT and fdt_lcd struct when we have clock | |
375 | * support there | |
376 | */ | |
377 | clock_start_periph_pll(PERIPH_ID_HOST1X, CLOCK_ID_PERIPH, | |
378 | 144 * 1000000); | |
379 | clock_start_periph_pll(PERIPH_ID_DISP1, CLOCK_ID_CGENERAL, | |
380 | 600 * 1000000); | |
381 | basic_init(&dc->cmd); | |
382 | basic_init_timer(&dc->disp); | |
383 | rgb_enable(&dc->com); | |
384 | ||
385 | if (config.pixel_clock) | |
386 | update_display_mode(&dc->disp, &config); | |
387 | ||
388 | if (setup_window(&window, &config)) | |
389 | return -1; | |
390 | ||
391 | update_window(dc, &window); | |
392 | ||
393 | return 0; | |
394 | } |