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