1 /*
2  * (C) Copyright 2012 - 2013 CompuLab, Ltd. <www.compulab.co.il>
3  *
4  * Authors: Nikita Kiryanov <nikita@compulab.co.il>
5  *
6  * Parsing code based on linux/drivers/video/pxafb.c
7  *
8  * SPDX-License-Identifier:	GPL-2.0+
9  */
10 
11 #include <common.h>
12 #include <asm/gpio.h>
13 #include <asm/io.h>
14 #include <stdio_dev.h>
15 #include <asm/arch/dss.h>
16 #include <lcd.h>
17 #include <asm/arch-omap3/dss.h>
18 
19 DECLARE_GLOBAL_DATA_PTR;
20 
21 enum display_type {
22 	NONE,
23 	DVI,
24 	DVI_CUSTOM,
25 };
26 
27 #define CMAP_ADDR	0x80100000
28 
29 /*
30  * The frame buffer is allocated before we have the chance to parse user input.
31  * To make sure enough memory is allocated for all resolutions, we define
32  * vl_{col | row} to the maximal resolution supported by OMAP3.
33  */
34 vidinfo_t panel_info = {
35 	.vl_col  = 1400,
36 	.vl_row  = 1050,
37 	.vl_bpix = LCD_BPP,
38 	.cmap = (ushort *)CMAP_ADDR,
39 };
40 
41 static struct panel_config panel_cfg;
42 static enum display_type lcd_def;
43 
44 /*
45  * A note on DVI presets;
46  * U-Boot can convert 8 bit BMP data to 16 bit BMP data, and OMAP DSS can
47  * convert 16 bit data into 24 bit data. Thus, GFXFORMAT_RGB16 allows us to
48  * support two BMP types with one setting.
49  */
50 static const struct panel_config preset_dvi_640X480 = {
51 	.lcd_size	= PANEL_LCD_SIZE(640, 480),
52 	.timing_h	= DSS_HBP(48) | DSS_HFP(16) | DSS_HSW(96),
53 	.timing_v	= DSS_VBP(33) | DSS_VFP(10) | DSS_VSW(2),
54 	.pol_freq	= DSS_IHS | DSS_IVS | DSS_IPC,
55 	.divisor	= 12 | (1 << 16),
56 	.data_lines	= LCD_INTERFACE_24_BIT,
57 	.panel_type	= ACTIVE_DISPLAY,
58 	.load_mode	= 2,
59 	.gfx_format	= GFXFORMAT_RGB16,
60 };
61 
62 static const struct panel_config preset_dvi_800X600 = {
63 	.lcd_size	= PANEL_LCD_SIZE(800, 600),
64 	.timing_h	= DSS_HBP(88) | DSS_HFP(40) | DSS_HSW(128),
65 	.timing_v	= DSS_VBP(23) | DSS_VFP(1) | DSS_VSW(4),
66 	.pol_freq	= DSS_IHS | DSS_IVS | DSS_IPC,
67 	.divisor	= 8 | (1 << 16),
68 	.data_lines	= LCD_INTERFACE_24_BIT,
69 	.panel_type	= ACTIVE_DISPLAY,
70 	.load_mode	= 2,
71 	.gfx_format	= GFXFORMAT_RGB16,
72 };
73 
74 static const struct panel_config preset_dvi_1024X768 = {
75 	.lcd_size	= PANEL_LCD_SIZE(1024, 768),
76 	.timing_h	= DSS_HBP(160) | DSS_HFP(24) | DSS_HSW(136),
77 	.timing_v	= DSS_VBP(29) | DSS_VFP(3) | DSS_VSW(6),
78 	.pol_freq	= DSS_IHS | DSS_IVS | DSS_IPC,
79 	.divisor	= 5 | (1 << 16),
80 	.data_lines	= LCD_INTERFACE_24_BIT,
81 	.panel_type	= ACTIVE_DISPLAY,
82 	.load_mode	= 2,
83 	.gfx_format	= GFXFORMAT_RGB16,
84 };
85 
86 static const struct panel_config preset_dvi_1152X864 = {
87 	.lcd_size	= PANEL_LCD_SIZE(1152, 864),
88 	.timing_h	= DSS_HBP(256) | DSS_HFP(64) | DSS_HSW(128),
89 	.timing_v	= DSS_VBP(32) | DSS_VFP(1) | DSS_VSW(3),
90 	.pol_freq	= DSS_IHS | DSS_IVS | DSS_IPC,
91 	.divisor	= 4 | (1 << 16),
92 	.data_lines	= LCD_INTERFACE_24_BIT,
93 	.panel_type	= ACTIVE_DISPLAY,
94 	.load_mode	= 2,
95 	.gfx_format	= GFXFORMAT_RGB16,
96 };
97 
98 static const struct panel_config preset_dvi_1280X960 = {
99 	.lcd_size	= PANEL_LCD_SIZE(1280, 960),
100 	.timing_h	= DSS_HBP(312) | DSS_HFP(96) | DSS_HSW(112),
101 	.timing_v	= DSS_VBP(36) | DSS_VFP(1) | DSS_VSW(3),
102 	.pol_freq	= DSS_IHS | DSS_IVS | DSS_IPC,
103 	.divisor	= 3 | (1 << 16),
104 	.data_lines	= LCD_INTERFACE_24_BIT,
105 	.panel_type	= ACTIVE_DISPLAY,
106 	.load_mode	= 2,
107 	.gfx_format	= GFXFORMAT_RGB16,
108 };
109 
110 static const struct panel_config preset_dvi_1280X1024 = {
111 	.lcd_size	= PANEL_LCD_SIZE(1280, 1024),
112 	.timing_h	= DSS_HBP(248) | DSS_HFP(48) | DSS_HSW(112),
113 	.timing_v	= DSS_VBP(38) | DSS_VFP(1) | DSS_VSW(3),
114 	.pol_freq	= DSS_IHS | DSS_IVS | DSS_IPC,
115 	.divisor	= 3 | (1 << 16),
116 	.data_lines	= LCD_INTERFACE_24_BIT,
117 	.panel_type	= ACTIVE_DISPLAY,
118 	.load_mode	= 2,
119 	.gfx_format	= GFXFORMAT_RGB16,
120 };
121 
122 /*
123  * set_resolution_params()
124  *
125  * Due to usage of multiple display related APIs resolution data is located in
126  * more than one place. This function updates them all.
127  */
128 static void set_resolution_params(int x, int y)
129 {
130 	panel_cfg.lcd_size = PANEL_LCD_SIZE(x, y);
131 	panel_info.vl_col = x;
132 	panel_info.vl_row = y;
133 	lcd_line_length = (panel_info.vl_col * NBITS(panel_info.vl_bpix)) / 8;
134 }
135 
136 static void set_preset(const struct panel_config preset, int x_res, int y_res)
137 {
138 	panel_cfg = preset;
139 	set_resolution_params(x_res, y_res);
140 }
141 
142 static enum display_type set_dvi_preset(const struct panel_config preset,
143 					int x_res, int y_res)
144 {
145 	set_preset(preset, x_res, y_res);
146 	return DVI;
147 }
148 
149 /*
150  * parse_mode() - parse the mode parameter of custom lcd settings
151  *
152  * @mode:	<res_x>x<res_y>
153  *
154  * Returns -1 on error, 0 on success.
155  */
156 static int parse_mode(const char *mode)
157 {
158 	unsigned int modelen = strlen(mode);
159 	int res_specified = 0;
160 	unsigned int xres = 0, yres = 0;
161 	int yres_specified = 0;
162 	int i;
163 
164 	for (i = modelen - 1; i >= 0; i--) {
165 		switch (mode[i]) {
166 		case 'x':
167 			if (!yres_specified) {
168 				yres = simple_strtoul(&mode[i + 1], NULL, 0);
169 				yres_specified = 1;
170 			} else {
171 				goto done_parsing;
172 			}
173 
174 			break;
175 		case '0' ... '9':
176 			break;
177 		default:
178 			goto done_parsing;
179 		}
180 	}
181 
182 	if (i < 0 && yres_specified) {
183 		xres = simple_strtoul(mode, NULL, 0);
184 		res_specified = 1;
185 	}
186 
187 done_parsing:
188 	if (res_specified) {
189 		set_resolution_params(xres, yres);
190 	} else {
191 		printf("LCD: invalid mode: %s\n", mode);
192 		return -1;
193 	}
194 
195 	return 0;
196 }
197 
198 #define PIXEL_CLK_NUMERATOR (26 * 432 / 39)
199 /*
200  * parse_pixclock() - Parse the pixclock parameter of custom lcd settings
201  *
202  * @pixclock:	the desired pixel clock
203  *
204  * Returns -1 on error, 0 on success.
205  *
206  * Handling the pixel_clock:
207  *
208  * Pixel clock is defined in the OMAP35x TRM as follows:
209  * pixel_clock =
210  * (SYS_CLK * 2 * PRCM.CM_CLKSEL2_PLL[18:8]) /
211  * (DSS.DISPC_DIVISOR[23:16] * DSS.DISPC_DIVISOR[6:0] *
212  * PRCM.CM_CLKSEL_DSS[4:0] * (PRCM.CM_CLKSEL2_PLL[6:0] + 1))
213  *
214  * In practice, this means that in order to set the
215  * divisor for the desired pixel clock one needs to
216  * solve the following equation:
217  *
218  * 26 * 432 / (39 * <pixel_clock>) = DSS.DISPC_DIVISOR[6:0]
219  *
220  * NOTE: the explicit equation above is reduced. Do not
221  * try to infer anything from these numbers.
222  */
223 static int parse_pixclock(char *pixclock)
224 {
225 	int divisor, pixclock_val;
226 	char *pixclk_start = pixclock;
227 
228 	pixclock_val = simple_strtoul(pixclock, &pixclock, 10);
229 	divisor = DIV_ROUND_UP(PIXEL_CLK_NUMERATOR, pixclock_val);
230 	/* 0 and 1 are illegal values for PCD */
231 	if (divisor <= 1)
232 		divisor = 2;
233 
234 	panel_cfg.divisor = divisor | (1 << 16);
235 	if (pixclock[0] != '\0') {
236 		printf("LCD: invalid value for pixclock:%s\n", pixclk_start);
237 		return -1;
238 	}
239 
240 	return 0;
241 }
242 
243 /*
244  * parse_setting() - parse a single setting of custom lcd parameters
245  *
246  * @setting:	The custom lcd setting <name>:<value>
247  *
248  * Returns -1 on failure, 0 on success.
249  */
250 static int parse_setting(char *setting)
251 {
252 	int num_val;
253 	char *setting_start = setting;
254 
255 	if (!strncmp(setting, "mode:", 5)) {
256 		return parse_mode(setting + 5);
257 	} else if (!strncmp(setting, "pixclock:", 9)) {
258 		return parse_pixclock(setting + 9);
259 	} else if (!strncmp(setting, "left:", 5)) {
260 		num_val = simple_strtoul(setting + 5, &setting, 0);
261 		panel_cfg.timing_h |= DSS_HBP(num_val);
262 	} else if (!strncmp(setting, "right:", 6)) {
263 		num_val = simple_strtoul(setting + 6, &setting, 0);
264 		panel_cfg.timing_h |= DSS_HFP(num_val);
265 	} else if (!strncmp(setting, "upper:", 6)) {
266 		num_val = simple_strtoul(setting + 6, &setting, 0);
267 		panel_cfg.timing_v |= DSS_VBP(num_val);
268 	} else if (!strncmp(setting, "lower:", 6)) {
269 		num_val = simple_strtoul(setting + 6, &setting, 0);
270 		panel_cfg.timing_v |= DSS_VFP(num_val);
271 	} else if (!strncmp(setting, "hsynclen:", 9)) {
272 		num_val = simple_strtoul(setting + 9, &setting, 0);
273 		panel_cfg.timing_h |= DSS_HSW(num_val);
274 	} else if (!strncmp(setting, "vsynclen:", 9)) {
275 		num_val = simple_strtoul(setting + 9, &setting, 0);
276 		panel_cfg.timing_v |= DSS_VSW(num_val);
277 	} else if (!strncmp(setting, "hsync:", 6)) {
278 		if (simple_strtoul(setting + 6, &setting, 0) == 0)
279 			panel_cfg.pol_freq |= DSS_IHS;
280 		else
281 			panel_cfg.pol_freq &= ~DSS_IHS;
282 	} else if (!strncmp(setting, "vsync:", 6)) {
283 		if (simple_strtoul(setting + 6, &setting, 0) == 0)
284 			panel_cfg.pol_freq |= DSS_IVS;
285 		else
286 			panel_cfg.pol_freq &= ~DSS_IVS;
287 	} else if (!strncmp(setting, "outputen:", 9)) {
288 		if (simple_strtoul(setting + 9, &setting, 0) == 0)
289 			panel_cfg.pol_freq |= DSS_IEO;
290 		else
291 			panel_cfg.pol_freq &= ~DSS_IEO;
292 	} else if (!strncmp(setting, "pixclockpol:", 12)) {
293 		if (simple_strtoul(setting + 12, &setting, 0) == 0)
294 			panel_cfg.pol_freq |= DSS_IPC;
295 		else
296 			panel_cfg.pol_freq &= ~DSS_IPC;
297 	} else if (!strncmp(setting, "active", 6)) {
298 		panel_cfg.panel_type = ACTIVE_DISPLAY;
299 		return 0; /* Avoid sanity check below */
300 	} else if (!strncmp(setting, "passive", 7)) {
301 		panel_cfg.panel_type = PASSIVE_DISPLAY;
302 		return 0; /* Avoid sanity check below */
303 	} else if (!strncmp(setting, "display:", 8)) {
304 		if (!strncmp(setting + 8, "dvi", 3)) {
305 			lcd_def = DVI_CUSTOM;
306 			return 0; /* Avoid sanity check below */
307 		}
308 	} else {
309 		printf("LCD: unknown option %s\n", setting_start);
310 		return -1;
311 	}
312 
313 	if (setting[0] != '\0') {
314 		printf("LCD: invalid value for %s\n", setting_start);
315 		return -1;
316 	}
317 
318 	return 0;
319 }
320 
321 /*
322  * env_parse_customlcd() - parse custom lcd params from an environment variable.
323  *
324  * @custom_lcd_params:	The environment variable containing the lcd params.
325  *
326  * Returns -1 on failure, 0 on success.
327  */
328 static int parse_customlcd(char *custom_lcd_params)
329 {
330 	char params_cpy[160];
331 	char *setting;
332 
333 	strncpy(params_cpy, custom_lcd_params, 160);
334 	setting = strtok(params_cpy, ",");
335 	while (setting) {
336 		if (parse_setting(setting) < 0)
337 			return -1;
338 
339 		setting = strtok(NULL, ",");
340 	}
341 
342 	/* Currently we don't support changing this via custom lcd params */
343 	panel_cfg.data_lines = LCD_INTERFACE_24_BIT;
344 	panel_cfg.gfx_format = GFXFORMAT_RGB16; /* See dvi predefines note */
345 
346 	return 0;
347 }
348 
349 /*
350  * env_parse_displaytype() - parse display type.
351  *
352  * Parses the environment variable "displaytype", which contains the
353  * name of the display type or preset, in which case it applies its
354  * configurations.
355  *
356  * Returns the type of display that was specified.
357  */
358 static enum display_type env_parse_displaytype(char *displaytype)
359 {
360 	if (!strncmp(displaytype, "dvi640x480", 10))
361 		return set_dvi_preset(preset_dvi_640X480, 640, 480);
362 	else if (!strncmp(displaytype, "dvi800x600", 10))
363 		return set_dvi_preset(preset_dvi_800X600, 800, 600);
364 	else if (!strncmp(displaytype, "dvi1024x768", 11))
365 		return set_dvi_preset(preset_dvi_1024X768, 1024, 768);
366 	else if (!strncmp(displaytype, "dvi1152x864", 11))
367 		return set_dvi_preset(preset_dvi_1152X864, 1152, 864);
368 	else if (!strncmp(displaytype, "dvi1280x960", 11))
369 		return set_dvi_preset(preset_dvi_1280X960, 1280, 960);
370 	else if (!strncmp(displaytype, "dvi1280x1024", 12))
371 		return set_dvi_preset(preset_dvi_1280X1024, 1280, 1024);
372 
373 	return NONE;
374 }
375 
376 void lcd_ctrl_init(void *lcdbase)
377 {
378 	struct prcm *prcm = (struct prcm *)PRCM_BASE;
379 	char *custom_lcd;
380 	char *displaytype = getenv("displaytype");
381 
382 	if (displaytype == NULL)
383 		return;
384 
385 	lcd_def = env_parse_displaytype(displaytype);
386 	/* If we did not recognize the preset, check if it's an env variable */
387 	if (lcd_def == NONE) {
388 		custom_lcd = getenv(displaytype);
389 		if (custom_lcd == NULL || parse_customlcd(custom_lcd) < 0)
390 			return;
391 	}
392 
393 	panel_cfg.frame_buffer = lcdbase;
394 	omap3_dss_panel_config(&panel_cfg);
395 	/*
396 	 * Pixel clock is defined with many divisions and only few
397 	 * multiplications of the system clock. Since DSS FCLK divisor is set
398 	 * to 16 by default, we need to set it to a smaller value, like 3
399 	 * (chosen via trial and error).
400 	 */
401 	clrsetbits_le32(&prcm->clksel_dss, 0xF, 3);
402 }
403 
404 void lcd_enable(void)
405 {
406 	if (lcd_def == DVI || lcd_def == DVI_CUSTOM) {
407 		gpio_direction_output(54, 0); /* Turn on DVI */
408 		omap3_dss_enable();
409 	}
410 }
411 
412 void lcd_setcolreg(ushort regno, ushort red, ushort green, ushort blue) {}
413