]>
Commit | Line | Data |
---|---|---|
83510766 SG |
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> | |
a085aa1f | 12 | #include <linux/ctype.h> |
83510766 SG |
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 | ||
58c733a7 SG |
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 | ||
83510766 | 60 | /* Move backwards one space */ |
7b9f7e44 | 61 | static int vidconsole_back(struct udevice *dev) |
83510766 SG |
62 | { |
63 | struct vidconsole_priv *priv = dev_get_uclass_priv(dev); | |
7b9f7e44 SG |
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 | } | |
83510766 | 72 | |
f2661786 | 73 | priv->xcur_frac -= VID_TO_POS(priv->x_charsize); |
c5b77d01 | 74 | if (priv->xcur_frac < priv->xstart_frac) { |
f2661786 SG |
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; | |
83510766 | 80 | } |
fb0b709e | 81 | video_sync(dev->parent); |
7b9f7e44 SG |
82 | |
83 | return 0; | |
83510766 SG |
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 | ||
c5b77d01 | 95 | priv->xcur_frac = priv->xstart_frac; |
f2661786 | 96 | priv->ycur += priv->y_charsize; |
83510766 SG |
97 | |
98 | /* Check if we need to scroll the terminal */ | |
f2661786 | 99 | if ((priv->ycur + priv->y_charsize) / priv->y_charsize > priv->rows) { |
83510766 SG |
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); | |
f2661786 | 104 | priv->ycur -= rows * priv->y_charsize; |
83510766 | 105 | } |
58c733a7 SG |
106 | priv->last_ch = 0; |
107 | ||
83510766 SG |
108 | video_sync(dev->parent); |
109 | } | |
110 | ||
703d885c RC |
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 | ||
a085aa1f RC |
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 | } | |
703d885c RC |
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 | } | |
a085aa1f RC |
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 | ||
83510766 SG |
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 | ||
a085aa1f RC |
308 | if (priv->escape) { |
309 | vidconsole_escape_char(dev, ch); | |
310 | return 0; | |
311 | } | |
312 | ||
83510766 | 313 | switch (ch) { |
a085aa1f RC |
314 | case '\x1b': |
315 | priv->escape_len = 0; | |
316 | priv->escape = 1; | |
317 | break; | |
5508f10a SG |
318 | case '\a': |
319 | /* beep */ | |
320 | break; | |
83510766 | 321 | case '\r': |
c5b77d01 | 322 | priv->xcur_frac = priv->xstart_frac; |
83510766 SG |
323 | break; |
324 | case '\n': | |
325 | vidconsole_newline(dev); | |
58c733a7 | 326 | vidconsole_entry_start(dev); |
83510766 SG |
327 | break; |
328 | case '\t': /* Tab (8 chars alignment) */ | |
f2661786 SG |
329 | priv->xcur_frac = ((priv->xcur_frac / priv->tab_width_frac) |
330 | + 1) * priv->tab_width_frac; | |
83510766 | 331 | |
f2661786 | 332 | if (priv->xcur_frac >= priv->xsize_frac) |
83510766 SG |
333 | vidconsole_newline(dev); |
334 | break; | |
335 | case '\b': | |
336 | vidconsole_back(dev); | |
58c733a7 | 337 | priv->last_ch = 0; |
83510766 SG |
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 | */ | |
f2661786 SG |
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) | |
83510766 | 352 | return ret; |
f2661786 | 353 | priv->xcur_frac += ret; |
58c733a7 | 354 | priv->last_ch = ch; |
f2661786 | 355 | if (priv->xcur_frac >= priv->xsize_frac) |
83510766 SG |
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); | |
889808da | 368 | video_sync(dev->parent); |
83510766 SG |
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++); | |
bbc8a8b4 | 377 | video_sync(dev->parent); |
83510766 SG |
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 | ||
f2661786 | 387 | priv->xsize_frac = VID_TO_POS(vid_priv->xsize); |
83510766 SG |
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; | |
83510766 | 397 | |
f2661786 SG |
398 | if (!priv->tab_width_frac) |
399 | priv->tab_width_frac = VID_TO_POS(priv->x_charsize) * 8; | |
400 | ||
f1a1247d SG |
401 | if (dev->seq) { |
402 | snprintf(sdev->name, sizeof(sdev->name), "vidconsole%d", | |
403 | dev->seq); | |
404 | } else { | |
405 | strcpy(sdev->name, "vidconsole"); | |
406 | } | |
f2661786 | 407 | |
83510766 SG |
408 | sdev->flags = DEV_FLAGS_OUTPUT; |
409 | sdev->putc = vidconsole_putc; | |
410 | sdev->puts = vidconsole_puts; | |
411 | sdev->priv = dev; | |
83510766 | 412 | |
720873bf | 413 | return stdio_register(sdev); |
83510766 SG |
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); | |
f2661786 SG |
427 | struct udevice *vid_dev = dev->parent; |
428 | struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); | |
83510766 | 429 | |
f2661786 SG |
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); | |
83510766 SG |
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 | ||
3f603cbb | 443 | if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev)) |
83510766 SG |
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 | ||
3f603cbb | 461 | if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev)) |
83510766 SG |
462 | return CMD_RET_FAILURE; |
463 | for (s = argv[1]; *s; s++) | |
464 | vidconsole_put_char(dev, *s); | |
465 | ||
889808da RC |
466 | video_sync(dev->parent); |
467 | ||
83510766 SG |
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 | ); |