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