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