123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885 |
- // SPDX-License-Identifier: GPL-2.0+
- /*
- * Character LCD driver for Linux
- *
- * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
- * Copyright (C) 2016-2017 Glider bvba
- */
- #include <linux/atomic.h>
- #include <linux/ctype.h>
- #include <linux/delay.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 <misc/charlcd.h>
- #define LCD_MINOR 156
- #define DEFAULT_LCD_BWIDTH 40
- #define DEFAULT_LCD_HWIDTH 64
- /* Keep the backlight on this many seconds for each flash */
- #define LCD_BL_TEMPO_PERIOD 4
- #define LCD_FLAG_B 0x0004 /* Blink on */
- #define LCD_FLAG_C 0x0008 /* Cursor on */
- #define LCD_FLAG_D 0x0010 /* Display on */
- #define LCD_FLAG_F 0x0020 /* Large font mode */
- #define LCD_FLAG_N 0x0040 /* 2-rows mode */
- #define LCD_FLAG_L 0x0080 /* Backlight enabled */
- /* LCD commands */
- #define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */
- #define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */
- #define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */
- #define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */
- #define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */
- #define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */
- #define LCD_CMD_BLINK_ON 0x01 /* Set blink on */
- #define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */
- #define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */
- #define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */
- #define LCD_CMD_FUNCTION_SET 0x20 /* Set function */
- #define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */
- #define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */
- #define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */
- #define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */
- #define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */
- #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 int flags;
- /* Contains the LCD X and Y offset */
- struct {
- unsigned long int x;
- unsigned long int y;
- } addr;
- /* 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[0];
- };
- #define to_priv(p) container_of(p, struct charlcd_priv, lcd)
- /* Device single-open policy control */
- static atomic_t charlcd_available = ATOMIC_INIT(1);
- /* sleeps that many milliseconds with a reschedule */
- static void long_sleep(int ms)
- {
- schedule_timeout_interruptible(msecs_to_jiffies(ms));
- }
- /* turn the backlight on or off */
- static void charlcd_backlight(struct charlcd *lcd, int on)
- {
- struct charlcd_priv *priv = 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);
- }
- 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, 0);
- }
- mutex_unlock(&priv->bl_tempo_lock);
- }
- /* turn the backlight on for a little while */
- void charlcd_poke(struct charlcd *lcd)
- {
- struct charlcd_priv *priv = 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, 1);
- 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_gotoxy(struct charlcd *lcd)
- {
- struct charlcd_priv *priv = to_priv(lcd);
- unsigned int addr;
- /*
- * we force the cursor to stay at the end of the
- * line if it wants to go farther
- */
- addr = priv->addr.x < lcd->bwidth ? priv->addr.x & (lcd->hwidth - 1)
- : lcd->bwidth - 1;
- if (priv->addr.y & 1)
- addr += lcd->hwidth;
- if (priv->addr.y & 2)
- addr += lcd->bwidth;
- lcd->ops->write_cmd(lcd, LCD_CMD_SET_DDRAM_ADDR | addr);
- }
- static void charlcd_home(struct charlcd *lcd)
- {
- struct charlcd_priv *priv = to_priv(lcd);
- priv->addr.x = 0;
- priv->addr.y = 0;
- charlcd_gotoxy(lcd);
- }
- static void charlcd_print(struct charlcd *lcd, char c)
- {
- struct charlcd_priv *priv = to_priv(lcd);
- if (priv->addr.x < lcd->bwidth) {
- if (lcd->char_conv)
- c = lcd->char_conv[(unsigned char)c];
- lcd->ops->write_data(lcd, c);
- priv->addr.x++;
- /* prevents the cursor from wrapping onto the next line */
- if (priv->addr.x == lcd->bwidth)
- charlcd_gotoxy(lcd);
- }
- }
- static void charlcd_clear_fast(struct charlcd *lcd)
- {
- int pos;
- charlcd_home(lcd);
- if (lcd->ops->clear_fast)
- lcd->ops->clear_fast(lcd);
- else
- for (pos = 0; pos < min(2, lcd->height) * lcd->hwidth; pos++)
- lcd->ops->write_data(lcd, ' ');
- charlcd_home(lcd);
- }
- /* clears the display and resets X/Y */
- static void charlcd_clear_display(struct charlcd *lcd)
- {
- struct charlcd_priv *priv = to_priv(lcd);
- lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CLEAR);
- priv->addr.x = 0;
- priv->addr.y = 0;
- /* we must wait a few milliseconds (15) */
- long_sleep(15);
- }
- static int charlcd_init_display(struct charlcd *lcd)
- {
- void (*write_cmd_raw)(struct charlcd *lcd, int cmd);
- struct charlcd_priv *priv = to_priv(lcd);
- u8 init;
- if (lcd->ifwidth != 4 && lcd->ifwidth != 8)
- return -EINVAL;
- priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
- LCD_FLAG_C | LCD_FLAG_B;
- long_sleep(20); /* wait 20 ms after power-up for the paranoid */
- /*
- * 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure
- * the LCD is in 8-bit mode afterwards
- */
- init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS;
- if (lcd->ifwidth == 4) {
- init >>= 4;
- write_cmd_raw = lcd->ops->write_cmd_raw4;
- } else {
- write_cmd_raw = lcd->ops->write_cmd;
- }
- write_cmd_raw(lcd, init);
- long_sleep(10);
- write_cmd_raw(lcd, init);
- long_sleep(10);
- write_cmd_raw(lcd, init);
- long_sleep(10);
- if (lcd->ifwidth == 4) {
- /* Switch to 4-bit mode, 1 line, small fonts */
- lcd->ops->write_cmd_raw4(lcd, LCD_CMD_FUNCTION_SET >> 4);
- long_sleep(10);
- }
- /* set font height and lines number */
- lcd->ops->write_cmd(lcd,
- LCD_CMD_FUNCTION_SET |
- ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
- ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) |
- ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0));
- long_sleep(10);
- /* display off, cursor off, blink off */
- lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CTRL);
- long_sleep(10);
- lcd->ops->write_cmd(lcd,
- LCD_CMD_DISPLAY_CTRL | /* set display mode */
- ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) |
- ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) |
- ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0));
- charlcd_backlight(lcd, (priv->flags & LCD_FLAG_L) ? 1 : 0);
- long_sleep(10);
- /* entry mode set : increment, cursor shifting */
- lcd->ops->write_cmd(lcd, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC);
- charlcd_clear_display(lcd);
- return 0;
- }
- /*
- * Parses an unsigned integer from a string, until a non-digit character
- * is found. The empty string is not accepted. No overflow checks are done.
- *
- * Returns whether the parsing was successful. Only in that case
- * the output parameters are written to.
- *
- * TODO: If the kernel adds an inplace version of kstrtoul(), this function
- * could be easily replaced by that.
- */
- static bool parse_n(const char *s, unsigned long *res, const char **next_s)
- {
- if (!isdigit(*s))
- return false;
- *res = 0;
- while (isdigit(*s)) {
- *res = *res * 10 + (*s - '0');
- ++s;
- }
- *next_s = s;
- return true;
- }
- /*
- * 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;
- for (;;) {
- if (!*s)
- return false;
- if (*s == ';')
- break;
- if (*s == 'x') {
- if (!parse_n(s + 1, &new_x, &s))
- return false;
- } else if (*s == 'y') {
- if (!parse_n(s + 1, &new_y, &s))
- return false;
- } 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 = 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;
- processed = 1;
- break;
- case 'd': /* Display OFF */
- priv->flags &= ~LCD_FLAG_D;
- processed = 1;
- break;
- case 'C': /* Cursor ON */
- priv->flags |= LCD_FLAG_C;
- processed = 1;
- break;
- case 'c': /* Cursor OFF */
- priv->flags &= ~LCD_FLAG_C;
- processed = 1;
- break;
- case 'B': /* Blink ON */
- priv->flags |= LCD_FLAG_B;
- processed = 1;
- break;
- case 'b': /* Blink OFF */
- priv->flags &= ~LCD_FLAG_B;
- processed = 1;
- break;
- case '+': /* Back light ON */
- priv->flags |= LCD_FLAG_L;
- processed = 1;
- break;
- case '-': /* Back light OFF */
- priv->flags &= ~LCD_FLAG_L;
- processed = 1;
- break;
- case '*': /* Flash back light */
- charlcd_poke(lcd);
- processed = 1;
- break;
- case 'f': /* Small Font */
- priv->flags &= ~LCD_FLAG_F;
- processed = 1;
- break;
- case 'F': /* Large Font */
- priv->flags |= LCD_FLAG_F;
- processed = 1;
- break;
- case 'n': /* One Line */
- priv->flags &= ~LCD_FLAG_N;
- processed = 1;
- break;
- case 'N': /* Two Lines */
- priv->flags |= LCD_FLAG_N;
- processed = 1;
- break;
- case 'l': /* Shift Cursor Left */
- if (priv->addr.x > 0) {
- /* back one char if not at end of line */
- if (priv->addr.x < lcd->bwidth)
- lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT);
- priv->addr.x--;
- }
- processed = 1;
- break;
- case 'r': /* shift cursor right */
- if (priv->addr.x < lcd->width) {
- /* allow the cursor to pass the end of the line */
- if (priv->addr.x < (lcd->bwidth - 1))
- lcd->ops->write_cmd(lcd,
- LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT);
- priv->addr.x++;
- }
- processed = 1;
- break;
- case 'L': /* shift display left */
- lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT);
- processed = 1;
- break;
- case 'R': /* shift display right */
- lcd->ops->write_cmd(lcd,
- LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT |
- LCD_CMD_SHIFT_RIGHT);
- processed = 1;
- break;
- case 'k': { /* kill end of line */
- int x;
- for (x = priv->addr.x; x < lcd->bwidth; x++)
- lcd->ops->write_data(lcd, ' ');
- /* restore cursor position */
- charlcd_gotoxy(lcd);
- processed = 1;
- break;
- }
- case 'I': /* reinitialize display */
- charlcd_init_display(lcd);
- processed = 1;
- break;
- case 'G': {
- /* Generator : LGcxxxxx...xx; must have <c> between '0'
- * and '7', representing the numerical ASCII code of the
- * redefined character, and <xx...xx> a sequence of 16
- * hex digits representing 8 bytes for each character.
- * Most LCDs will only use 5 lower bits of the 7 first
- * bytes.
- */
- unsigned char cgbytes[8];
- unsigned char cgaddr;
- int cgoffset;
- int shift;
- char value;
- int addr;
- if (!strchr(esc, ';'))
- break;
- esc++;
- cgaddr = *(esc++) - '0';
- if (cgaddr > 7) {
- processed = 1;
- break;
- }
- cgoffset = 0;
- shift = 0;
- value = 0;
- while (*esc && cgoffset < 8) {
- shift ^= 4;
- if (*esc >= '0' && *esc <= '9') {
- value |= (*esc - '0') << shift;
- } else if (*esc >= 'A' && *esc <= 'F') {
- value |= (*esc - 'A' + 10) << shift;
- } else if (*esc >= 'a' && *esc <= 'f') {
- value |= (*esc - 'a' + 10) << shift;
- } else {
- esc++;
- continue;
- }
- if (shift == 0) {
- cgbytes[cgoffset++] = value;
- value = 0;
- }
- esc++;
- }
- lcd->ops->write_cmd(lcd, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8));
- for (addr = 0; addr < cgoffset; addr++)
- lcd->ops->write_data(lcd, cgbytes[addr]);
- /* ensures that we stop writing to CGRAM */
- charlcd_gotoxy(lcd);
- 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, &priv->addr.x, &priv->addr.y))
- charlcd_gotoxy(lcd);
- /* Regardless of its validity, mark as processed */
- processed = 1;
- break;
- }
- /* TODO: This indent party here got ugly, clean it! */
- /* Check whether one flag was changed */
- if (oldflags == priv->flags)
- return processed;
- /* check whether one of B,C,D flags were changed */
- if ((oldflags ^ priv->flags) &
- (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D))
- /* set display mode */
- lcd->ops->write_cmd(lcd,
- LCD_CMD_DISPLAY_CTRL |
- ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) |
- ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) |
- ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0));
- /* check whether one of F,N flags was changed */
- else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N))
- lcd->ops->write_cmd(lcd,
- LCD_CMD_FUNCTION_SET |
- ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
- ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) |
- ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0));
- /* check whether L flag was changed */
- else if ((oldflags ^ priv->flags) & LCD_FLAG_L)
- charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L));
- return processed;
- }
- static void charlcd_write_char(struct charlcd *lcd, char c)
- {
- struct charlcd_priv *priv = 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 (priv->addr.x > 0) {
- /*
- * check if we're not at the
- * end of the line
- */
- if (priv->addr.x < lcd->bwidth)
- /* back one char */
- lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT);
- priv->addr.x--;
- }
- /* replace with a space */
- lcd->ops->write_data(lcd, ' ');
- /* back one char again */
- lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT);
- break;
- case '\f':
- /* quickly clear the display */
- charlcd_clear_fast(lcd);
- break;
- case '\n':
- /*
- * flush the remainder of the current line and
- * go to the beginning of the next line
- */
- for (; priv->addr.x < lcd->bwidth; priv->addr.x++)
- lcd->ops->write_data(lcd, ' ');
- priv->addr.x = 0;
- priv->addr.y = (priv->addr.y + 1) % lcd->height;
- charlcd_gotoxy(lcd);
- break;
- case '\r':
- /* go to the beginning of the same line */
- priv->addr.x = 0;
- charlcd_gotoxy(lcd);
- 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_fast(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 (!in_interrupt() && (((count + 1) & 0x1f) == 0))
- /*
- * let's be a little nice with other processes
- * that need some CPU
- */
- schedule();
- 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 = 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) {
- charlcd_clear_display(&priv->lcd);
- priv->must_clear = false;
- }
- 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 (!in_interrupt() && (((count + 1) & 0x1f) == 0))
- /*
- * let's be a little nice with other processes
- * that need some CPU
- */
- schedule();
- charlcd_write_char(lcd, *tmp);
- }
- }
- /* initialize the LCD driver */
- static int charlcd_init(struct charlcd *lcd)
- {
- struct charlcd_priv *priv = to_priv(lcd);
- int ret;
- 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.
- */
- ret = charlcd_init_display(lcd);
- if (ret)
- return ret;
- /* display a short message */
- #ifdef CONFIG_PANEL_CHANGE_MESSAGE
- #ifdef CONFIG_PANEL_BOOT_MESSAGE
- charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE);
- #endif
- #else
- charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE "\n");
- #endif
- /* clear the display on the next device opening */
- priv->must_clear = true;
- charlcd_home(lcd);
- return 0;
- }
- struct charlcd *charlcd_alloc(unsigned int drvdata_size)
- {
- struct charlcd_priv *priv;
- struct charlcd *lcd;
- priv = kzalloc(sizeof(*priv) + drvdata_size, GFP_KERNEL);
- if (!priv)
- return NULL;
- priv->esc_seq.len = -1;
- lcd = &priv->lcd;
- lcd->ifwidth = 8;
- lcd->bwidth = DEFAULT_LCD_BWIDTH;
- lcd->hwidth = DEFAULT_LCD_HWIDTH;
- lcd->drvdata = priv->drvdata;
- return lcd;
- }
- EXPORT_SYMBOL_GPL(charlcd_alloc);
- 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 = {
- panel_notify_sys,
- NULL,
- 0
- };
- 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 = 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, 0);
- }
- return 0;
- }
- EXPORT_SYMBOL_GPL(charlcd_unregister);
- MODULE_LICENSE("GPL");
|