Home | History | Annotate | Download | only in video
      1 // SPDX-License-Identifier: GPL-2.0+
      2 /*
      3  * Copyright (c) 2015 Google, Inc
      4  * (C) Copyright 2001-2015
      5  * DENX Software Engineering -- wd (at) 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);
     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);
    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  * Process a character while accumulating an escape string.  Chars are
    170  * accumulated into escape_buf until the end of escape sequence is
    171  * found, at which point the sequence is parsed and processed.
    172  */
    173 static void vidconsole_escape_char(struct udevice *dev, char ch)
    174 {
    175 	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
    176 
    177 	if (!IS_ENABLED(CONFIG_VIDEO_ANSI))
    178 		goto error;
    179 
    180 	/* Sanity checking for bogus ESC sequences: */
    181 	if (priv->escape_len >= sizeof(priv->escape_buf))
    182 		goto error;
    183 	if (priv->escape_len == 0 && ch != '[')
    184 		goto error;
    185 
    186 	priv->escape_buf[priv->escape_len++] = ch;
    187 
    188 	/*
    189 	 * Escape sequences are terminated by a letter, so keep
    190 	 * accumulating until we get one:
    191 	 */
    192 	if (!isalpha(ch))
    193 		return;
    194 
    195 	/*
    196 	 * clear escape mode first, otherwise things will get highly
    197 	 * surprising if you hit any debug prints that come back to
    198 	 * this console.
    199 	 */
    200 	priv->escape = 0;
    201 
    202 	switch (ch) {
    203 	case 'H':
    204 	case 'f': {
    205 		int row, col;
    206 		char *s = priv->escape_buf;
    207 
    208 		/*
    209 		 * Set cursor position: [%d;%df or [%d;%dH
    210 		 */
    211 		s++;    /* [ */
    212 		s = parsenum(s, &row);
    213 		s++;    /* ; */
    214 		s = parsenum(s, &col);
    215 
    216 		priv->ycur = row * priv->y_charsize;
    217 		priv->xcur_frac = priv->xstart_frac +
    218 			VID_TO_POS(col * priv->x_charsize);
    219 
    220 		break;
    221 	}
    222 	case 'J': {
    223 		int mode;
    224 
    225 		/*
    226 		 * Clear part/all screen:
    227 		 *   [J or [0J - clear screen from cursor down
    228 		 *   [1J       - clear screen from cursor up
    229 		 *   [2J       - clear entire screen
    230 		 *
    231 		 * TODO we really only handle entire-screen case, others
    232 		 * probably require some additions to video-uclass (and
    233 		 * are not really needed yet by efi_console)
    234 		 */
    235 		parsenum(priv->escape_buf + 1, &mode);
    236 
    237 		if (mode == 2) {
    238 			video_clear(dev->parent);
    239 			video_sync(dev->parent);
    240 			priv->ycur = 0;
    241 			priv->xcur_frac = priv->xstart_frac;
    242 		} else {
    243 			debug("unsupported clear mode: %d\n", mode);
    244 		}
    245 		break;
    246 	}
    247 	case 'm': {
    248 		struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
    249 		char *s = priv->escape_buf;
    250 		char *end = &priv->escape_buf[priv->escape_len];
    251 
    252 		/*
    253 		 * Set graphics mode: [%d;...;%dm
    254 		 *
    255 		 * Currently only supports the color attributes:
    256 		 *
    257 		 * Foreground Colors:
    258 		 *
    259 		 *   30	Black
    260 		 *   31	Red
    261 		 *   32	Green
    262 		 *   33	Yellow
    263 		 *   34	Blue
    264 		 *   35	Magenta
    265 		 *   36	Cyan
    266 		 *   37	White
    267 		 *
    268 		 * Background Colors:
    269 		 *
    270 		 *   40	Black
    271 		 *   41	Red
    272 		 *   42	Green
    273 		 *   43	Yellow
    274 		 *   44	Blue
    275 		 *   45	Magenta
    276 		 *   46	Cyan
    277 		 *   47	White
    278 		 */
    279 
    280 		s++;    /* [ */
    281 		while (s < end) {
    282 			int val;
    283 
    284 			s = parsenum(s, &val);
    285 			s++;
    286 
    287 			switch (val) {
    288 			case 0:
    289 				/* all attributes off */
    290 				video_set_default_colors(vid_priv);
    291 				break;
    292 			case 1:
    293 				/* bold */
    294 				vid_priv->fg_col_idx |= 8;
    295 				vid_priv->colour_fg = vid_console_color(
    296 						vid_priv, vid_priv->fg_col_idx);
    297 				break;
    298 			case 30 ... 37:
    299 				/* foreground color */
    300 				vid_priv->fg_col_idx &= ~7;
    301 				vid_priv->fg_col_idx |= val - 30;
    302 				vid_priv->colour_fg = vid_console_color(
    303 						vid_priv, vid_priv->fg_col_idx);
    304 				break;
    305 			case 40 ... 47:
    306 				/* background color */
    307 				vid_priv->colour_bg = vid_console_color(
    308 							vid_priv, val - 40);
    309 				break;
    310 			default:
    311 				/* ignore unsupported SGR parameter */
    312 				break;
    313 			}
    314 		}
    315 
    316 		break;
    317 	}
    318 	default:
    319 		debug("unrecognized escape sequence: %*s\n",
    320 		      priv->escape_len, priv->escape_buf);
    321 	}
    322 
    323 	return;
    324 
    325 error:
    326 	/* something went wrong, just revert to normal mode: */
    327 	priv->escape = 0;
    328 }
    329 
    330 int vidconsole_put_char(struct udevice *dev, char ch)
    331 {
    332 	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
    333 	int ret;
    334 
    335 	if (priv->escape) {
    336 		vidconsole_escape_char(dev, ch);
    337 		return 0;
    338 	}
    339 
    340 	switch (ch) {
    341 	case '\x1b':
    342 		priv->escape_len = 0;
    343 		priv->escape = 1;
    344 		break;
    345 	case '\a':
    346 		/* beep */
    347 		break;
    348 	case '\r':
    349 		priv->xcur_frac = priv->xstart_frac;
    350 		break;
    351 	case '\n':
    352 		vidconsole_newline(dev);
    353 		vidconsole_entry_start(dev);
    354 		break;
    355 	case '\t':	/* Tab (8 chars alignment) */
    356 		priv->xcur_frac = ((priv->xcur_frac / priv->tab_width_frac)
    357 				+ 1) * priv->tab_width_frac;
    358 
    359 		if (priv->xcur_frac >= priv->xsize_frac)
    360 			vidconsole_newline(dev);
    361 		break;
    362 	case '\b':
    363 		vidconsole_back(dev);
    364 		priv->last_ch = 0;
    365 		break;
    366 	default:
    367 		/*
    368 		 * Failure of this function normally indicates an unsupported
    369 		 * colour depth. Check this and return an error to help with
    370 		 * diagnosis.
    371 		 */
    372 		ret = vidconsole_putc_xy(dev, priv->xcur_frac, priv->ycur, ch);
    373 		if (ret == -EAGAIN) {
    374 			vidconsole_newline(dev);
    375 			ret = vidconsole_putc_xy(dev, priv->xcur_frac,
    376 						 priv->ycur, ch);
    377 		}
    378 		if (ret < 0)
    379 			return ret;
    380 		priv->xcur_frac += ret;
    381 		priv->last_ch = ch;
    382 		if (priv->xcur_frac >= priv->xsize_frac)
    383 			vidconsole_newline(dev);
    384 		break;
    385 	}
    386 
    387 	return 0;
    388 }
    389 
    390 static void vidconsole_putc(struct stdio_dev *sdev, const char ch)
    391 {
    392 	struct udevice *dev = sdev->priv;
    393 
    394 	vidconsole_put_char(dev, ch);
    395 	video_sync(dev->parent);
    396 }
    397 
    398 static void vidconsole_puts(struct stdio_dev *sdev, const char *s)
    399 {
    400 	struct udevice *dev = sdev->priv;
    401 
    402 	while (*s)
    403 		vidconsole_put_char(dev, *s++);
    404 	video_sync(dev->parent);
    405 }
    406 
    407 /* Set up the number of rows and colours (rotated drivers override this) */
    408 static int vidconsole_pre_probe(struct udevice *dev)
    409 {
    410 	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
    411 	struct udevice *vid = dev->parent;
    412 	struct video_priv *vid_priv = dev_get_uclass_priv(vid);
    413 
    414 	priv->xsize_frac = VID_TO_POS(vid_priv->xsize);
    415 
    416 	return 0;
    417 }
    418 
    419 /* Register the device with stdio */
    420 static int vidconsole_post_probe(struct udevice *dev)
    421 {
    422 	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
    423 	struct stdio_dev *sdev = &priv->sdev;
    424 
    425 	if (!priv->tab_width_frac)
    426 		priv->tab_width_frac = VID_TO_POS(priv->x_charsize) * 8;
    427 
    428 	if (dev->seq) {
    429 		snprintf(sdev->name, sizeof(sdev->name), "vidconsole%d",
    430 			 dev->seq);
    431 	} else {
    432 		strcpy(sdev->name, "vidconsole");
    433 	}
    434 
    435 	sdev->flags = DEV_FLAGS_OUTPUT;
    436 	sdev->putc = vidconsole_putc;
    437 	sdev->puts = vidconsole_puts;
    438 	sdev->priv = dev;
    439 
    440 	return stdio_register(sdev);
    441 }
    442 
    443 UCLASS_DRIVER(vidconsole) = {
    444 	.id		= UCLASS_VIDEO_CONSOLE,
    445 	.name		= "vidconsole0",
    446 	.pre_probe	= vidconsole_pre_probe,
    447 	.post_probe	= vidconsole_post_probe,
    448 	.per_device_auto_alloc_size	= sizeof(struct vidconsole_priv),
    449 };
    450 
    451 void vidconsole_position_cursor(struct udevice *dev, unsigned col, unsigned row)
    452 {
    453 	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
    454 	struct udevice *vid_dev = dev->parent;
    455 	struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
    456 
    457 	priv->xcur_frac = VID_TO_POS(min_t(short, col, vid_priv->xsize - 1));
    458 	priv->ycur = min_t(short, row, vid_priv->ysize - 1);
    459 }
    460 
    461 static int do_video_setcursor(cmd_tbl_t *cmdtp, int flag, int argc,
    462 			      char *const argv[])
    463 {
    464 	unsigned int col, row;
    465 	struct udevice *dev;
    466 
    467 	if (argc != 3)
    468 		return CMD_RET_USAGE;
    469 
    470 	if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev))
    471 		return CMD_RET_FAILURE;
    472 	col = simple_strtoul(argv[1], NULL, 10);
    473 	row = simple_strtoul(argv[2], NULL, 10);
    474 	vidconsole_position_cursor(dev, col, row);
    475 
    476 	return 0;
    477 }
    478 
    479 static int do_video_puts(cmd_tbl_t *cmdtp, int flag, int argc,
    480 			 char *const argv[])
    481 {
    482 	struct udevice *dev;
    483 	const char *s;
    484 
    485 	if (argc != 2)
    486 		return CMD_RET_USAGE;
    487 
    488 	if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev))
    489 		return CMD_RET_FAILURE;
    490 	for (s = argv[1]; *s; s++)
    491 		vidconsole_put_char(dev, *s);
    492 
    493 	video_sync(dev->parent);
    494 
    495 	return 0;
    496 }
    497 
    498 U_BOOT_CMD(
    499 	setcurs, 3,	1,	do_video_setcursor,
    500 	"set cursor position within screen",
    501 	"    <col> <row> in character"
    502 );
    503 
    504 U_BOOT_CMD(
    505 	lcdputs, 2,	1,	do_video_puts,
    506 	"print string on video framebuffer",
    507 	"    <string>"
    508 );
    509