linux/drivers/auxdisplay/charlcd.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Character LCD driver for Linux
   4 *
   5 * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
   6 * Copyright (C) 2016-2017 Glider bvba
   7 */
   8
   9#include <linux/atomic.h>
  10#include <linux/ctype.h>
  11#include <linux/fs.h>
  12#include <linux/miscdevice.h>
  13#include <linux/module.h>
  14#include <linux/notifier.h>
  15#include <linux/reboot.h>
  16#include <linux/slab.h>
  17#include <linux/uaccess.h>
  18#include <linux/workqueue.h>
  19
  20#include <generated/utsrelease.h>
  21
  22#include "charlcd.h"
  23
  24/* Keep the backlight on this many seconds for each flash */
  25#define LCD_BL_TEMPO_PERIOD     4
  26
  27#define LCD_ESCAPE_LEN          24      /* Max chars for LCD escape command */
  28#define LCD_ESCAPE_CHAR         27      /* Use char 27 for escape command */
  29
  30struct charlcd_priv {
  31        struct charlcd lcd;
  32
  33        struct delayed_work bl_work;
  34        struct mutex bl_tempo_lock;     /* Protects access to bl_tempo */
  35        bool bl_tempo;
  36
  37        bool must_clear;
  38
  39        /* contains the LCD config state */
  40        unsigned long int flags;
  41
  42        /* Current escape sequence and it's length or -1 if outside */
  43        struct {
  44                char buf[LCD_ESCAPE_LEN + 1];
  45                int len;
  46        } esc_seq;
  47
  48        unsigned long long drvdata[];
  49};
  50
  51#define charlcd_to_priv(p)      container_of(p, struct charlcd_priv, lcd)
  52
  53/* Device single-open policy control */
  54static atomic_t charlcd_available = ATOMIC_INIT(1);
  55
  56/* turn the backlight on or off */
  57void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on)
  58{
  59        struct charlcd_priv *priv = charlcd_to_priv(lcd);
  60
  61        if (!lcd->ops->backlight)
  62                return;
  63
  64        mutex_lock(&priv->bl_tempo_lock);
  65        if (!priv->bl_tempo)
  66                lcd->ops->backlight(lcd, on);
  67        mutex_unlock(&priv->bl_tempo_lock);
  68}
  69EXPORT_SYMBOL_GPL(charlcd_backlight);
  70
  71static void charlcd_bl_off(struct work_struct *work)
  72{
  73        struct delayed_work *dwork = to_delayed_work(work);
  74        struct charlcd_priv *priv =
  75                container_of(dwork, struct charlcd_priv, bl_work);
  76
  77        mutex_lock(&priv->bl_tempo_lock);
  78        if (priv->bl_tempo) {
  79                priv->bl_tempo = false;
  80                if (!(priv->flags & LCD_FLAG_L))
  81                        priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
  82        }
  83        mutex_unlock(&priv->bl_tempo_lock);
  84}
  85
  86/* turn the backlight on for a little while */
  87void charlcd_poke(struct charlcd *lcd)
  88{
  89        struct charlcd_priv *priv = charlcd_to_priv(lcd);
  90
  91        if (!lcd->ops->backlight)
  92                return;
  93
  94        cancel_delayed_work_sync(&priv->bl_work);
  95
  96        mutex_lock(&priv->bl_tempo_lock);
  97        if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L))
  98                lcd->ops->backlight(lcd, CHARLCD_ON);
  99        priv->bl_tempo = true;
 100        schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ);
 101        mutex_unlock(&priv->bl_tempo_lock);
 102}
 103EXPORT_SYMBOL_GPL(charlcd_poke);
 104
 105static void charlcd_home(struct charlcd *lcd)
 106{
 107        lcd->addr.x = 0;
 108        lcd->addr.y = 0;
 109        lcd->ops->home(lcd);
 110}
 111
 112static void charlcd_print(struct charlcd *lcd, char c)
 113{
 114        if (lcd->addr.x >= lcd->width)
 115                return;
 116
 117        if (lcd->char_conv)
 118                c = lcd->char_conv[(unsigned char)c];
 119
 120        if (!lcd->ops->print(lcd, c))
 121                lcd->addr.x++;
 122
 123        /* prevents the cursor from wrapping onto the next line */
 124        if (lcd->addr.x == lcd->width)
 125                lcd->ops->gotoxy(lcd, lcd->addr.x - 1, lcd->addr.y);
 126}
 127
 128static void charlcd_clear_display(struct charlcd *lcd)
 129{
 130        lcd->ops->clear_display(lcd);
 131        lcd->addr.x = 0;
 132        lcd->addr.y = 0;
 133}
 134
 135/*
 136 * Parses a movement command of the form "(.*);", where the group can be
 137 * any number of subcommands of the form "(x|y)[0-9]+".
 138 *
 139 * Returns whether the command is valid. The position arguments are
 140 * only written if the parsing was successful.
 141 *
 142 * For instance:
 143 *   - ";"          returns (<original x>, <original y>).
 144 *   - "x1;"        returns (1, <original y>).
 145 *   - "y2x1;"      returns (1, 2).
 146 *   - "x12y34x56;" returns (56, 34).
 147 *   - ""           fails.
 148 *   - "x"          fails.
 149 *   - "x;"         fails.
 150 *   - "x1"         fails.
 151 *   - "xy12;"      fails.
 152 *   - "x12yy12;"   fails.
 153 *   - "xx"         fails.
 154 */
 155static bool parse_xy(const char *s, unsigned long *x, unsigned long *y)
 156{
 157        unsigned long new_x = *x;
 158        unsigned long new_y = *y;
 159        char *p;
 160
 161        for (;;) {
 162                if (!*s)
 163                        return false;
 164
 165                if (*s == ';')
 166                        break;
 167
 168                if (*s == 'x') {
 169                        new_x = simple_strtoul(s + 1, &p, 10);
 170                        if (p == s + 1)
 171                                return false;
 172                        s = p;
 173                } else if (*s == 'y') {
 174                        new_y = simple_strtoul(s + 1, &p, 10);
 175                        if (p == s + 1)
 176                                return false;
 177                        s = p;
 178                } else {
 179                        return false;
 180                }
 181        }
 182
 183        *x = new_x;
 184        *y = new_y;
 185        return true;
 186}
 187
 188/*
 189 * These are the file operation function for user access to /dev/lcd
 190 * This function can also be called from inside the kernel, by
 191 * setting file and ppos to NULL.
 192 *
 193 */
 194
 195static inline int handle_lcd_special_code(struct charlcd *lcd)
 196{
 197        struct charlcd_priv *priv = charlcd_to_priv(lcd);
 198
 199        /* LCD special codes */
 200
 201        int processed = 0;
 202
 203        char *esc = priv->esc_seq.buf + 2;
 204        int oldflags = priv->flags;
 205
 206        /* check for display mode flags */
 207        switch (*esc) {
 208        case 'D':       /* Display ON */
 209                priv->flags |= LCD_FLAG_D;
 210                if (priv->flags != oldflags)
 211                        lcd->ops->display(lcd, CHARLCD_ON);
 212
 213                processed = 1;
 214                break;
 215        case 'd':       /* Display OFF */
 216                priv->flags &= ~LCD_FLAG_D;
 217                if (priv->flags != oldflags)
 218                        lcd->ops->display(lcd, CHARLCD_OFF);
 219
 220                processed = 1;
 221                break;
 222        case 'C':       /* Cursor ON */
 223                priv->flags |= LCD_FLAG_C;
 224                if (priv->flags != oldflags)
 225                        lcd->ops->cursor(lcd, CHARLCD_ON);
 226
 227                processed = 1;
 228                break;
 229        case 'c':       /* Cursor OFF */
 230                priv->flags &= ~LCD_FLAG_C;
 231                if (priv->flags != oldflags)
 232                        lcd->ops->cursor(lcd, CHARLCD_OFF);
 233
 234                processed = 1;
 235                break;
 236        case 'B':       /* Blink ON */
 237                priv->flags |= LCD_FLAG_B;
 238                if (priv->flags != oldflags)
 239                        lcd->ops->blink(lcd, CHARLCD_ON);
 240
 241                processed = 1;
 242                break;
 243        case 'b':       /* Blink OFF */
 244                priv->flags &= ~LCD_FLAG_B;
 245                if (priv->flags != oldflags)
 246                        lcd->ops->blink(lcd, CHARLCD_OFF);
 247
 248                processed = 1;
 249                break;
 250        case '+':       /* Back light ON */
 251                priv->flags |= LCD_FLAG_L;
 252                if (priv->flags != oldflags)
 253                        charlcd_backlight(lcd, CHARLCD_ON);
 254
 255                processed = 1;
 256                break;
 257        case '-':       /* Back light OFF */
 258                priv->flags &= ~LCD_FLAG_L;
 259                if (priv->flags != oldflags)
 260                        charlcd_backlight(lcd, CHARLCD_OFF);
 261
 262                processed = 1;
 263                break;
 264        case '*':       /* Flash back light */
 265                charlcd_poke(lcd);
 266                processed = 1;
 267                break;
 268        case 'f':       /* Small Font */
 269                priv->flags &= ~LCD_FLAG_F;
 270                if (priv->flags != oldflags)
 271                        lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_SMALL);
 272
 273                processed = 1;
 274                break;
 275        case 'F':       /* Large Font */
 276                priv->flags |= LCD_FLAG_F;
 277                if (priv->flags != oldflags)
 278                        lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_LARGE);
 279
 280                processed = 1;
 281                break;
 282        case 'n':       /* One Line */
 283                priv->flags &= ~LCD_FLAG_N;
 284                if (priv->flags != oldflags)
 285                        lcd->ops->lines(lcd, CHARLCD_LINES_1);
 286
 287                processed = 1;
 288                break;
 289        case 'N':       /* Two Lines */
 290                priv->flags |= LCD_FLAG_N;
 291                if (priv->flags != oldflags)
 292                        lcd->ops->lines(lcd, CHARLCD_LINES_2);
 293
 294                processed = 1;
 295                break;
 296        case 'l':       /* Shift Cursor Left */
 297                if (lcd->addr.x > 0) {
 298                        if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
 299                                lcd->addr.x--;
 300                }
 301
 302                processed = 1;
 303                break;
 304        case 'r':       /* shift cursor right */
 305                if (lcd->addr.x < lcd->width) {
 306                        if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_RIGHT))
 307                                lcd->addr.x++;
 308                }
 309
 310                processed = 1;
 311                break;
 312        case 'L':       /* shift display left */
 313                lcd->ops->shift_display(lcd, CHARLCD_SHIFT_LEFT);
 314                processed = 1;
 315                break;
 316        case 'R':       /* shift display right */
 317                lcd->ops->shift_display(lcd, CHARLCD_SHIFT_RIGHT);
 318                processed = 1;
 319                break;
 320        case 'k': {     /* kill end of line */
 321                int x, xs, ys;
 322
 323                xs = lcd->addr.x;
 324                ys = lcd->addr.y;
 325                for (x = lcd->addr.x; x < lcd->width; x++)
 326                        lcd->ops->print(lcd, ' ');
 327
 328                /* restore cursor position */
 329                lcd->addr.x = xs;
 330                lcd->addr.y = ys;
 331                lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
 332                processed = 1;
 333                break;
 334        }
 335        case 'I':       /* reinitialize display */
 336                lcd->ops->init_display(lcd);
 337                priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
 338                        LCD_FLAG_C | LCD_FLAG_B;
 339                processed = 1;
 340                break;
 341        case 'G':
 342                if (lcd->ops->redefine_char)
 343                        processed = lcd->ops->redefine_char(lcd, esc);
 344                else
 345                        processed = 1;
 346                break;
 347
 348        case 'x':       /* gotoxy : LxXXX[yYYY]; */
 349        case 'y':       /* gotoxy : LyYYY[xXXX]; */
 350                if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';')
 351                        break;
 352
 353                /* If the command is valid, move to the new address */
 354                if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y))
 355                        lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
 356
 357                /* Regardless of its validity, mark as processed */
 358                processed = 1;
 359                break;
 360        }
 361
 362        return processed;
 363}
 364
 365static void charlcd_write_char(struct charlcd *lcd, char c)
 366{
 367        struct charlcd_priv *priv = charlcd_to_priv(lcd);
 368
 369        /* first, we'll test if we're in escape mode */
 370        if ((c != '\n') && priv->esc_seq.len >= 0) {
 371                /* yes, let's add this char to the buffer */
 372                priv->esc_seq.buf[priv->esc_seq.len++] = c;
 373                priv->esc_seq.buf[priv->esc_seq.len] = '\0';
 374        } else {
 375                /* aborts any previous escape sequence */
 376                priv->esc_seq.len = -1;
 377
 378                switch (c) {
 379                case LCD_ESCAPE_CHAR:
 380                        /* start of an escape sequence */
 381                        priv->esc_seq.len = 0;
 382                        priv->esc_seq.buf[priv->esc_seq.len] = '\0';
 383                        break;
 384                case '\b':
 385                        /* go back one char and clear it */
 386                        if (lcd->addr.x > 0) {
 387                                /* back one char */
 388                                if (!lcd->ops->shift_cursor(lcd,
 389                                                        CHARLCD_SHIFT_LEFT))
 390                                        lcd->addr.x--;
 391                        }
 392                        /* replace with a space */
 393                        charlcd_print(lcd, ' ');
 394                        /* back one char again */
 395                        if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
 396                                lcd->addr.x--;
 397
 398                        break;
 399                case '\f':
 400                        /* quickly clear the display */
 401                        charlcd_clear_display(lcd);
 402                        break;
 403                case '\n':
 404                        /*
 405                         * flush the remainder of the current line and
 406                         * go to the beginning of the next line
 407                         */
 408                        for (; lcd->addr.x < lcd->width; lcd->addr.x++)
 409                                lcd->ops->print(lcd, ' ');
 410
 411                        lcd->addr.x = 0;
 412                        lcd->addr.y = (lcd->addr.y + 1) % lcd->height;
 413                        lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
 414                        break;
 415                case '\r':
 416                        /* go to the beginning of the same line */
 417                        lcd->addr.x = 0;
 418                        lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
 419                        break;
 420                case '\t':
 421                        /* print a space instead of the tab */
 422                        charlcd_print(lcd, ' ');
 423                        break;
 424                default:
 425                        /* simply print this char */
 426                        charlcd_print(lcd, c);
 427                        break;
 428                }
 429        }
 430
 431        /*
 432         * now we'll see if we're in an escape mode and if the current
 433         * escape sequence can be understood.
 434         */
 435        if (priv->esc_seq.len >= 2) {
 436                int processed = 0;
 437
 438                if (!strcmp(priv->esc_seq.buf, "[2J")) {
 439                        /* clear the display */
 440                        charlcd_clear_display(lcd);
 441                        processed = 1;
 442                } else if (!strcmp(priv->esc_seq.buf, "[H")) {
 443                        /* cursor to home */
 444                        charlcd_home(lcd);
 445                        processed = 1;
 446                }
 447                /* codes starting with ^[[L */
 448                else if ((priv->esc_seq.len >= 3) &&
 449                         (priv->esc_seq.buf[0] == '[') &&
 450                         (priv->esc_seq.buf[1] == 'L')) {
 451                        processed = handle_lcd_special_code(lcd);
 452                }
 453
 454                /* LCD special escape codes */
 455                /*
 456                 * flush the escape sequence if it's been processed
 457                 * or if it is getting too long.
 458                 */
 459                if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN))
 460                        priv->esc_seq.len = -1;
 461        } /* escape codes */
 462}
 463
 464static struct charlcd *the_charlcd;
 465
 466static ssize_t charlcd_write(struct file *file, const char __user *buf,
 467                             size_t count, loff_t *ppos)
 468{
 469        const char __user *tmp = buf;
 470        char c;
 471
 472        for (; count-- > 0; (*ppos)++, tmp++) {
 473                if (((count + 1) & 0x1f) == 0) {
 474                        /*
 475                         * charlcd_write() is invoked as a VFS->write() callback
 476                         * and as such it is always invoked from preemptible
 477                         * context and may sleep.
 478                         */
 479                        cond_resched();
 480                }
 481
 482                if (get_user(c, tmp))
 483                        return -EFAULT;
 484
 485                charlcd_write_char(the_charlcd, c);
 486        }
 487
 488        return tmp - buf;
 489}
 490
 491static int charlcd_open(struct inode *inode, struct file *file)
 492{
 493        struct charlcd_priv *priv = charlcd_to_priv(the_charlcd);
 494        int ret;
 495
 496        ret = -EBUSY;
 497        if (!atomic_dec_and_test(&charlcd_available))
 498                goto fail;      /* open only once at a time */
 499
 500        ret = -EPERM;
 501        if (file->f_mode & FMODE_READ)  /* device is write-only */
 502                goto fail;
 503
 504        if (priv->must_clear) {
 505                priv->lcd.ops->clear_display(&priv->lcd);
 506                priv->must_clear = false;
 507                priv->lcd.addr.x = 0;
 508                priv->lcd.addr.y = 0;
 509        }
 510        return nonseekable_open(inode, file);
 511
 512 fail:
 513        atomic_inc(&charlcd_available);
 514        return ret;
 515}
 516
 517static int charlcd_release(struct inode *inode, struct file *file)
 518{
 519        atomic_inc(&charlcd_available);
 520        return 0;
 521}
 522
 523static const struct file_operations charlcd_fops = {
 524        .write   = charlcd_write,
 525        .open    = charlcd_open,
 526        .release = charlcd_release,
 527        .llseek  = no_llseek,
 528};
 529
 530static struct miscdevice charlcd_dev = {
 531        .minor  = LCD_MINOR,
 532        .name   = "lcd",
 533        .fops   = &charlcd_fops,
 534};
 535
 536static void charlcd_puts(struct charlcd *lcd, const char *s)
 537{
 538        const char *tmp = s;
 539        int count = strlen(s);
 540
 541        for (; count-- > 0; tmp++) {
 542                if (((count + 1) & 0x1f) == 0)
 543                        cond_resched();
 544
 545                charlcd_write_char(lcd, *tmp);
 546        }
 547}
 548
 549#ifdef CONFIG_PANEL_BOOT_MESSAGE
 550#define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
 551#else
 552#define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n"
 553#endif
 554
 555#ifdef CONFIG_CHARLCD_BL_ON
 556#define LCD_INIT_BL "\x1b[L+"
 557#elif defined(CONFIG_CHARLCD_BL_FLASH)
 558#define LCD_INIT_BL "\x1b[L*"
 559#else
 560#define LCD_INIT_BL "\x1b[L-"
 561#endif
 562
 563/* initialize the LCD driver */
 564static int charlcd_init(struct charlcd *lcd)
 565{
 566        struct charlcd_priv *priv = charlcd_to_priv(lcd);
 567        int ret;
 568
 569        priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
 570                      LCD_FLAG_C | LCD_FLAG_B;
 571        if (lcd->ops->backlight) {
 572                mutex_init(&priv->bl_tempo_lock);
 573                INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off);
 574        }
 575
 576        /*
 577         * before this line, we must NOT send anything to the display.
 578         * Since charlcd_init_display() needs to write data, we have to
 579         * enable mark the LCD initialized just before.
 580         */
 581        ret = lcd->ops->init_display(lcd);
 582        if (ret)
 583                return ret;
 584
 585        /* display a short message */
 586        charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT);
 587
 588        /* clear the display on the next device opening */
 589        priv->must_clear = true;
 590        charlcd_home(lcd);
 591        return 0;
 592}
 593
 594struct charlcd *charlcd_alloc(void)
 595{
 596        struct charlcd_priv *priv;
 597        struct charlcd *lcd;
 598
 599        priv = kzalloc(sizeof(*priv), GFP_KERNEL);
 600        if (!priv)
 601                return NULL;
 602
 603        priv->esc_seq.len = -1;
 604
 605        lcd = &priv->lcd;
 606
 607        return lcd;
 608}
 609EXPORT_SYMBOL_GPL(charlcd_alloc);
 610
 611void charlcd_free(struct charlcd *lcd)
 612{
 613        kfree(charlcd_to_priv(lcd));
 614}
 615EXPORT_SYMBOL_GPL(charlcd_free);
 616
 617static int panel_notify_sys(struct notifier_block *this, unsigned long code,
 618                            void *unused)
 619{
 620        struct charlcd *lcd = the_charlcd;
 621
 622        switch (code) {
 623        case SYS_DOWN:
 624                charlcd_puts(lcd,
 625                             "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+");
 626                break;
 627        case SYS_HALT:
 628                charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+");
 629                break;
 630        case SYS_POWER_OFF:
 631                charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");
 632                break;
 633        default:
 634                break;
 635        }
 636        return NOTIFY_DONE;
 637}
 638
 639static struct notifier_block panel_notifier = {
 640        panel_notify_sys,
 641        NULL,
 642        0
 643};
 644
 645int charlcd_register(struct charlcd *lcd)
 646{
 647        int ret;
 648
 649        ret = charlcd_init(lcd);
 650        if (ret)
 651                return ret;
 652
 653        ret = misc_register(&charlcd_dev);
 654        if (ret)
 655                return ret;
 656
 657        the_charlcd = lcd;
 658        register_reboot_notifier(&panel_notifier);
 659        return 0;
 660}
 661EXPORT_SYMBOL_GPL(charlcd_register);
 662
 663int charlcd_unregister(struct charlcd *lcd)
 664{
 665        struct charlcd_priv *priv = charlcd_to_priv(lcd);
 666
 667        unregister_reboot_notifier(&panel_notifier);
 668        charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-");
 669        misc_deregister(&charlcd_dev);
 670        the_charlcd = NULL;
 671        if (lcd->ops->backlight) {
 672                cancel_delayed_work_sync(&priv->bl_work);
 673                priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
 674        }
 675
 676        return 0;
 677}
 678EXPORT_SYMBOL_GPL(charlcd_unregister);
 679
 680MODULE_LICENSE("GPL");
 681