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