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