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 #define LOG_CATEGORY UCLASS_VIDEO_CONSOLE
11
12 #include <common.h>
13 #include <command.h>
14 #include <console.h>
15 #include <log.h>
16 #include <dm.h>
17 #include <video.h>
18 #include <video_console.h>
19 #include <video_font.h> /* Bitmap font for code page 437 */
20 #include <linux/ctype.h>
21
22 /*
23 * Structure to describe a console color
24 */
25 struct vid_rgb {
26 u32 r;
27 u32 g;
28 u32 b;
29 };
30
31 /* By default we scroll by a single line */
32 #ifndef CONFIG_CONSOLE_SCROLL_LINES
33 #define CONFIG_CONSOLE_SCROLL_LINES 1
34 #endif
35
vidconsole_putc_xy(struct udevice * dev,uint x,uint y,char ch)36 int vidconsole_putc_xy(struct udevice *dev, uint x, uint y, char ch)
37 {
38 struct vidconsole_ops *ops = vidconsole_get_ops(dev);
39
40 if (!ops->putc_xy)
41 return -ENOSYS;
42 return ops->putc_xy(dev, x, y, ch);
43 }
44
vidconsole_move_rows(struct udevice * dev,uint rowdst,uint rowsrc,uint count)45 int vidconsole_move_rows(struct udevice *dev, uint rowdst, uint rowsrc,
46 uint count)
47 {
48 struct vidconsole_ops *ops = vidconsole_get_ops(dev);
49
50 if (!ops->move_rows)
51 return -ENOSYS;
52 return ops->move_rows(dev, rowdst, rowsrc, count);
53 }
54
vidconsole_set_row(struct udevice * dev,uint row,int clr)55 int vidconsole_set_row(struct udevice *dev, uint row, int clr)
56 {
57 struct vidconsole_ops *ops = vidconsole_get_ops(dev);
58
59 if (!ops->set_row)
60 return -ENOSYS;
61 return ops->set_row(dev, row, clr);
62 }
63
vidconsole_entry_start(struct udevice * dev)64 static int vidconsole_entry_start(struct udevice *dev)
65 {
66 struct vidconsole_ops *ops = vidconsole_get_ops(dev);
67
68 if (!ops->entry_start)
69 return -ENOSYS;
70 return ops->entry_start(dev);
71 }
72
73 /* Move backwards one space */
vidconsole_back(struct udevice * dev)74 static int vidconsole_back(struct udevice *dev)
75 {
76 struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
77 struct vidconsole_ops *ops = vidconsole_get_ops(dev);
78 int ret;
79
80 if (ops->backspace) {
81 ret = ops->backspace(dev);
82 if (ret != -ENOSYS)
83 return ret;
84 }
85
86 priv->xcur_frac -= VID_TO_POS(priv->x_charsize);
87 if (priv->xcur_frac < priv->xstart_frac) {
88 priv->xcur_frac = (priv->cols - 1) *
89 VID_TO_POS(priv->x_charsize);
90 priv->ycur -= priv->y_charsize;
91 if (priv->ycur < 0)
92 priv->ycur = 0;
93 }
94 return video_sync(dev->parent, false);
95 }
96
97 /* Move to a newline, scrolling the display if necessary */
vidconsole_newline(struct udevice * dev)98 static void vidconsole_newline(struct udevice *dev)
99 {
100 struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
101 struct udevice *vid_dev = dev->parent;
102 struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
103 const int rows = CONFIG_CONSOLE_SCROLL_LINES;
104 int i, ret;
105
106 priv->xcur_frac = priv->xstart_frac;
107 priv->ycur += priv->y_charsize;
108
109 /* Check if we need to scroll the terminal */
110 if ((priv->ycur + priv->y_charsize) / priv->y_charsize > priv->rows) {
111 vidconsole_move_rows(dev, 0, rows, priv->rows - rows);
112 for (i = 0; i < rows; i++)
113 vidconsole_set_row(dev, priv->rows - i - 1,
114 vid_priv->colour_bg);
115 priv->ycur -= rows * priv->y_charsize;
116 }
117 priv->last_ch = 0;
118
119 ret = video_sync(dev->parent, false);
120 if (ret) {
121 #ifdef DEBUG
122 console_puts_select_stderr(true, "[vc err: video_sync]");
123 #endif
124 }
125 }
126
127 static const struct vid_rgb colors[VID_COLOR_COUNT] = {
128 { 0x00, 0x00, 0x00 }, /* black */
129 { 0xc0, 0x00, 0x00 }, /* red */
130 { 0x00, 0xc0, 0x00 }, /* green */
131 { 0xc0, 0x60, 0x00 }, /* brown */
132 { 0x00, 0x00, 0xc0 }, /* blue */
133 { 0xc0, 0x00, 0xc0 }, /* magenta */
134 { 0x00, 0xc0, 0xc0 }, /* cyan */
135 { 0xc0, 0xc0, 0xc0 }, /* light gray */
136 { 0x80, 0x80, 0x80 }, /* gray */
137 { 0xff, 0x00, 0x00 }, /* bright red */
138 { 0x00, 0xff, 0x00 }, /* bright green */
139 { 0xff, 0xff, 0x00 }, /* yellow */
140 { 0x00, 0x00, 0xff }, /* bright blue */
141 { 0xff, 0x00, 0xff }, /* bright magenta */
142 { 0x00, 0xff, 0xff }, /* bright cyan */
143 { 0xff, 0xff, 0xff }, /* white */
144 };
145
vid_console_color(struct video_priv * priv,unsigned int idx)146 u32 vid_console_color(struct video_priv *priv, unsigned int idx)
147 {
148 switch (priv->bpix) {
149 case VIDEO_BPP16:
150 if (CONFIG_IS_ENABLED(VIDEO_BPP16)) {
151 return ((colors[idx].r >> 3) << 11) |
152 ((colors[idx].g >> 2) << 5) |
153 ((colors[idx].b >> 3) << 0);
154 }
155 break;
156 case VIDEO_BPP32:
157 if (CONFIG_IS_ENABLED(VIDEO_BPP32)) {
158 if (priv->format == VIDEO_X2R10G10B10)
159 return (colors[idx].r << 22) |
160 (colors[idx].g << 12) |
161 (colors[idx].b << 2);
162 else
163 return (colors[idx].r << 16) |
164 (colors[idx].g << 8) |
165 (colors[idx].b << 0);
166 }
167 break;
168 default:
169 break;
170 }
171
172 /*
173 * For unknown bit arrangements just support
174 * black and white.
175 */
176 if (idx)
177 return 0xffffff; /* white */
178
179 return 0x000000; /* black */
180 }
181
parsenum(char * s,int * num)182 static char *parsenum(char *s, int *num)
183 {
184 char *end;
185 *num = simple_strtol(s, &end, 10);
186 return end;
187 }
188
189 /**
190 * set_cursor_position() - set cursor position
191 *
192 * @priv: private data of the video console
193 * @row: new row
194 * @col: new column
195 */
set_cursor_position(struct vidconsole_priv * priv,int row,int col)196 static void set_cursor_position(struct vidconsole_priv *priv, int row, int col)
197 {
198 /*
199 * Ensure we stay in the bounds of the screen.
200 */
201 if (row >= priv->rows)
202 row = priv->rows - 1;
203 if (col >= priv->cols)
204 col = priv->cols - 1;
205
206 priv->ycur = row * priv->y_charsize;
207 priv->xcur_frac = priv->xstart_frac +
208 VID_TO_POS(col * priv->x_charsize);
209 }
210
211 /**
212 * get_cursor_position() - get cursor position
213 *
214 * @priv: private data of the video console
215 * @row: row
216 * @col: column
217 */
get_cursor_position(struct vidconsole_priv * priv,int * row,int * col)218 static void get_cursor_position(struct vidconsole_priv *priv,
219 int *row, int *col)
220 {
221 *row = priv->ycur / priv->y_charsize;
222 *col = VID_TO_PIXEL(priv->xcur_frac - priv->xstart_frac) /
223 priv->x_charsize;
224 }
225
226 /*
227 * Process a character while accumulating an escape string. Chars are
228 * accumulated into escape_buf until the end of escape sequence is
229 * found, at which point the sequence is parsed and processed.
230 */
vidconsole_escape_char(struct udevice * dev,char ch)231 static void vidconsole_escape_char(struct udevice *dev, char ch)
232 {
233 struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
234
235 if (!IS_ENABLED(CONFIG_VIDEO_ANSI))
236 goto error;
237
238 /* Sanity checking for bogus ESC sequences: */
239 if (priv->escape_len >= sizeof(priv->escape_buf))
240 goto error;
241 if (priv->escape_len == 0) {
242 switch (ch) {
243 case '7':
244 /* Save cursor position */
245 get_cursor_position(priv, &priv->row_saved,
246 &priv->col_saved);
247 priv->escape = 0;
248
249 return;
250 case '8': {
251 /* Restore cursor position */
252 int row = priv->row_saved;
253 int col = priv->col_saved;
254
255 set_cursor_position(priv, row, col);
256 priv->escape = 0;
257 return;
258 }
259 case '[':
260 break;
261 default:
262 goto error;
263 }
264 }
265
266 priv->escape_buf[priv->escape_len++] = ch;
267
268 /*
269 * Escape sequences are terminated by a letter, so keep
270 * accumulating until we get one:
271 */
272 if (!isalpha(ch))
273 return;
274
275 /*
276 * clear escape mode first, otherwise things will get highly
277 * surprising if you hit any debug prints that come back to
278 * this console.
279 */
280 priv->escape = 0;
281
282 switch (ch) {
283 case 'A':
284 case 'B':
285 case 'C':
286 case 'D':
287 case 'E':
288 case 'F': {
289 int row, col, num;
290 char *s = priv->escape_buf;
291
292 /*
293 * Cursor up/down: [%dA, [%dB, [%dE, [%dF
294 * Cursor left/right: [%dD, [%dC
295 */
296 s++; /* [ */
297 s = parsenum(s, &num);
298 if (num == 0) /* No digit in sequence ... */
299 num = 1; /* ... means "move by 1". */
300
301 get_cursor_position(priv, &row, &col);
302 if (ch == 'A' || ch == 'F')
303 row -= num;
304 if (ch == 'C')
305 col += num;
306 if (ch == 'D')
307 col -= num;
308 if (ch == 'B' || ch == 'E')
309 row += num;
310 if (ch == 'E' || ch == 'F')
311 col = 0;
312 if (col < 0)
313 col = 0;
314 if (row < 0)
315 row = 0;
316 /* Right and bottom overflows are handled in the callee. */
317 set_cursor_position(priv, row, col);
318 break;
319 }
320 case 'H':
321 case 'f': {
322 int row, col;
323 char *s = priv->escape_buf;
324
325 /*
326 * Set cursor position: [%d;%df or [%d;%dH
327 */
328 s++; /* [ */
329 s = parsenum(s, &row);
330 s++; /* ; */
331 s = parsenum(s, &col);
332
333 /*
334 * Video origin is [0, 0], terminal origin is [1, 1].
335 */
336 if (row)
337 --row;
338 if (col)
339 --col;
340
341 set_cursor_position(priv, row, col);
342
343 break;
344 }
345 case 'J': {
346 int mode;
347
348 /*
349 * Clear part/all screen:
350 * [J or [0J - clear screen from cursor down
351 * [1J - clear screen from cursor up
352 * [2J - clear entire screen
353 *
354 * TODO we really only handle entire-screen case, others
355 * probably require some additions to video-uclass (and
356 * are not really needed yet by efi_console)
357 */
358 parsenum(priv->escape_buf + 1, &mode);
359
360 if (mode == 2) {
361 int ret;
362
363 video_clear(dev->parent);
364 ret = video_sync(dev->parent, false);
365 if (ret) {
366 #ifdef DEBUG
367 console_puts_select_stderr(true, "[vc err: video_sync]");
368 #endif
369 }
370 priv->ycur = 0;
371 priv->xcur_frac = priv->xstart_frac;
372 } else {
373 debug("unsupported clear mode: %d\n", mode);
374 }
375 break;
376 }
377 case 'K': {
378 struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
379 int mode;
380
381 /*
382 * Clear (parts of) current line
383 * [0K - clear line to end
384 * [2K - clear entire line
385 */
386 parsenum(priv->escape_buf + 1, &mode);
387
388 if (mode == 2) {
389 int row, col;
390
391 get_cursor_position(priv, &row, &col);
392 vidconsole_set_row(dev, row, vid_priv->colour_bg);
393 }
394 break;
395 }
396 case 'm': {
397 struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
398 char *s = priv->escape_buf;
399 char *end = &priv->escape_buf[priv->escape_len];
400
401 /*
402 * Set graphics mode: [%d;...;%dm
403 *
404 * Currently only supports the color attributes:
405 *
406 * Foreground Colors:
407 *
408 * 30 Black
409 * 31 Red
410 * 32 Green
411 * 33 Yellow
412 * 34 Blue
413 * 35 Magenta
414 * 36 Cyan
415 * 37 White
416 *
417 * Background Colors:
418 *
419 * 40 Black
420 * 41 Red
421 * 42 Green
422 * 43 Yellow
423 * 44 Blue
424 * 45 Magenta
425 * 46 Cyan
426 * 47 White
427 */
428
429 s++; /* [ */
430 while (s < end) {
431 int val;
432
433 s = parsenum(s, &val);
434 s++;
435
436 switch (val) {
437 case 0:
438 /* all attributes off */
439 video_set_default_colors(dev->parent, false);
440 break;
441 case 1:
442 /* bold */
443 vid_priv->fg_col_idx |= 8;
444 vid_priv->colour_fg = vid_console_color(
445 vid_priv, vid_priv->fg_col_idx);
446 break;
447 case 7:
448 /* reverse video */
449 vid_priv->colour_fg = vid_console_color(
450 vid_priv, vid_priv->bg_col_idx);
451 vid_priv->colour_bg = vid_console_color(
452 vid_priv, vid_priv->fg_col_idx);
453 break;
454 case 30 ... 37:
455 /* foreground color */
456 vid_priv->fg_col_idx &= ~7;
457 vid_priv->fg_col_idx |= val - 30;
458 vid_priv->colour_fg = vid_console_color(
459 vid_priv, vid_priv->fg_col_idx);
460 break;
461 case 40 ... 47:
462 /* background color, also mask the bold bit */
463 vid_priv->bg_col_idx &= ~0xf;
464 vid_priv->bg_col_idx |= val - 40;
465 vid_priv->colour_bg = vid_console_color(
466 vid_priv, vid_priv->bg_col_idx);
467 break;
468 default:
469 /* ignore unsupported SGR parameter */
470 break;
471 }
472 }
473
474 break;
475 }
476 default:
477 debug("unrecognized escape sequence: %*s\n",
478 priv->escape_len, priv->escape_buf);
479 }
480
481 return;
482
483 error:
484 /* something went wrong, just revert to normal mode: */
485 priv->escape = 0;
486 }
487
488 /* Put that actual character on the screen (using the CP437 code page). */
vidconsole_output_glyph(struct udevice * dev,char ch)489 static int vidconsole_output_glyph(struct udevice *dev, char ch)
490 {
491 struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
492 int ret;
493
494 /*
495 * Failure of this function normally indicates an unsupported
496 * colour depth. Check this and return an error to help with
497 * diagnosis.
498 */
499 ret = vidconsole_putc_xy(dev, priv->xcur_frac, priv->ycur, ch);
500 if (ret == -EAGAIN) {
501 vidconsole_newline(dev);
502 ret = vidconsole_putc_xy(dev, priv->xcur_frac, priv->ycur, ch);
503 }
504 if (ret < 0)
505 return ret;
506 priv->xcur_frac += ret;
507 priv->last_ch = ch;
508 if (priv->xcur_frac >= priv->xsize_frac)
509 vidconsole_newline(dev);
510
511 return 0;
512 }
513
vidconsole_put_char(struct udevice * dev,char ch)514 int vidconsole_put_char(struct udevice *dev, char ch)
515 {
516 struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
517 int ret;
518
519 if (priv->escape) {
520 vidconsole_escape_char(dev, ch);
521 return 0;
522 }
523
524 switch (ch) {
525 case '\x1b':
526 priv->escape_len = 0;
527 priv->escape = 1;
528 break;
529 case '\a':
530 /* beep */
531 break;
532 case '\r':
533 priv->xcur_frac = priv->xstart_frac;
534 break;
535 case '\n':
536 vidconsole_newline(dev);
537 vidconsole_entry_start(dev);
538 break;
539 case '\t': /* Tab (8 chars alignment) */
540 priv->xcur_frac = ((priv->xcur_frac / priv->tab_width_frac)
541 + 1) * priv->tab_width_frac;
542
543 if (priv->xcur_frac >= priv->xsize_frac)
544 vidconsole_newline(dev);
545 break;
546 case '\b':
547 vidconsole_back(dev);
548 priv->last_ch = 0;
549 break;
550 default:
551 ret = vidconsole_output_glyph(dev, ch);
552 if (ret < 0)
553 return ret;
554 break;
555 }
556
557 return 0;
558 }
559
vidconsole_put_string(struct udevice * dev,const char * str)560 int vidconsole_put_string(struct udevice *dev, const char *str)
561 {
562 const char *s;
563 int ret;
564
565 for (s = str; *s; s++) {
566 ret = vidconsole_put_char(dev, *s);
567 if (ret)
568 return ret;
569 }
570
571 return 0;
572 }
573
vidconsole_putc(struct stdio_dev * sdev,const char ch)574 static void vidconsole_putc(struct stdio_dev *sdev, const char ch)
575 {
576 struct udevice *dev = sdev->priv;
577 int ret;
578
579 ret = vidconsole_put_char(dev, ch);
580 if (ret) {
581 #ifdef DEBUG
582 console_puts_select_stderr(true, "[vc err: putc]");
583 #endif
584 }
585 ret = video_sync(dev->parent, false);
586 if (ret) {
587 #ifdef DEBUG
588 console_puts_select_stderr(true, "[vc err: video_sync]");
589 #endif
590 }
591 }
592
vidconsole_puts(struct stdio_dev * sdev,const char * s)593 static void vidconsole_puts(struct stdio_dev *sdev, const char *s)
594 {
595 struct udevice *dev = sdev->priv;
596 int ret;
597
598 ret = vidconsole_put_string(dev, s);
599 if (ret) {
600 #ifdef DEBUG
601 char str[30];
602
603 snprintf(str, sizeof(str), "[vc err: puts %d]", ret);
604 console_puts_select_stderr(true, str);
605 #endif
606 }
607 ret = video_sync(dev->parent, false);
608 if (ret) {
609 #ifdef DEBUG
610 console_puts_select_stderr(true, "[vc err: video_sync]");
611 #endif
612 }
613 }
614
615 /* Set up the number of rows and colours (rotated drivers override this) */
vidconsole_pre_probe(struct udevice * dev)616 static int vidconsole_pre_probe(struct udevice *dev)
617 {
618 struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
619 struct udevice *vid = dev->parent;
620 struct video_priv *vid_priv = dev_get_uclass_priv(vid);
621
622 priv->xsize_frac = VID_TO_POS(vid_priv->xsize);
623
624 return 0;
625 }
626
627 /* Register the device with stdio */
vidconsole_post_probe(struct udevice * dev)628 static int vidconsole_post_probe(struct udevice *dev)
629 {
630 struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
631 struct stdio_dev *sdev = &priv->sdev;
632
633 if (!priv->tab_width_frac)
634 priv->tab_width_frac = VID_TO_POS(priv->x_charsize) * 8;
635
636 if (dev_seq(dev)) {
637 snprintf(sdev->name, sizeof(sdev->name), "vidconsole%d",
638 dev_seq(dev));
639 } else {
640 strcpy(sdev->name, "vidconsole");
641 }
642
643 sdev->flags = DEV_FLAGS_OUTPUT;
644 sdev->putc = vidconsole_putc;
645 sdev->puts = vidconsole_puts;
646 sdev->priv = dev;
647
648 return stdio_register(sdev);
649 }
650
651 UCLASS_DRIVER(vidconsole) = {
652 .id = UCLASS_VIDEO_CONSOLE,
653 .name = "vidconsole0",
654 .pre_probe = vidconsole_pre_probe,
655 .post_probe = vidconsole_post_probe,
656 .per_device_auto = sizeof(struct vidconsole_priv),
657 };
658
659 #ifdef CONFIG_VIDEO_COPY
vidconsole_sync_copy(struct udevice * dev,void * from,void * to)660 int vidconsole_sync_copy(struct udevice *dev, void *from, void *to)
661 {
662 struct udevice *vid = dev_get_parent(dev);
663
664 return video_sync_copy(vid, from, to);
665 }
666
vidconsole_memmove(struct udevice * dev,void * dst,const void * src,int size)667 int vidconsole_memmove(struct udevice *dev, void *dst, const void *src,
668 int size)
669 {
670 memmove(dst, src, size);
671 return vidconsole_sync_copy(dev, dst, dst + size);
672 }
673 #endif
674
675 #if CONFIG_IS_ENABLED(CMD_VIDCONSOLE)
vidconsole_position_cursor(struct udevice * dev,unsigned col,unsigned row)676 void vidconsole_position_cursor(struct udevice *dev, unsigned col, unsigned row)
677 {
678 struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
679 struct udevice *vid_dev = dev->parent;
680 struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
681
682 col *= priv->x_charsize;
683 row *= priv->y_charsize;
684 priv->xcur_frac = VID_TO_POS(min_t(short, col, vid_priv->xsize - 1));
685 priv->xstart_frac = priv->xcur_frac;
686 priv->ycur = min_t(short, row, vid_priv->ysize - 1);
687 }
688
do_video_setcursor(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])689 static int do_video_setcursor(struct cmd_tbl *cmdtp, int flag, int argc,
690 char *const argv[])
691 {
692 unsigned int col, row;
693 struct udevice *dev;
694
695 if (argc != 3)
696 return CMD_RET_USAGE;
697
698 if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev))
699 return CMD_RET_FAILURE;
700 col = dectoul(argv[1], NULL);
701 row = dectoul(argv[2], NULL);
702 vidconsole_position_cursor(dev, col, row);
703
704 return 0;
705 }
706
do_video_puts(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])707 static int do_video_puts(struct cmd_tbl *cmdtp, int flag, int argc,
708 char *const argv[])
709 {
710 struct udevice *dev;
711 const char *s;
712
713 if (argc != 2)
714 return CMD_RET_USAGE;
715
716 if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev))
717 return CMD_RET_FAILURE;
718 for (s = argv[1]; *s; s++)
719 vidconsole_put_char(dev, *s);
720
721 return video_sync(dev->parent, false);
722 }
723
724 U_BOOT_CMD(
725 setcurs, 3, 1, do_video_setcursor,
726 "set cursor position within screen",
727 " <col> <row> in character"
728 );
729
730 U_BOOT_CMD(
731 lcdputs, 2, 1, do_video_puts,
732 "print string on video framebuffer",
733 " <string>"
734 );
735 #endif /* CONFIG_IS_ENABLED(CMD_VIDCONSOLE) */
736