123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681 |
- // SPDX-License-Identifier: GPL-2.0+
- /*
- * Character LCD driver for Linux
- *
- * Copyright (C) 2000-2008, Willy Tarreau <[email protected]>
- * Copyright (C) 2016-2017 Glider bvba
- */
- #include <linux/atomic.h>
- #include <linux/ctype.h>
- #include <linux/fs.h>
- #include <linux/miscdevice.h>
- #include <linux/module.h>
- #include <linux/notifier.h>
- #include <linux/reboot.h>
- #include <linux/slab.h>
- #include <linux/uaccess.h>
- #include <linux/workqueue.h>
- #include <generated/utsrelease.h>
- #include "charlcd.h"
- /* Keep the backlight on this many seconds for each flash */
- #define LCD_BL_TEMPO_PERIOD 4
- #define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */
- #define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */
- struct charlcd_priv {
- struct charlcd lcd;
- struct delayed_work bl_work;
- struct mutex bl_tempo_lock; /* Protects access to bl_tempo */
- bool bl_tempo;
- bool must_clear;
- /* contains the LCD config state */
- unsigned long flags;
- /* Current escape sequence and it's length or -1 if outside */
- struct {
- char buf[LCD_ESCAPE_LEN + 1];
- int len;
- } esc_seq;
- unsigned long long drvdata[];
- };
- #define charlcd_to_priv(p) container_of(p, struct charlcd_priv, lcd)
- /* Device single-open policy control */
- static atomic_t charlcd_available = ATOMIC_INIT(1);
- /* turn the backlight on or off */
- void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on)
- {
- struct charlcd_priv *priv = charlcd_to_priv(lcd);
- if (!lcd->ops->backlight)
- return;
- mutex_lock(&priv->bl_tempo_lock);
- if (!priv->bl_tempo)
- lcd->ops->backlight(lcd, on);
- mutex_unlock(&priv->bl_tempo_lock);
- }
- EXPORT_SYMBOL_GPL(charlcd_backlight);
- static void charlcd_bl_off(struct work_struct *work)
- {
- struct delayed_work *dwork = to_delayed_work(work);
- struct charlcd_priv *priv =
- container_of(dwork, struct charlcd_priv, bl_work);
- mutex_lock(&priv->bl_tempo_lock);
- if (priv->bl_tempo) {
- priv->bl_tempo = false;
- if (!(priv->flags & LCD_FLAG_L))
- priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
- }
- mutex_unlock(&priv->bl_tempo_lock);
- }
- /* turn the backlight on for a little while */
- void charlcd_poke(struct charlcd *lcd)
- {
- struct charlcd_priv *priv = charlcd_to_priv(lcd);
- if (!lcd->ops->backlight)
- return;
- cancel_delayed_work_sync(&priv->bl_work);
- mutex_lock(&priv->bl_tempo_lock);
- if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L))
- lcd->ops->backlight(lcd, CHARLCD_ON);
- priv->bl_tempo = true;
- schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ);
- mutex_unlock(&priv->bl_tempo_lock);
- }
- EXPORT_SYMBOL_GPL(charlcd_poke);
- static void charlcd_home(struct charlcd *lcd)
- {
- lcd->addr.x = 0;
- lcd->addr.y = 0;
- lcd->ops->home(lcd);
- }
- static void charlcd_print(struct charlcd *lcd, char c)
- {
- if (lcd->addr.x >= lcd->width)
- return;
- if (lcd->char_conv)
- c = lcd->char_conv[(unsigned char)c];
- if (!lcd->ops->print(lcd, c))
- lcd->addr.x++;
- /* prevents the cursor from wrapping onto the next line */
- if (lcd->addr.x == lcd->width)
- lcd->ops->gotoxy(lcd, lcd->addr.x - 1, lcd->addr.y);
- }
- static void charlcd_clear_display(struct charlcd *lcd)
- {
- lcd->ops->clear_display(lcd);
- lcd->addr.x = 0;
- lcd->addr.y = 0;
- }
- /*
- * Parses a movement command of the form "(.*);", where the group can be
- * any number of subcommands of the form "(x|y)[0-9]+".
- *
- * Returns whether the command is valid. The position arguments are
- * only written if the parsing was successful.
- *
- * For instance:
- * - ";" returns (<original x>, <original y>).
- * - "x1;" returns (1, <original y>).
- * - "y2x1;" returns (1, 2).
- * - "x12y34x56;" returns (56, 34).
- * - "" fails.
- * - "x" fails.
- * - "x;" fails.
- * - "x1" fails.
- * - "xy12;" fails.
- * - "x12yy12;" fails.
- * - "xx" fails.
- */
- static bool parse_xy(const char *s, unsigned long *x, unsigned long *y)
- {
- unsigned long new_x = *x;
- unsigned long new_y = *y;
- char *p;
- for (;;) {
- if (!*s)
- return false;
- if (*s == ';')
- break;
- if (*s == 'x') {
- new_x = simple_strtoul(s + 1, &p, 10);
- if (p == s + 1)
- return false;
- s = p;
- } else if (*s == 'y') {
- new_y = simple_strtoul(s + 1, &p, 10);
- if (p == s + 1)
- return false;
- s = p;
- } else {
- return false;
- }
- }
- *x = new_x;
- *y = new_y;
- return true;
- }
- /*
- * These are the file operation function for user access to /dev/lcd
- * This function can also be called from inside the kernel, by
- * setting file and ppos to NULL.
- *
- */
- static inline int handle_lcd_special_code(struct charlcd *lcd)
- {
- struct charlcd_priv *priv = charlcd_to_priv(lcd);
- /* LCD special codes */
- int processed = 0;
- char *esc = priv->esc_seq.buf + 2;
- int oldflags = priv->flags;
- /* check for display mode flags */
- switch (*esc) {
- case 'D': /* Display ON */
- priv->flags |= LCD_FLAG_D;
- if (priv->flags != oldflags)
- lcd->ops->display(lcd, CHARLCD_ON);
- processed = 1;
- break;
- case 'd': /* Display OFF */
- priv->flags &= ~LCD_FLAG_D;
- if (priv->flags != oldflags)
- lcd->ops->display(lcd, CHARLCD_OFF);
- processed = 1;
- break;
- case 'C': /* Cursor ON */
- priv->flags |= LCD_FLAG_C;
- if (priv->flags != oldflags)
- lcd->ops->cursor(lcd, CHARLCD_ON);
- processed = 1;
- break;
- case 'c': /* Cursor OFF */
- priv->flags &= ~LCD_FLAG_C;
- if (priv->flags != oldflags)
- lcd->ops->cursor(lcd, CHARLCD_OFF);
- processed = 1;
- break;
- case 'B': /* Blink ON */
- priv->flags |= LCD_FLAG_B;
- if (priv->flags != oldflags)
- lcd->ops->blink(lcd, CHARLCD_ON);
- processed = 1;
- break;
- case 'b': /* Blink OFF */
- priv->flags &= ~LCD_FLAG_B;
- if (priv->flags != oldflags)
- lcd->ops->blink(lcd, CHARLCD_OFF);
- processed = 1;
- break;
- case '+': /* Back light ON */
- priv->flags |= LCD_FLAG_L;
- if (priv->flags != oldflags)
- charlcd_backlight(lcd, CHARLCD_ON);
- processed = 1;
- break;
- case '-': /* Back light OFF */
- priv->flags &= ~LCD_FLAG_L;
- if (priv->flags != oldflags)
- charlcd_backlight(lcd, CHARLCD_OFF);
- processed = 1;
- break;
- case '*': /* Flash back light */
- charlcd_poke(lcd);
- processed = 1;
- break;
- case 'f': /* Small Font */
- priv->flags &= ~LCD_FLAG_F;
- if (priv->flags != oldflags)
- lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_SMALL);
- processed = 1;
- break;
- case 'F': /* Large Font */
- priv->flags |= LCD_FLAG_F;
- if (priv->flags != oldflags)
- lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_LARGE);
- processed = 1;
- break;
- case 'n': /* One Line */
- priv->flags &= ~LCD_FLAG_N;
- if (priv->flags != oldflags)
- lcd->ops->lines(lcd, CHARLCD_LINES_1);
- processed = 1;
- break;
- case 'N': /* Two Lines */
- priv->flags |= LCD_FLAG_N;
- if (priv->flags != oldflags)
- lcd->ops->lines(lcd, CHARLCD_LINES_2);
- processed = 1;
- break;
- case 'l': /* Shift Cursor Left */
- if (lcd->addr.x > 0) {
- if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
- lcd->addr.x--;
- }
- processed = 1;
- break;
- case 'r': /* shift cursor right */
- if (lcd->addr.x < lcd->width) {
- if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_RIGHT))
- lcd->addr.x++;
- }
- processed = 1;
- break;
- case 'L': /* shift display left */
- lcd->ops->shift_display(lcd, CHARLCD_SHIFT_LEFT);
- processed = 1;
- break;
- case 'R': /* shift display right */
- lcd->ops->shift_display(lcd, CHARLCD_SHIFT_RIGHT);
- processed = 1;
- break;
- case 'k': { /* kill end of line */
- int x, xs, ys;
- xs = lcd->addr.x;
- ys = lcd->addr.y;
- for (x = lcd->addr.x; x < lcd->width; x++)
- lcd->ops->print(lcd, ' ');
- /* restore cursor position */
- lcd->addr.x = xs;
- lcd->addr.y = ys;
- lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
- processed = 1;
- break;
- }
- case 'I': /* reinitialize display */
- lcd->ops->init_display(lcd);
- priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
- LCD_FLAG_C | LCD_FLAG_B;
- processed = 1;
- break;
- case 'G':
- if (lcd->ops->redefine_char)
- processed = lcd->ops->redefine_char(lcd, esc);
- else
- processed = 1;
- break;
- case 'x': /* gotoxy : LxXXX[yYYY]; */
- case 'y': /* gotoxy : LyYYY[xXXX]; */
- if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';')
- break;
- /* If the command is valid, move to the new address */
- if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y))
- lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
- /* Regardless of its validity, mark as processed */
- processed = 1;
- break;
- }
- return processed;
- }
- static void charlcd_write_char(struct charlcd *lcd, char c)
- {
- struct charlcd_priv *priv = charlcd_to_priv(lcd);
- /* first, we'll test if we're in escape mode */
- if ((c != '\n') && priv->esc_seq.len >= 0) {
- /* yes, let's add this char to the buffer */
- priv->esc_seq.buf[priv->esc_seq.len++] = c;
- priv->esc_seq.buf[priv->esc_seq.len] = '\0';
- } else {
- /* aborts any previous escape sequence */
- priv->esc_seq.len = -1;
- switch (c) {
- case LCD_ESCAPE_CHAR:
- /* start of an escape sequence */
- priv->esc_seq.len = 0;
- priv->esc_seq.buf[priv->esc_seq.len] = '\0';
- break;
- case '\b':
- /* go back one char and clear it */
- if (lcd->addr.x > 0) {
- /* back one char */
- if (!lcd->ops->shift_cursor(lcd,
- CHARLCD_SHIFT_LEFT))
- lcd->addr.x--;
- }
- /* replace with a space */
- charlcd_print(lcd, ' ');
- /* back one char again */
- if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
- lcd->addr.x--;
- break;
- case '\f':
- /* quickly clear the display */
- charlcd_clear_display(lcd);
- break;
- case '\n':
- /*
- * flush the remainder of the current line and
- * go to the beginning of the next line
- */
- for (; lcd->addr.x < lcd->width; lcd->addr.x++)
- lcd->ops->print(lcd, ' ');
- lcd->addr.x = 0;
- lcd->addr.y = (lcd->addr.y + 1) % lcd->height;
- lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
- break;
- case '\r':
- /* go to the beginning of the same line */
- lcd->addr.x = 0;
- lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
- break;
- case '\t':
- /* print a space instead of the tab */
- charlcd_print(lcd, ' ');
- break;
- default:
- /* simply print this char */
- charlcd_print(lcd, c);
- break;
- }
- }
- /*
- * now we'll see if we're in an escape mode and if the current
- * escape sequence can be understood.
- */
- if (priv->esc_seq.len >= 2) {
- int processed = 0;
- if (!strcmp(priv->esc_seq.buf, "[2J")) {
- /* clear the display */
- charlcd_clear_display(lcd);
- processed = 1;
- } else if (!strcmp(priv->esc_seq.buf, "[H")) {
- /* cursor to home */
- charlcd_home(lcd);
- processed = 1;
- }
- /* codes starting with ^[[L */
- else if ((priv->esc_seq.len >= 3) &&
- (priv->esc_seq.buf[0] == '[') &&
- (priv->esc_seq.buf[1] == 'L')) {
- processed = handle_lcd_special_code(lcd);
- }
- /* LCD special escape codes */
- /*
- * flush the escape sequence if it's been processed
- * or if it is getting too long.
- */
- if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN))
- priv->esc_seq.len = -1;
- } /* escape codes */
- }
- static struct charlcd *the_charlcd;
- static ssize_t charlcd_write(struct file *file, const char __user *buf,
- size_t count, loff_t *ppos)
- {
- const char __user *tmp = buf;
- char c;
- for (; count-- > 0; (*ppos)++, tmp++) {
- if (((count + 1) & 0x1f) == 0) {
- /*
- * charlcd_write() is invoked as a VFS->write() callback
- * and as such it is always invoked from preemptible
- * context and may sleep.
- */
- cond_resched();
- }
- if (get_user(c, tmp))
- return -EFAULT;
- charlcd_write_char(the_charlcd, c);
- }
- return tmp - buf;
- }
- static int charlcd_open(struct inode *inode, struct file *file)
- {
- struct charlcd_priv *priv = charlcd_to_priv(the_charlcd);
- int ret;
- ret = -EBUSY;
- if (!atomic_dec_and_test(&charlcd_available))
- goto fail; /* open only once at a time */
- ret = -EPERM;
- if (file->f_mode & FMODE_READ) /* device is write-only */
- goto fail;
- if (priv->must_clear) {
- priv->lcd.ops->clear_display(&priv->lcd);
- priv->must_clear = false;
- priv->lcd.addr.x = 0;
- priv->lcd.addr.y = 0;
- }
- return nonseekable_open(inode, file);
- fail:
- atomic_inc(&charlcd_available);
- return ret;
- }
- static int charlcd_release(struct inode *inode, struct file *file)
- {
- atomic_inc(&charlcd_available);
- return 0;
- }
- static const struct file_operations charlcd_fops = {
- .write = charlcd_write,
- .open = charlcd_open,
- .release = charlcd_release,
- .llseek = no_llseek,
- };
- static struct miscdevice charlcd_dev = {
- .minor = LCD_MINOR,
- .name = "lcd",
- .fops = &charlcd_fops,
- };
- static void charlcd_puts(struct charlcd *lcd, const char *s)
- {
- const char *tmp = s;
- int count = strlen(s);
- for (; count-- > 0; tmp++) {
- if (((count + 1) & 0x1f) == 0)
- cond_resched();
- charlcd_write_char(lcd, *tmp);
- }
- }
- #ifdef CONFIG_PANEL_BOOT_MESSAGE
- #define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
- #else
- #define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n"
- #endif
- #ifdef CONFIG_CHARLCD_BL_ON
- #define LCD_INIT_BL "\x1b[L+"
- #elif defined(CONFIG_CHARLCD_BL_FLASH)
- #define LCD_INIT_BL "\x1b[L*"
- #else
- #define LCD_INIT_BL "\x1b[L-"
- #endif
- /* initialize the LCD driver */
- static int charlcd_init(struct charlcd *lcd)
- {
- struct charlcd_priv *priv = charlcd_to_priv(lcd);
- int ret;
- priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
- LCD_FLAG_C | LCD_FLAG_B;
- if (lcd->ops->backlight) {
- mutex_init(&priv->bl_tempo_lock);
- INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off);
- }
- /*
- * before this line, we must NOT send anything to the display.
- * Since charlcd_init_display() needs to write data, we have to
- * enable mark the LCD initialized just before.
- */
- if (WARN_ON(!lcd->ops->init_display))
- return -EINVAL;
- ret = lcd->ops->init_display(lcd);
- if (ret)
- return ret;
- /* display a short message */
- charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT);
- /* clear the display on the next device opening */
- priv->must_clear = true;
- charlcd_home(lcd);
- return 0;
- }
- struct charlcd *charlcd_alloc(void)
- {
- struct charlcd_priv *priv;
- struct charlcd *lcd;
- priv = kzalloc(sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return NULL;
- priv->esc_seq.len = -1;
- lcd = &priv->lcd;
- return lcd;
- }
- EXPORT_SYMBOL_GPL(charlcd_alloc);
- void charlcd_free(struct charlcd *lcd)
- {
- kfree(charlcd_to_priv(lcd));
- }
- EXPORT_SYMBOL_GPL(charlcd_free);
- static int panel_notify_sys(struct notifier_block *this, unsigned long code,
- void *unused)
- {
- struct charlcd *lcd = the_charlcd;
- switch (code) {
- case SYS_DOWN:
- charlcd_puts(lcd,
- "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+");
- break;
- case SYS_HALT:
- charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+");
- break;
- case SYS_POWER_OFF:
- charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");
- break;
- default:
- break;
- }
- return NOTIFY_DONE;
- }
- static struct notifier_block panel_notifier = {
- .notifier_call = panel_notify_sys,
- };
- int charlcd_register(struct charlcd *lcd)
- {
- int ret;
- ret = charlcd_init(lcd);
- if (ret)
- return ret;
- ret = misc_register(&charlcd_dev);
- if (ret)
- return ret;
- the_charlcd = lcd;
- register_reboot_notifier(&panel_notifier);
- return 0;
- }
- EXPORT_SYMBOL_GPL(charlcd_register);
- int charlcd_unregister(struct charlcd *lcd)
- {
- struct charlcd_priv *priv = charlcd_to_priv(lcd);
- unregister_reboot_notifier(&panel_notifier);
- charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-");
- misc_deregister(&charlcd_dev);
- the_charlcd = NULL;
- if (lcd->ops->backlight) {
- cancel_delayed_work_sync(&priv->bl_work);
- priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
- }
- return 0;
- }
- EXPORT_SYMBOL_GPL(charlcd_unregister);
- MODULE_LICENSE("GPL");
|