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 /* 276 * Video origin is [0, 0], terminal origin is [1, 1]. 277 */ 278 if (row) 279 --row; 280 if (col) 281 --col; 282 283 set_cursor_position(priv, row, col); 284 285 break; 286 } 287 case 'J': { 288 int mode; 289 290 /* 291 * Clear part/all screen: 292 * [J or [0J - clear screen from cursor down 293 * [1J - clear screen from cursor up 294 * [2J - clear entire screen 295 * 296 * TODO we really only handle entire-screen case, others 297 * probably require some additions to video-uclass (and 298 * are not really needed yet by efi_console) 299 */ 300 parsenum(priv->escape_buf + 1, &mode); 301 302 if (mode == 2) { 303 video_clear(dev->parent); 304 video_sync(dev->parent, false); 305 priv->ycur = 0; 306 priv->xcur_frac = priv->xstart_frac; 307 } else { 308 debug("unsupported clear mode: %d\n", mode); 309 } 310 break; 311 } 312 case 'm': { 313 struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent); 314 char *s = priv->escape_buf; 315 char *end = &priv->escape_buf[priv->escape_len]; 316 317 /* 318 * Set graphics mode: [%d;...;%dm 319 * 320 * Currently only supports the color attributes: 321 * 322 * Foreground Colors: 323 * 324 * 30 Black 325 * 31 Red 326 * 32 Green 327 * 33 Yellow 328 * 34 Blue 329 * 35 Magenta 330 * 36 Cyan 331 * 37 White 332 * 333 * Background Colors: 334 * 335 * 40 Black 336 * 41 Red 337 * 42 Green 338 * 43 Yellow 339 * 44 Blue 340 * 45 Magenta 341 * 46 Cyan 342 * 47 White 343 */ 344 345 s++; /* [ */ 346 while (s < end) { 347 int val; 348 349 s = parsenum(s, &val); 350 s++; 351 352 switch (val) { 353 case 0: 354 /* all attributes off */ 355 video_set_default_colors(dev->parent, false); 356 break; 357 case 1: 358 /* bold */ 359 vid_priv->fg_col_idx |= 8; 360 vid_priv->colour_fg = vid_console_color( 361 vid_priv, vid_priv->fg_col_idx); 362 break; 363 case 30 ... 37: 364 /* foreground color */ 365 vid_priv->fg_col_idx &= ~7; 366 vid_priv->fg_col_idx |= val - 30; 367 vid_priv->colour_fg = vid_console_color( 368 vid_priv, vid_priv->fg_col_idx); 369 break; 370 case 40 ... 47: 371 /* background color */ 372 vid_priv->colour_bg = vid_console_color( 373 vid_priv, val - 40); 374 break; 375 default: 376 /* ignore unsupported SGR parameter */ 377 break; 378 } 379 } 380 381 break; 382 } 383 default: 384 debug("unrecognized escape sequence: %*s\n", 385 priv->escape_len, priv->escape_buf); 386 } 387 388 return; 389 390 error: 391 /* something went wrong, just revert to normal mode: */ 392 priv->escape = 0; 393 } 394 395 int vidconsole_put_char(struct udevice *dev, char ch) 396 { 397 struct vidconsole_priv *priv = dev_get_uclass_priv(dev); 398 int ret; 399 400 if (priv->escape) { 401 vidconsole_escape_char(dev, ch); 402 return 0; 403 } 404 405 switch (ch) { 406 case '\x1b': 407 priv->escape_len = 0; 408 priv->escape = 1; 409 break; 410 case '\a': 411 /* beep */ 412 break; 413 case '\r': 414 priv->xcur_frac = priv->xstart_frac; 415 break; 416 case '\n': 417 vidconsole_newline(dev); 418 vidconsole_entry_start(dev); 419 break; 420 case '\t': /* Tab (8 chars alignment) */ 421 priv->xcur_frac = ((priv->xcur_frac / priv->tab_width_frac) 422 + 1) * priv->tab_width_frac; 423 424 if (priv->xcur_frac >= priv->xsize_frac) 425 vidconsole_newline(dev); 426 break; 427 case '\b': 428 vidconsole_back(dev); 429 priv->last_ch = 0; 430 break; 431 default: 432 /* 433 * Failure of this function normally indicates an unsupported 434 * colour depth. Check this and return an error to help with 435 * diagnosis. 436 */ 437 ret = vidconsole_putc_xy(dev, priv->xcur_frac, priv->ycur, ch); 438 if (ret == -EAGAIN) { 439 vidconsole_newline(dev); 440 ret = vidconsole_putc_xy(dev, priv->xcur_frac, 441 priv->ycur, ch); 442 } 443 if (ret < 0) 444 return ret; 445 priv->xcur_frac += ret; 446 priv->last_ch = ch; 447 if (priv->xcur_frac >= priv->xsize_frac) 448 vidconsole_newline(dev); 449 break; 450 } 451 452 return 0; 453 } 454 455 static void vidconsole_putc(struct stdio_dev *sdev, const char ch) 456 { 457 struct udevice *dev = sdev->priv; 458 459 vidconsole_put_char(dev, ch); 460 video_sync(dev->parent, false); 461 } 462 463 static void vidconsole_puts(struct stdio_dev *sdev, const char *s) 464 { 465 struct udevice *dev = sdev->priv; 466 467 while (*s) 468 vidconsole_put_char(dev, *s++); 469 video_sync(dev->parent, false); 470 } 471 472 /* Set up the number of rows and colours (rotated drivers override this) */ 473 static int vidconsole_pre_probe(struct udevice *dev) 474 { 475 struct vidconsole_priv *priv = dev_get_uclass_priv(dev); 476 struct udevice *vid = dev->parent; 477 struct video_priv *vid_priv = dev_get_uclass_priv(vid); 478 479 priv->xsize_frac = VID_TO_POS(vid_priv->xsize); 480 481 return 0; 482 } 483 484 /* Register the device with stdio */ 485 static int vidconsole_post_probe(struct udevice *dev) 486 { 487 struct vidconsole_priv *priv = dev_get_uclass_priv(dev); 488 struct stdio_dev *sdev = &priv->sdev; 489 490 if (!priv->tab_width_frac) 491 priv->tab_width_frac = VID_TO_POS(priv->x_charsize) * 8; 492 493 if (dev->seq) { 494 snprintf(sdev->name, sizeof(sdev->name), "vidconsole%d", 495 dev->seq); 496 } else { 497 strcpy(sdev->name, "vidconsole"); 498 } 499 500 sdev->flags = DEV_FLAGS_OUTPUT; 501 sdev->putc = vidconsole_putc; 502 sdev->puts = vidconsole_puts; 503 sdev->priv = dev; 504 505 return stdio_register(sdev); 506 } 507 508 UCLASS_DRIVER(vidconsole) = { 509 .id = UCLASS_VIDEO_CONSOLE, 510 .name = "vidconsole0", 511 .pre_probe = vidconsole_pre_probe, 512 .post_probe = vidconsole_post_probe, 513 .per_device_auto_alloc_size = sizeof(struct vidconsole_priv), 514 }; 515 516 void vidconsole_position_cursor(struct udevice *dev, unsigned col, unsigned row) 517 { 518 struct vidconsole_priv *priv = dev_get_uclass_priv(dev); 519 struct udevice *vid_dev = dev->parent; 520 struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); 521 522 col *= priv->x_charsize; 523 row *= priv->y_charsize; 524 priv->xcur_frac = VID_TO_POS(min_t(short, col, vid_priv->xsize - 1)); 525 priv->ycur = min_t(short, row, vid_priv->ysize - 1); 526 } 527 528 static int do_video_setcursor(cmd_tbl_t *cmdtp, int flag, int argc, 529 char *const argv[]) 530 { 531 unsigned int col, row; 532 struct udevice *dev; 533 534 if (argc != 3) 535 return CMD_RET_USAGE; 536 537 if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev)) 538 return CMD_RET_FAILURE; 539 col = simple_strtoul(argv[1], NULL, 10); 540 row = simple_strtoul(argv[2], NULL, 10); 541 vidconsole_position_cursor(dev, col, row); 542 543 return 0; 544 } 545 546 static int do_video_puts(cmd_tbl_t *cmdtp, int flag, int argc, 547 char *const argv[]) 548 { 549 struct udevice *dev; 550 const char *s; 551 552 if (argc != 2) 553 return CMD_RET_USAGE; 554 555 if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev)) 556 return CMD_RET_FAILURE; 557 for (s = argv[1]; *s; s++) 558 vidconsole_put_char(dev, *s); 559 560 video_sync(dev->parent, false); 561 562 return 0; 563 } 564 565 U_BOOT_CMD( 566 setcurs, 3, 1, do_video_setcursor, 567 "set cursor position within screen", 568 " <col> <row> in character" 569 ); 570 571 U_BOOT_CMD( 572 lcdputs, 2, 1, do_video_puts, 573 "print string on video framebuffer", 574 " <string>" 575 ); 576