1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (c) 2015 Google, Inc 4 * (C) Copyright 2001-2015 5 * DENX Software Engineering -- wd@denx.de 6 * Compulab Ltd - http://compulab.co.il/ 7 * Bernecker & Rainer Industrieelektronik GmbH - http://www.br-automation.com 8 */ 9 10 #include <common.h> 11 #include <linux/ctype.h> 12 #include <dm.h> 13 #include <video.h> 14 #include <video_console.h> 15 #include <video_font.h> /* Bitmap font for code page 437 */ 16 17 /* 18 * Structure to describe a console color 19 */ 20 struct vid_rgb { 21 u32 r; 22 u32 g; 23 u32 b; 24 }; 25 26 /* By default we scroll by a single line */ 27 #ifndef CONFIG_CONSOLE_SCROLL_LINES 28 #define CONFIG_CONSOLE_SCROLL_LINES 1 29 #endif 30 31 int vidconsole_putc_xy(struct udevice *dev, uint x, uint y, char ch) 32 { 33 struct vidconsole_ops *ops = vidconsole_get_ops(dev); 34 35 if (!ops->putc_xy) 36 return -ENOSYS; 37 return ops->putc_xy(dev, x, y, ch); 38 } 39 40 int vidconsole_move_rows(struct udevice *dev, uint rowdst, uint rowsrc, 41 uint count) 42 { 43 struct vidconsole_ops *ops = vidconsole_get_ops(dev); 44 45 if (!ops->move_rows) 46 return -ENOSYS; 47 return ops->move_rows(dev, rowdst, rowsrc, count); 48 } 49 50 int vidconsole_set_row(struct udevice *dev, uint row, int clr) 51 { 52 struct vidconsole_ops *ops = vidconsole_get_ops(dev); 53 54 if (!ops->set_row) 55 return -ENOSYS; 56 return ops->set_row(dev, row, clr); 57 } 58 59 static int vidconsole_entry_start(struct udevice *dev) 60 { 61 struct vidconsole_ops *ops = vidconsole_get_ops(dev); 62 63 if (!ops->entry_start) 64 return -ENOSYS; 65 return ops->entry_start(dev); 66 } 67 68 /* Move backwards one space */ 69 static int vidconsole_back(struct udevice *dev) 70 { 71 struct vidconsole_priv *priv = dev_get_uclass_priv(dev); 72 struct vidconsole_ops *ops = vidconsole_get_ops(dev); 73 int ret; 74 75 if (ops->backspace) { 76 ret = ops->backspace(dev); 77 if (ret != -ENOSYS) 78 return ret; 79 } 80 81 priv->xcur_frac -= VID_TO_POS(priv->x_charsize); 82 if (priv->xcur_frac < priv->xstart_frac) { 83 priv->xcur_frac = (priv->cols - 1) * 84 VID_TO_POS(priv->x_charsize); 85 priv->ycur -= priv->y_charsize; 86 if (priv->ycur < 0) 87 priv->ycur = 0; 88 } 89 video_sync(dev->parent, false); 90 91 return 0; 92 } 93 94 /* Move to a newline, scrolling the display if necessary */ 95 static void vidconsole_newline(struct udevice *dev) 96 { 97 struct vidconsole_priv *priv = dev_get_uclass_priv(dev); 98 struct udevice *vid_dev = dev->parent; 99 struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); 100 const int rows = CONFIG_CONSOLE_SCROLL_LINES; 101 int i; 102 103 priv->xcur_frac = priv->xstart_frac; 104 priv->ycur += priv->y_charsize; 105 106 /* Check if we need to scroll the terminal */ 107 if ((priv->ycur + priv->y_charsize) / priv->y_charsize > priv->rows) { 108 vidconsole_move_rows(dev, 0, rows, priv->rows - rows); 109 for (i = 0; i < rows; i++) 110 vidconsole_set_row(dev, priv->rows - i - 1, 111 vid_priv->colour_bg); 112 priv->ycur -= rows * priv->y_charsize; 113 } 114 priv->last_ch = 0; 115 116 video_sync(dev->parent, false); 117 } 118 119 static const struct vid_rgb colors[VID_COLOR_COUNT] = { 120 { 0x00, 0x00, 0x00 }, /* black */ 121 { 0xc0, 0x00, 0x00 }, /* red */ 122 { 0x00, 0xc0, 0x00 }, /* green */ 123 { 0xc0, 0x60, 0x00 }, /* brown */ 124 { 0x00, 0x00, 0xc0 }, /* blue */ 125 { 0xc0, 0x00, 0xc0 }, /* magenta */ 126 { 0x00, 0xc0, 0xc0 }, /* cyan */ 127 { 0xc0, 0xc0, 0xc0 }, /* light gray */ 128 { 0x80, 0x80, 0x80 }, /* gray */ 129 { 0xff, 0x00, 0x00 }, /* bright red */ 130 { 0x00, 0xff, 0x00 }, /* bright green */ 131 { 0xff, 0xff, 0x00 }, /* yellow */ 132 { 0x00, 0x00, 0xff }, /* bright blue */ 133 { 0xff, 0x00, 0xff }, /* bright magenta */ 134 { 0x00, 0xff, 0xff }, /* bright cyan */ 135 { 0xff, 0xff, 0xff }, /* white */ 136 }; 137 138 u32 vid_console_color(struct video_priv *priv, unsigned int idx) 139 { 140 switch (priv->bpix) { 141 case VIDEO_BPP16: 142 return ((colors[idx].r >> 3) << 11) | 143 ((colors[idx].g >> 2) << 5) | 144 ((colors[idx].b >> 3) << 0); 145 case VIDEO_BPP32: 146 return (colors[idx].r << 16) | 147 (colors[idx].g << 8) | 148 (colors[idx].b << 0); 149 default: 150 /* 151 * For unknown bit arrangements just support 152 * black and white. 153 */ 154 if (idx) 155 return 0xffffff; /* white */ 156 else 157 return 0x000000; /* black */ 158 } 159 } 160 161 static char *parsenum(char *s, int *num) 162 { 163 char *end; 164 *num = simple_strtol(s, &end, 10); 165 return end; 166 } 167 168 /** 169 * set_cursor_position() - set cursor position 170 * 171 * @priv: private data of the video console 172 * @row: new row 173 * @col: new column 174 */ 175 static void set_cursor_position(struct vidconsole_priv *priv, int row, int col) 176 { 177 /* 178 * Ensure we stay in the bounds of the screen. 179 */ 180 if (row >= priv->rows) 181 row = priv->rows - 1; 182 if (col >= priv->cols) 183 col = priv->cols - 1; 184 185 priv->ycur = row * priv->y_charsize; 186 priv->xcur_frac = priv->xstart_frac + 187 VID_TO_POS(col * priv->x_charsize); 188 } 189 190 /** 191 * get_cursor_position() - get cursor position 192 * 193 * @priv: private data of the video console 194 * @row: row 195 * @col: column 196 */ 197 static void get_cursor_position(struct vidconsole_priv *priv, 198 int *row, int *col) 199 { 200 *row = priv->ycur / priv->y_charsize; 201 *col = VID_TO_PIXEL(priv->xcur_frac - priv->xstart_frac) / 202 priv->x_charsize; 203 } 204 205 /* 206 * Process a character while accumulating an escape string. Chars are 207 * accumulated into escape_buf until the end of escape sequence is 208 * found, at which point the sequence is parsed and processed. 209 */ 210 static void vidconsole_escape_char(struct udevice *dev, char ch) 211 { 212 struct vidconsole_priv *priv = dev_get_uclass_priv(dev); 213 214 if (!IS_ENABLED(CONFIG_VIDEO_ANSI)) 215 goto error; 216 217 /* Sanity checking for bogus ESC sequences: */ 218 if (priv->escape_len >= sizeof(priv->escape_buf)) 219 goto error; 220 if (priv->escape_len == 0) { 221 switch (ch) { 222 case '7': 223 /* Save cursor position */ 224 get_cursor_position(priv, &priv->row_saved, 225 &priv->col_saved); 226 priv->escape = 0; 227 228 return; 229 case '8': { 230 /* Restore cursor position */ 231 int row = priv->row_saved; 232 int col = priv->col_saved; 233 234 set_cursor_position(priv, row, col); 235 priv->escape = 0; 236 return; 237 } 238 case '[': 239 break; 240 default: 241 goto error; 242 } 243 } 244 245 priv->escape_buf[priv->escape_len++] = ch; 246 247 /* 248 * Escape sequences are terminated by a letter, so keep 249 * accumulating until we get one: 250 */ 251 if (!isalpha(ch)) 252 return; 253 254 /* 255 * clear escape mode first, otherwise things will get highly 256 * surprising if you hit any debug prints that come back to 257 * this console. 258 */ 259 priv->escape = 0; 260 261 switch (ch) { 262 case 'H': 263 case 'f': { 264 int row, col; 265 char *s = priv->escape_buf; 266 267 /* 268 * Set cursor position: [%d;%df or [%d;%dH 269 */ 270 s++; /* [ */ 271 s = parsenum(s, &row); 272 s++; /* ; */ 273 s = parsenum(s, &col); 274 275 set_cursor_position(priv, row, col); 276 277 break; 278 } 279 case 'J': { 280 int mode; 281 282 /* 283 * Clear part/all screen: 284 * [J or [0J - clear screen from cursor down 285 * [1J - clear screen from cursor up 286 * [2J - clear entire screen 287 * 288 * TODO we really only handle entire-screen case, others 289 * probably require some additions to video-uclass (and 290 * are not really needed yet by efi_console) 291 */ 292 parsenum(priv->escape_buf + 1, &mode); 293 294 if (mode == 2) { 295 video_clear(dev->parent); 296 video_sync(dev->parent, false); 297 priv->ycur = 0; 298 priv->xcur_frac = priv->xstart_frac; 299 } else { 300 debug("unsupported clear mode: %d\n", mode); 301 } 302 break; 303 } 304 case 'm': { 305 struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent); 306 char *s = priv->escape_buf; 307 char *end = &priv->escape_buf[priv->escape_len]; 308 309 /* 310 * Set graphics mode: [%d;...;%dm 311 * 312 * Currently only supports the color attributes: 313 * 314 * Foreground Colors: 315 * 316 * 30 Black 317 * 31 Red 318 * 32 Green 319 * 33 Yellow 320 * 34 Blue 321 * 35 Magenta 322 * 36 Cyan 323 * 37 White 324 * 325 * Background Colors: 326 * 327 * 40 Black 328 * 41 Red 329 * 42 Green 330 * 43 Yellow 331 * 44 Blue 332 * 45 Magenta 333 * 46 Cyan 334 * 47 White 335 */ 336 337 s++; /* [ */ 338 while (s < end) { 339 int val; 340 341 s = parsenum(s, &val); 342 s++; 343 344 switch (val) { 345 case 0: 346 /* all attributes off */ 347 video_set_default_colors(vid_priv); 348 break; 349 case 1: 350 /* bold */ 351 vid_priv->fg_col_idx |= 8; 352 vid_priv->colour_fg = vid_console_color( 353 vid_priv, vid_priv->fg_col_idx); 354 break; 355 case 30 ... 37: 356 /* foreground color */ 357 vid_priv->fg_col_idx &= ~7; 358 vid_priv->fg_col_idx |= val - 30; 359 vid_priv->colour_fg = vid_console_color( 360 vid_priv, vid_priv->fg_col_idx); 361 break; 362 case 40 ... 47: 363 /* background color */ 364 vid_priv->colour_bg = vid_console_color( 365 vid_priv, val - 40); 366 break; 367 default: 368 /* ignore unsupported SGR parameter */ 369 break; 370 } 371 } 372 373 break; 374 } 375 default: 376 debug("unrecognized escape sequence: %*s\n", 377 priv->escape_len, priv->escape_buf); 378 } 379 380 return; 381 382 error: 383 /* something went wrong, just revert to normal mode: */ 384 priv->escape = 0; 385 } 386 387 int vidconsole_put_char(struct udevice *dev, char ch) 388 { 389 struct vidconsole_priv *priv = dev_get_uclass_priv(dev); 390 int ret; 391 392 if (priv->escape) { 393 vidconsole_escape_char(dev, ch); 394 return 0; 395 } 396 397 switch (ch) { 398 case '\x1b': 399 priv->escape_len = 0; 400 priv->escape = 1; 401 break; 402 case '\a': 403 /* beep */ 404 break; 405 case '\r': 406 priv->xcur_frac = priv->xstart_frac; 407 break; 408 case '\n': 409 vidconsole_newline(dev); 410 vidconsole_entry_start(dev); 411 break; 412 case '\t': /* Tab (8 chars alignment) */ 413 priv->xcur_frac = ((priv->xcur_frac / priv->tab_width_frac) 414 + 1) * priv->tab_width_frac; 415 416 if (priv->xcur_frac >= priv->xsize_frac) 417 vidconsole_newline(dev); 418 break; 419 case '\b': 420 vidconsole_back(dev); 421 priv->last_ch = 0; 422 break; 423 default: 424 /* 425 * Failure of this function normally indicates an unsupported 426 * colour depth. Check this and return an error to help with 427 * diagnosis. 428 */ 429 ret = vidconsole_putc_xy(dev, priv->xcur_frac, priv->ycur, ch); 430 if (ret == -EAGAIN) { 431 vidconsole_newline(dev); 432 ret = vidconsole_putc_xy(dev, priv->xcur_frac, 433 priv->ycur, ch); 434 } 435 if (ret < 0) 436 return ret; 437 priv->xcur_frac += ret; 438 priv->last_ch = ch; 439 if (priv->xcur_frac >= priv->xsize_frac) 440 vidconsole_newline(dev); 441 break; 442 } 443 444 return 0; 445 } 446 447 static void vidconsole_putc(struct stdio_dev *sdev, const char ch) 448 { 449 struct udevice *dev = sdev->priv; 450 451 vidconsole_put_char(dev, ch); 452 video_sync(dev->parent, false); 453 } 454 455 static void vidconsole_puts(struct stdio_dev *sdev, const char *s) 456 { 457 struct udevice *dev = sdev->priv; 458 459 while (*s) 460 vidconsole_put_char(dev, *s++); 461 video_sync(dev->parent, false); 462 } 463 464 /* Set up the number of rows and colours (rotated drivers override this) */ 465 static int vidconsole_pre_probe(struct udevice *dev) 466 { 467 struct vidconsole_priv *priv = dev_get_uclass_priv(dev); 468 struct udevice *vid = dev->parent; 469 struct video_priv *vid_priv = dev_get_uclass_priv(vid); 470 471 priv->xsize_frac = VID_TO_POS(vid_priv->xsize); 472 473 return 0; 474 } 475 476 /* Register the device with stdio */ 477 static int vidconsole_post_probe(struct udevice *dev) 478 { 479 struct vidconsole_priv *priv = dev_get_uclass_priv(dev); 480 struct stdio_dev *sdev = &priv->sdev; 481 482 if (!priv->tab_width_frac) 483 priv->tab_width_frac = VID_TO_POS(priv->x_charsize) * 8; 484 485 if (dev->seq) { 486 snprintf(sdev->name, sizeof(sdev->name), "vidconsole%d", 487 dev->seq); 488 } else { 489 strcpy(sdev->name, "vidconsole"); 490 } 491 492 sdev->flags = DEV_FLAGS_OUTPUT; 493 sdev->putc = vidconsole_putc; 494 sdev->puts = vidconsole_puts; 495 sdev->priv = dev; 496 497 return stdio_register(sdev); 498 } 499 500 UCLASS_DRIVER(vidconsole) = { 501 .id = UCLASS_VIDEO_CONSOLE, 502 .name = "vidconsole0", 503 .pre_probe = vidconsole_pre_probe, 504 .post_probe = vidconsole_post_probe, 505 .per_device_auto_alloc_size = sizeof(struct vidconsole_priv), 506 }; 507 508 void vidconsole_position_cursor(struct udevice *dev, unsigned col, unsigned row) 509 { 510 struct vidconsole_priv *priv = dev_get_uclass_priv(dev); 511 struct udevice *vid_dev = dev->parent; 512 struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); 513 514 col *= priv->x_charsize; 515 row *= priv->y_charsize; 516 priv->xcur_frac = VID_TO_POS(min_t(short, col, vid_priv->xsize - 1)); 517 priv->ycur = min_t(short, row, vid_priv->ysize - 1); 518 } 519 520 static int do_video_setcursor(cmd_tbl_t *cmdtp, int flag, int argc, 521 char *const argv[]) 522 { 523 unsigned int col, row; 524 struct udevice *dev; 525 526 if (argc != 3) 527 return CMD_RET_USAGE; 528 529 if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev)) 530 return CMD_RET_FAILURE; 531 col = simple_strtoul(argv[1], NULL, 10); 532 row = simple_strtoul(argv[2], NULL, 10); 533 vidconsole_position_cursor(dev, col, row); 534 535 return 0; 536 } 537 538 static int do_video_puts(cmd_tbl_t *cmdtp, int flag, int argc, 539 char *const argv[]) 540 { 541 struct udevice *dev; 542 const char *s; 543 544 if (argc != 2) 545 return CMD_RET_USAGE; 546 547 if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev)) 548 return CMD_RET_FAILURE; 549 for (s = argv[1]; *s; s++) 550 vidconsole_put_char(dev, *s); 551 552 video_sync(dev->parent, false); 553 554 return 0; 555 } 556 557 U_BOOT_CMD( 558 setcurs, 3, 1, do_video_setcursor, 559 "set cursor position within screen", 560 " <col> <row> in character" 561 ); 562 563 U_BOOT_CMD( 564 lcdputs, 2, 1, do_video_puts, 565 "print string on video framebuffer", 566 " <string>" 567 ); 568