1351f683bSMiguel Ojeda // SPDX-License-Identifier: GPL-2.0+ 239f8ea46SGeert Uytterhoeven /* 339f8ea46SGeert Uytterhoeven * Character LCD driver for Linux 439f8ea46SGeert Uytterhoeven * 539f8ea46SGeert Uytterhoeven * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu> 639f8ea46SGeert Uytterhoeven * Copyright (C) 2016-2017 Glider bvba 739f8ea46SGeert Uytterhoeven */ 839f8ea46SGeert Uytterhoeven 939f8ea46SGeert Uytterhoeven #include <linux/atomic.h> 10b34050faSMiguel Ojeda #include <linux/ctype.h> 1139f8ea46SGeert Uytterhoeven #include <linux/delay.h> 1239f8ea46SGeert Uytterhoeven #include <linux/fs.h> 1339f8ea46SGeert Uytterhoeven #include <linux/miscdevice.h> 1439f8ea46SGeert Uytterhoeven #include <linux/module.h> 1539f8ea46SGeert Uytterhoeven #include <linux/notifier.h> 1639f8ea46SGeert Uytterhoeven #include <linux/reboot.h> 1739f8ea46SGeert Uytterhoeven #include <linux/slab.h> 1839f8ea46SGeert Uytterhoeven #include <linux/uaccess.h> 1939f8ea46SGeert Uytterhoeven #include <linux/workqueue.h> 2039f8ea46SGeert Uytterhoeven 2139f8ea46SGeert Uytterhoeven #include <generated/utsrelease.h> 2239f8ea46SGeert Uytterhoeven 2375354284SMasahiro Yamada #include "charlcd.h" 242545c1c9SLars Poeschel #include "hd44780_common.h" 2539f8ea46SGeert Uytterhoeven 2639f8ea46SGeert Uytterhoeven /* Keep the backlight on this many seconds for each flash */ 2739f8ea46SGeert Uytterhoeven #define LCD_BL_TEMPO_PERIOD 4 2839f8ea46SGeert Uytterhoeven 2939f8ea46SGeert Uytterhoeven #define LCD_FLAG_B 0x0004 /* Blink on */ 3039f8ea46SGeert Uytterhoeven #define LCD_FLAG_C 0x0008 /* Cursor on */ 3139f8ea46SGeert Uytterhoeven #define LCD_FLAG_D 0x0010 /* Display on */ 3239f8ea46SGeert Uytterhoeven #define LCD_FLAG_F 0x0020 /* Large font mode */ 3339f8ea46SGeert Uytterhoeven #define LCD_FLAG_N 0x0040 /* 2-rows mode */ 3439f8ea46SGeert Uytterhoeven #define LCD_FLAG_L 0x0080 /* Backlight enabled */ 3539f8ea46SGeert Uytterhoeven 3639f8ea46SGeert Uytterhoeven /* LCD commands */ 3739f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */ 3839f8ea46SGeert Uytterhoeven 3939f8ea46SGeert Uytterhoeven #define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */ 4039f8ea46SGeert Uytterhoeven #define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */ 4139f8ea46SGeert Uytterhoeven 4239f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */ 4339f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */ 4439f8ea46SGeert Uytterhoeven #define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */ 4539f8ea46SGeert Uytterhoeven #define LCD_CMD_BLINK_ON 0x01 /* Set blink on */ 4639f8ea46SGeert Uytterhoeven 4739f8ea46SGeert Uytterhoeven #define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */ 4839f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */ 4939f8ea46SGeert Uytterhoeven #define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */ 5039f8ea46SGeert Uytterhoeven 5139f8ea46SGeert Uytterhoeven #define LCD_CMD_FUNCTION_SET 0x20 /* Set function */ 5239f8ea46SGeert Uytterhoeven #define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */ 5339f8ea46SGeert Uytterhoeven #define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */ 5439f8ea46SGeert Uytterhoeven #define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */ 5539f8ea46SGeert Uytterhoeven 5639f8ea46SGeert Uytterhoeven #define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */ 5739f8ea46SGeert Uytterhoeven 5839f8ea46SGeert Uytterhoeven #define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */ 5939f8ea46SGeert Uytterhoeven 6039f8ea46SGeert Uytterhoeven #define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */ 6139f8ea46SGeert Uytterhoeven #define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */ 6239f8ea46SGeert Uytterhoeven 6339f8ea46SGeert Uytterhoeven struct charlcd_priv { 6439f8ea46SGeert Uytterhoeven struct charlcd lcd; 6539f8ea46SGeert Uytterhoeven 6639f8ea46SGeert Uytterhoeven struct delayed_work bl_work; 6739f8ea46SGeert Uytterhoeven struct mutex bl_tempo_lock; /* Protects access to bl_tempo */ 6839f8ea46SGeert Uytterhoeven bool bl_tempo; 6939f8ea46SGeert Uytterhoeven 7039f8ea46SGeert Uytterhoeven bool must_clear; 7139f8ea46SGeert Uytterhoeven 7239f8ea46SGeert Uytterhoeven /* contains the LCD config state */ 7339f8ea46SGeert Uytterhoeven unsigned long int flags; 7439f8ea46SGeert Uytterhoeven 7539f8ea46SGeert Uytterhoeven /* Current escape sequence and it's length or -1 if outside */ 7639f8ea46SGeert Uytterhoeven struct { 7739f8ea46SGeert Uytterhoeven char buf[LCD_ESCAPE_LEN + 1]; 7839f8ea46SGeert Uytterhoeven int len; 7939f8ea46SGeert Uytterhoeven } esc_seq; 8039f8ea46SGeert Uytterhoeven 812f920c0fSGustavo A. R. Silva unsigned long long drvdata[]; 8239f8ea46SGeert Uytterhoeven }; 8339f8ea46SGeert Uytterhoeven 84b658a211SAndy Shevchenko #define charlcd_to_priv(p) container_of(p, struct charlcd_priv, lcd) 8539f8ea46SGeert Uytterhoeven 8639f8ea46SGeert Uytterhoeven /* Device single-open policy control */ 8739f8ea46SGeert Uytterhoeven static atomic_t charlcd_available = ATOMIC_INIT(1); 8839f8ea46SGeert Uytterhoeven 8939f8ea46SGeert Uytterhoeven /* sleeps that many milliseconds with a reschedule */ 9039f8ea46SGeert Uytterhoeven static void long_sleep(int ms) 9139f8ea46SGeert Uytterhoeven { 9239f8ea46SGeert Uytterhoeven schedule_timeout_interruptible(msecs_to_jiffies(ms)); 9339f8ea46SGeert Uytterhoeven } 9439f8ea46SGeert Uytterhoeven 9539f8ea46SGeert Uytterhoeven /* turn the backlight on or off */ 9666ce7d5cSLars Poeschel static void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on) 9739f8ea46SGeert Uytterhoeven { 98b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 9939f8ea46SGeert Uytterhoeven 10039f8ea46SGeert Uytterhoeven if (!lcd->ops->backlight) 10139f8ea46SGeert Uytterhoeven return; 10239f8ea46SGeert Uytterhoeven 10339f8ea46SGeert Uytterhoeven mutex_lock(&priv->bl_tempo_lock); 10439f8ea46SGeert Uytterhoeven if (!priv->bl_tempo) 10539f8ea46SGeert Uytterhoeven lcd->ops->backlight(lcd, on); 10639f8ea46SGeert Uytterhoeven mutex_unlock(&priv->bl_tempo_lock); 10739f8ea46SGeert Uytterhoeven } 10839f8ea46SGeert Uytterhoeven 10939f8ea46SGeert Uytterhoeven static void charlcd_bl_off(struct work_struct *work) 11039f8ea46SGeert Uytterhoeven { 11139f8ea46SGeert Uytterhoeven struct delayed_work *dwork = to_delayed_work(work); 11239f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = 11339f8ea46SGeert Uytterhoeven container_of(dwork, struct charlcd_priv, bl_work); 11439f8ea46SGeert Uytterhoeven 11539f8ea46SGeert Uytterhoeven mutex_lock(&priv->bl_tempo_lock); 11639f8ea46SGeert Uytterhoeven if (priv->bl_tempo) { 11739f8ea46SGeert Uytterhoeven priv->bl_tempo = false; 11839f8ea46SGeert Uytterhoeven if (!(priv->flags & LCD_FLAG_L)) 11939f8ea46SGeert Uytterhoeven priv->lcd.ops->backlight(&priv->lcd, 0); 12039f8ea46SGeert Uytterhoeven } 12139f8ea46SGeert Uytterhoeven mutex_unlock(&priv->bl_tempo_lock); 12239f8ea46SGeert Uytterhoeven } 12339f8ea46SGeert Uytterhoeven 12439f8ea46SGeert Uytterhoeven /* turn the backlight on for a little while */ 12539f8ea46SGeert Uytterhoeven void charlcd_poke(struct charlcd *lcd) 12639f8ea46SGeert Uytterhoeven { 127b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 12839f8ea46SGeert Uytterhoeven 12939f8ea46SGeert Uytterhoeven if (!lcd->ops->backlight) 13039f8ea46SGeert Uytterhoeven return; 13139f8ea46SGeert Uytterhoeven 13239f8ea46SGeert Uytterhoeven cancel_delayed_work_sync(&priv->bl_work); 13339f8ea46SGeert Uytterhoeven 13439f8ea46SGeert Uytterhoeven mutex_lock(&priv->bl_tempo_lock); 13539f8ea46SGeert Uytterhoeven if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L)) 13639f8ea46SGeert Uytterhoeven lcd->ops->backlight(lcd, 1); 13739f8ea46SGeert Uytterhoeven priv->bl_tempo = true; 13839f8ea46SGeert Uytterhoeven schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ); 13939f8ea46SGeert Uytterhoeven mutex_unlock(&priv->bl_tempo_lock); 14039f8ea46SGeert Uytterhoeven } 14139f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_poke); 14239f8ea46SGeert Uytterhoeven 14339f8ea46SGeert Uytterhoeven static void charlcd_home(struct charlcd *lcd) 14439f8ea46SGeert Uytterhoeven { 14511588b59SLars Poeschel lcd->addr.x = 0; 14611588b59SLars Poeschel lcd->addr.y = 0; 147*88645a86SLars Poeschel lcd->ops->home(lcd); 14839f8ea46SGeert Uytterhoeven } 14939f8ea46SGeert Uytterhoeven 15039f8ea46SGeert Uytterhoeven static void charlcd_print(struct charlcd *lcd, char c) 15139f8ea46SGeert Uytterhoeven { 152d3a2fb81SLars Poeschel struct hd44780_common *hdc = lcd->drvdata; 153d3a2fb81SLars Poeschel 15439f8ea46SGeert Uytterhoeven if (lcd->char_conv) 15539f8ea46SGeert Uytterhoeven c = lcd->char_conv[(unsigned char)c]; 156b26deabbSLars Poeschel 157b26deabbSLars Poeschel if (!lcd->ops->print(lcd, c)) 15811588b59SLars Poeschel lcd->addr.x++; 15954bc937fSSean Young 16039f8ea46SGeert Uytterhoeven /* prevents the cursor from wrapping onto the next line */ 161d3a2fb81SLars Poeschel if (lcd->addr.x == hdc->bwidth) 162d3a2fb81SLars Poeschel lcd->ops->gotoxy(lcd); 16339f8ea46SGeert Uytterhoeven } 16439f8ea46SGeert Uytterhoeven 16539f8ea46SGeert Uytterhoeven static void charlcd_clear_fast(struct charlcd *lcd) 16639f8ea46SGeert Uytterhoeven { 1672545c1c9SLars Poeschel struct hd44780_common *hdc = lcd->drvdata; 16839f8ea46SGeert Uytterhoeven int pos; 16939f8ea46SGeert Uytterhoeven 17039f8ea46SGeert Uytterhoeven charlcd_home(lcd); 17139f8ea46SGeert Uytterhoeven 17239f8ea46SGeert Uytterhoeven if (lcd->ops->clear_fast) 17339f8ea46SGeert Uytterhoeven lcd->ops->clear_fast(lcd); 17439f8ea46SGeert Uytterhoeven else 1752545c1c9SLars Poeschel for (pos = 0; pos < min(2, lcd->height) * hdc->hwidth; pos++) 176b26deabbSLars Poeschel lcd->ops->print(lcd, ' '); 17739f8ea46SGeert Uytterhoeven 17839f8ea46SGeert Uytterhoeven charlcd_home(lcd); 17939f8ea46SGeert Uytterhoeven } 18039f8ea46SGeert Uytterhoeven 18139f8ea46SGeert Uytterhoeven /* clears the display and resets X/Y */ 18239f8ea46SGeert Uytterhoeven static void charlcd_clear_display(struct charlcd *lcd) 18339f8ea46SGeert Uytterhoeven { 1842c6a82f2SLars Poeschel struct hd44780_common *hdc = lcd->drvdata; 18539f8ea46SGeert Uytterhoeven 1862c6a82f2SLars Poeschel hdc->write_cmd(hdc, LCD_CMD_DISPLAY_CLEAR); 18711588b59SLars Poeschel lcd->addr.x = 0; 18811588b59SLars Poeschel lcd->addr.y = 0; 18939f8ea46SGeert Uytterhoeven /* we must wait a few milliseconds (15) */ 19039f8ea46SGeert Uytterhoeven long_sleep(15); 19139f8ea46SGeert Uytterhoeven } 19239f8ea46SGeert Uytterhoeven 19339f8ea46SGeert Uytterhoeven static int charlcd_init_display(struct charlcd *lcd) 19439f8ea46SGeert Uytterhoeven { 1952c6a82f2SLars Poeschel void (*write_cmd_raw)(struct hd44780_common *hdc, int cmd); 196b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 1973fc04dd7SLars Poeschel struct hd44780_common *hdc = lcd->drvdata; 198ac201479SGeert Uytterhoeven u8 init; 199ac201479SGeert Uytterhoeven 2003fc04dd7SLars Poeschel if (hdc->ifwidth != 4 && hdc->ifwidth != 8) 201ac201479SGeert Uytterhoeven return -EINVAL; 20239f8ea46SGeert Uytterhoeven 20339f8ea46SGeert Uytterhoeven priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | 20439f8ea46SGeert Uytterhoeven LCD_FLAG_C | LCD_FLAG_B; 20539f8ea46SGeert Uytterhoeven 20639f8ea46SGeert Uytterhoeven long_sleep(20); /* wait 20 ms after power-up for the paranoid */ 20739f8ea46SGeert Uytterhoeven 208ac201479SGeert Uytterhoeven /* 209ac201479SGeert Uytterhoeven * 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure 210ac201479SGeert Uytterhoeven * the LCD is in 8-bit mode afterwards 211ac201479SGeert Uytterhoeven */ 212ac201479SGeert Uytterhoeven init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS; 2133fc04dd7SLars Poeschel if (hdc->ifwidth == 4) { 214ac201479SGeert Uytterhoeven init >>= 4; 2152c6a82f2SLars Poeschel write_cmd_raw = hdc->write_cmd_raw4; 216ac201479SGeert Uytterhoeven } else { 2172c6a82f2SLars Poeschel write_cmd_raw = hdc->write_cmd; 218ac201479SGeert Uytterhoeven } 2192c6a82f2SLars Poeschel write_cmd_raw(hdc, init); 22039f8ea46SGeert Uytterhoeven long_sleep(10); 2212c6a82f2SLars Poeschel write_cmd_raw(hdc, init); 22239f8ea46SGeert Uytterhoeven long_sleep(10); 2232c6a82f2SLars Poeschel write_cmd_raw(hdc, init); 22439f8ea46SGeert Uytterhoeven long_sleep(10); 22539f8ea46SGeert Uytterhoeven 2263fc04dd7SLars Poeschel if (hdc->ifwidth == 4) { 227ac201479SGeert Uytterhoeven /* Switch to 4-bit mode, 1 line, small fonts */ 2282c6a82f2SLars Poeschel hdc->write_cmd_raw4(hdc, LCD_CMD_FUNCTION_SET >> 4); 229ac201479SGeert Uytterhoeven long_sleep(10); 230ac201479SGeert Uytterhoeven } 231ac201479SGeert Uytterhoeven 23239f8ea46SGeert Uytterhoeven /* set font height and lines number */ 2332c6a82f2SLars Poeschel hdc->write_cmd(hdc, 234ac201479SGeert Uytterhoeven LCD_CMD_FUNCTION_SET | 2353fc04dd7SLars Poeschel ((hdc->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | 23639f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | 23739f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); 23839f8ea46SGeert Uytterhoeven long_sleep(10); 23939f8ea46SGeert Uytterhoeven 24039f8ea46SGeert Uytterhoeven /* display off, cursor off, blink off */ 2412c6a82f2SLars Poeschel hdc->write_cmd(hdc, LCD_CMD_DISPLAY_CTRL); 24239f8ea46SGeert Uytterhoeven long_sleep(10); 24339f8ea46SGeert Uytterhoeven 2442c6a82f2SLars Poeschel hdc->write_cmd(hdc, 24539f8ea46SGeert Uytterhoeven LCD_CMD_DISPLAY_CTRL | /* set display mode */ 24639f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | 24739f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | 24839f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); 24939f8ea46SGeert Uytterhoeven 25039f8ea46SGeert Uytterhoeven charlcd_backlight(lcd, (priv->flags & LCD_FLAG_L) ? 1 : 0); 25139f8ea46SGeert Uytterhoeven 25239f8ea46SGeert Uytterhoeven long_sleep(10); 25339f8ea46SGeert Uytterhoeven 25439f8ea46SGeert Uytterhoeven /* entry mode set : increment, cursor shifting */ 2552c6a82f2SLars Poeschel hdc->write_cmd(hdc, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC); 25639f8ea46SGeert Uytterhoeven 25739f8ea46SGeert Uytterhoeven charlcd_clear_display(lcd); 25839f8ea46SGeert Uytterhoeven return 0; 25939f8ea46SGeert Uytterhoeven } 26039f8ea46SGeert Uytterhoeven 26139f8ea46SGeert Uytterhoeven /* 262b34050faSMiguel Ojeda * Parses a movement command of the form "(.*);", where the group can be 263b34050faSMiguel Ojeda * any number of subcommands of the form "(x|y)[0-9]+". 264b34050faSMiguel Ojeda * 265b34050faSMiguel Ojeda * Returns whether the command is valid. The position arguments are 266b34050faSMiguel Ojeda * only written if the parsing was successful. 267b34050faSMiguel Ojeda * 268b34050faSMiguel Ojeda * For instance: 269b34050faSMiguel Ojeda * - ";" returns (<original x>, <original y>). 270b34050faSMiguel Ojeda * - "x1;" returns (1, <original y>). 271b34050faSMiguel Ojeda * - "y2x1;" returns (1, 2). 272b34050faSMiguel Ojeda * - "x12y34x56;" returns (56, 34). 273b34050faSMiguel Ojeda * - "" fails. 274b34050faSMiguel Ojeda * - "x" fails. 275b34050faSMiguel Ojeda * - "x;" fails. 276b34050faSMiguel Ojeda * - "x1" fails. 277b34050faSMiguel Ojeda * - "xy12;" fails. 278b34050faSMiguel Ojeda * - "x12yy12;" fails. 279b34050faSMiguel Ojeda * - "xx" fails. 280b34050faSMiguel Ojeda */ 281b34050faSMiguel Ojeda static bool parse_xy(const char *s, unsigned long *x, unsigned long *y) 282b34050faSMiguel Ojeda { 283b34050faSMiguel Ojeda unsigned long new_x = *x; 284b34050faSMiguel Ojeda unsigned long new_y = *y; 285d717e7daSAndy Shevchenko char *p; 286b34050faSMiguel Ojeda 287b34050faSMiguel Ojeda for (;;) { 288b34050faSMiguel Ojeda if (!*s) 289b34050faSMiguel Ojeda return false; 290b34050faSMiguel Ojeda 291b34050faSMiguel Ojeda if (*s == ';') 292b34050faSMiguel Ojeda break; 293b34050faSMiguel Ojeda 294b34050faSMiguel Ojeda if (*s == 'x') { 295d717e7daSAndy Shevchenko new_x = simple_strtoul(s + 1, &p, 10); 296d717e7daSAndy Shevchenko if (p == s + 1) 297b34050faSMiguel Ojeda return false; 298d717e7daSAndy Shevchenko s = p; 299b34050faSMiguel Ojeda } else if (*s == 'y') { 300d717e7daSAndy Shevchenko new_y = simple_strtoul(s + 1, &p, 10); 301d717e7daSAndy Shevchenko if (p == s + 1) 302b34050faSMiguel Ojeda return false; 303d717e7daSAndy Shevchenko s = p; 304b34050faSMiguel Ojeda } else { 305b34050faSMiguel Ojeda return false; 306b34050faSMiguel Ojeda } 307b34050faSMiguel Ojeda } 308b34050faSMiguel Ojeda 309b34050faSMiguel Ojeda *x = new_x; 310b34050faSMiguel Ojeda *y = new_y; 311b34050faSMiguel Ojeda return true; 312b34050faSMiguel Ojeda } 313b34050faSMiguel Ojeda 314b34050faSMiguel Ojeda /* 31539f8ea46SGeert Uytterhoeven * These are the file operation function for user access to /dev/lcd 31639f8ea46SGeert Uytterhoeven * This function can also be called from inside the kernel, by 31739f8ea46SGeert Uytterhoeven * setting file and ppos to NULL. 31839f8ea46SGeert Uytterhoeven * 31939f8ea46SGeert Uytterhoeven */ 32039f8ea46SGeert Uytterhoeven 32139f8ea46SGeert Uytterhoeven static inline int handle_lcd_special_code(struct charlcd *lcd) 32239f8ea46SGeert Uytterhoeven { 323b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 3242545c1c9SLars Poeschel struct hd44780_common *hdc = lcd->drvdata; 32539f8ea46SGeert Uytterhoeven 32639f8ea46SGeert Uytterhoeven /* LCD special codes */ 32739f8ea46SGeert Uytterhoeven 32839f8ea46SGeert Uytterhoeven int processed = 0; 32939f8ea46SGeert Uytterhoeven 33039f8ea46SGeert Uytterhoeven char *esc = priv->esc_seq.buf + 2; 33139f8ea46SGeert Uytterhoeven int oldflags = priv->flags; 33239f8ea46SGeert Uytterhoeven 33339f8ea46SGeert Uytterhoeven /* check for display mode flags */ 33439f8ea46SGeert Uytterhoeven switch (*esc) { 33539f8ea46SGeert Uytterhoeven case 'D': /* Display ON */ 33639f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_D; 33739f8ea46SGeert Uytterhoeven processed = 1; 33839f8ea46SGeert Uytterhoeven break; 33939f8ea46SGeert Uytterhoeven case 'd': /* Display OFF */ 34039f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_D; 34139f8ea46SGeert Uytterhoeven processed = 1; 34239f8ea46SGeert Uytterhoeven break; 34339f8ea46SGeert Uytterhoeven case 'C': /* Cursor ON */ 34439f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_C; 34539f8ea46SGeert Uytterhoeven processed = 1; 34639f8ea46SGeert Uytterhoeven break; 34739f8ea46SGeert Uytterhoeven case 'c': /* Cursor OFF */ 34839f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_C; 34939f8ea46SGeert Uytterhoeven processed = 1; 35039f8ea46SGeert Uytterhoeven break; 35139f8ea46SGeert Uytterhoeven case 'B': /* Blink ON */ 35239f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_B; 35339f8ea46SGeert Uytterhoeven processed = 1; 35439f8ea46SGeert Uytterhoeven break; 35539f8ea46SGeert Uytterhoeven case 'b': /* Blink OFF */ 35639f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_B; 35739f8ea46SGeert Uytterhoeven processed = 1; 35839f8ea46SGeert Uytterhoeven break; 35939f8ea46SGeert Uytterhoeven case '+': /* Back light ON */ 36039f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_L; 36139f8ea46SGeert Uytterhoeven processed = 1; 36239f8ea46SGeert Uytterhoeven break; 36339f8ea46SGeert Uytterhoeven case '-': /* Back light OFF */ 36439f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_L; 36539f8ea46SGeert Uytterhoeven processed = 1; 36639f8ea46SGeert Uytterhoeven break; 36739f8ea46SGeert Uytterhoeven case '*': /* Flash back light */ 36839f8ea46SGeert Uytterhoeven charlcd_poke(lcd); 36939f8ea46SGeert Uytterhoeven processed = 1; 37039f8ea46SGeert Uytterhoeven break; 37139f8ea46SGeert Uytterhoeven case 'f': /* Small Font */ 37239f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_F; 37339f8ea46SGeert Uytterhoeven processed = 1; 37439f8ea46SGeert Uytterhoeven break; 37539f8ea46SGeert Uytterhoeven case 'F': /* Large Font */ 37639f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_F; 37739f8ea46SGeert Uytterhoeven processed = 1; 37839f8ea46SGeert Uytterhoeven break; 37939f8ea46SGeert Uytterhoeven case 'n': /* One Line */ 38039f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_N; 38139f8ea46SGeert Uytterhoeven processed = 1; 38239f8ea46SGeert Uytterhoeven break; 38339f8ea46SGeert Uytterhoeven case 'N': /* Two Lines */ 38439f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_N; 38599b9b490SRobert Abel processed = 1; 38639f8ea46SGeert Uytterhoeven break; 38739f8ea46SGeert Uytterhoeven case 'l': /* Shift Cursor Left */ 38811588b59SLars Poeschel if (lcd->addr.x > 0) { 38939f8ea46SGeert Uytterhoeven /* back one char if not at end of line */ 39011588b59SLars Poeschel if (lcd->addr.x < hdc->bwidth) 3912c6a82f2SLars Poeschel hdc->write_cmd(hdc, LCD_CMD_SHIFT); 39211588b59SLars Poeschel lcd->addr.x--; 39339f8ea46SGeert Uytterhoeven } 39439f8ea46SGeert Uytterhoeven processed = 1; 39539f8ea46SGeert Uytterhoeven break; 39639f8ea46SGeert Uytterhoeven case 'r': /* shift cursor right */ 39711588b59SLars Poeschel if (lcd->addr.x < lcd->width) { 39839f8ea46SGeert Uytterhoeven /* allow the cursor to pass the end of the line */ 39911588b59SLars Poeschel if (lcd->addr.x < (hdc->bwidth - 1)) 4002c6a82f2SLars Poeschel hdc->write_cmd(hdc, 40139f8ea46SGeert Uytterhoeven LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT); 40211588b59SLars Poeschel lcd->addr.x++; 40339f8ea46SGeert Uytterhoeven } 40439f8ea46SGeert Uytterhoeven processed = 1; 40539f8ea46SGeert Uytterhoeven break; 40639f8ea46SGeert Uytterhoeven case 'L': /* shift display left */ 4072c6a82f2SLars Poeschel hdc->write_cmd(hdc, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT); 40839f8ea46SGeert Uytterhoeven processed = 1; 40939f8ea46SGeert Uytterhoeven break; 41039f8ea46SGeert Uytterhoeven case 'R': /* shift display right */ 4112c6a82f2SLars Poeschel hdc->write_cmd(hdc, 41239f8ea46SGeert Uytterhoeven LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT | 41339f8ea46SGeert Uytterhoeven LCD_CMD_SHIFT_RIGHT); 41439f8ea46SGeert Uytterhoeven processed = 1; 41539f8ea46SGeert Uytterhoeven break; 41639f8ea46SGeert Uytterhoeven case 'k': { /* kill end of line */ 417b26deabbSLars Poeschel int x, xs, ys; 41839f8ea46SGeert Uytterhoeven 419b26deabbSLars Poeschel xs = lcd->addr.x; 420b26deabbSLars Poeschel ys = lcd->addr.y; 42111588b59SLars Poeschel for (x = lcd->addr.x; x < hdc->bwidth; x++) 422b26deabbSLars Poeschel lcd->ops->print(lcd, ' '); 42339f8ea46SGeert Uytterhoeven 42439f8ea46SGeert Uytterhoeven /* restore cursor position */ 425b26deabbSLars Poeschel lcd->addr.x = xs; 426b26deabbSLars Poeschel lcd->addr.y = ys; 427d3a2fb81SLars Poeschel lcd->ops->gotoxy(lcd); 42839f8ea46SGeert Uytterhoeven processed = 1; 42939f8ea46SGeert Uytterhoeven break; 43039f8ea46SGeert Uytterhoeven } 43139f8ea46SGeert Uytterhoeven case 'I': /* reinitialize display */ 43239f8ea46SGeert Uytterhoeven charlcd_init_display(lcd); 43339f8ea46SGeert Uytterhoeven processed = 1; 43439f8ea46SGeert Uytterhoeven break; 43539f8ea46SGeert Uytterhoeven case 'G': { 43639f8ea46SGeert Uytterhoeven /* Generator : LGcxxxxx...xx; must have <c> between '0' 43739f8ea46SGeert Uytterhoeven * and '7', representing the numerical ASCII code of the 43839f8ea46SGeert Uytterhoeven * redefined character, and <xx...xx> a sequence of 16 43939f8ea46SGeert Uytterhoeven * hex digits representing 8 bytes for each character. 44039f8ea46SGeert Uytterhoeven * Most LCDs will only use 5 lower bits of the 7 first 44139f8ea46SGeert Uytterhoeven * bytes. 44239f8ea46SGeert Uytterhoeven */ 44339f8ea46SGeert Uytterhoeven 44439f8ea46SGeert Uytterhoeven unsigned char cgbytes[8]; 44539f8ea46SGeert Uytterhoeven unsigned char cgaddr; 44639f8ea46SGeert Uytterhoeven int cgoffset; 44739f8ea46SGeert Uytterhoeven int shift; 44839f8ea46SGeert Uytterhoeven char value; 44939f8ea46SGeert Uytterhoeven int addr; 45039f8ea46SGeert Uytterhoeven 45139f8ea46SGeert Uytterhoeven if (!strchr(esc, ';')) 45239f8ea46SGeert Uytterhoeven break; 45339f8ea46SGeert Uytterhoeven 45439f8ea46SGeert Uytterhoeven esc++; 45539f8ea46SGeert Uytterhoeven 45639f8ea46SGeert Uytterhoeven cgaddr = *(esc++) - '0'; 45739f8ea46SGeert Uytterhoeven if (cgaddr > 7) { 45839f8ea46SGeert Uytterhoeven processed = 1; 45939f8ea46SGeert Uytterhoeven break; 46039f8ea46SGeert Uytterhoeven } 46139f8ea46SGeert Uytterhoeven 46239f8ea46SGeert Uytterhoeven cgoffset = 0; 46339f8ea46SGeert Uytterhoeven shift = 0; 46439f8ea46SGeert Uytterhoeven value = 0; 46539f8ea46SGeert Uytterhoeven while (*esc && cgoffset < 8) { 4663f03b649SAndy Shevchenko int half; 46739f8ea46SGeert Uytterhoeven 4683f03b649SAndy Shevchenko shift ^= 4; 4693f03b649SAndy Shevchenko 4703f03b649SAndy Shevchenko half = hex_to_bin(*esc++); 4713f03b649SAndy Shevchenko if (half < 0) 4723f03b649SAndy Shevchenko continue; 4733f03b649SAndy Shevchenko 4743f03b649SAndy Shevchenko value |= half << shift; 47539f8ea46SGeert Uytterhoeven if (shift == 0) { 47639f8ea46SGeert Uytterhoeven cgbytes[cgoffset++] = value; 47739f8ea46SGeert Uytterhoeven value = 0; 47839f8ea46SGeert Uytterhoeven } 47939f8ea46SGeert Uytterhoeven } 48039f8ea46SGeert Uytterhoeven 4812c6a82f2SLars Poeschel hdc->write_cmd(hdc, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8)); 48239f8ea46SGeert Uytterhoeven for (addr = 0; addr < cgoffset; addr++) 48371ff701bSLars Poeschel hdc->write_data(hdc, cgbytes[addr]); 48439f8ea46SGeert Uytterhoeven 48539f8ea46SGeert Uytterhoeven /* ensures that we stop writing to CGRAM */ 486d3a2fb81SLars Poeschel lcd->ops->gotoxy(lcd); 48739f8ea46SGeert Uytterhoeven processed = 1; 48839f8ea46SGeert Uytterhoeven break; 48939f8ea46SGeert Uytterhoeven } 49039f8ea46SGeert Uytterhoeven case 'x': /* gotoxy : LxXXX[yYYY]; */ 49139f8ea46SGeert Uytterhoeven case 'y': /* gotoxy : LyYYY[xXXX]; */ 4929bc30ab8SMans Rullgard if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';') 4939bc30ab8SMans Rullgard break; 4949bc30ab8SMans Rullgard 495b34050faSMiguel Ojeda /* If the command is valid, move to the new address */ 49611588b59SLars Poeschel if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y)) 497d3a2fb81SLars Poeschel lcd->ops->gotoxy(lcd); 498b34050faSMiguel Ojeda 499b34050faSMiguel Ojeda /* Regardless of its validity, mark as processed */ 50039f8ea46SGeert Uytterhoeven processed = 1; 50139f8ea46SGeert Uytterhoeven break; 50239f8ea46SGeert Uytterhoeven } 50339f8ea46SGeert Uytterhoeven 50439f8ea46SGeert Uytterhoeven /* TODO: This indent party here got ugly, clean it! */ 50539f8ea46SGeert Uytterhoeven /* Check whether one flag was changed */ 50639f8ea46SGeert Uytterhoeven if (oldflags == priv->flags) 50739f8ea46SGeert Uytterhoeven return processed; 50839f8ea46SGeert Uytterhoeven 50939f8ea46SGeert Uytterhoeven /* check whether one of B,C,D flags were changed */ 51039f8ea46SGeert Uytterhoeven if ((oldflags ^ priv->flags) & 51139f8ea46SGeert Uytterhoeven (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D)) 51239f8ea46SGeert Uytterhoeven /* set display mode */ 5132c6a82f2SLars Poeschel hdc->write_cmd(hdc, 51439f8ea46SGeert Uytterhoeven LCD_CMD_DISPLAY_CTRL | 51539f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | 51639f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | 51739f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); 51839f8ea46SGeert Uytterhoeven /* check whether one of F,N flags was changed */ 51939f8ea46SGeert Uytterhoeven else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N)) 5202c6a82f2SLars Poeschel hdc->write_cmd(hdc, 521ac201479SGeert Uytterhoeven LCD_CMD_FUNCTION_SET | 5223fc04dd7SLars Poeschel ((hdc->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | 52339f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | 52439f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); 52539f8ea46SGeert Uytterhoeven /* check whether L flag was changed */ 52639f8ea46SGeert Uytterhoeven else if ((oldflags ^ priv->flags) & LCD_FLAG_L) 52739f8ea46SGeert Uytterhoeven charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L)); 52839f8ea46SGeert Uytterhoeven 52939f8ea46SGeert Uytterhoeven return processed; 53039f8ea46SGeert Uytterhoeven } 53139f8ea46SGeert Uytterhoeven 53239f8ea46SGeert Uytterhoeven static void charlcd_write_char(struct charlcd *lcd, char c) 53339f8ea46SGeert Uytterhoeven { 534b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 5352545c1c9SLars Poeschel struct hd44780_common *hdc = lcd->drvdata; 53639f8ea46SGeert Uytterhoeven 53739f8ea46SGeert Uytterhoeven /* first, we'll test if we're in escape mode */ 53839f8ea46SGeert Uytterhoeven if ((c != '\n') && priv->esc_seq.len >= 0) { 53939f8ea46SGeert Uytterhoeven /* yes, let's add this char to the buffer */ 54039f8ea46SGeert Uytterhoeven priv->esc_seq.buf[priv->esc_seq.len++] = c; 5418c483758SRobert Abel priv->esc_seq.buf[priv->esc_seq.len] = '\0'; 54239f8ea46SGeert Uytterhoeven } else { 54339f8ea46SGeert Uytterhoeven /* aborts any previous escape sequence */ 54439f8ea46SGeert Uytterhoeven priv->esc_seq.len = -1; 54539f8ea46SGeert Uytterhoeven 54639f8ea46SGeert Uytterhoeven switch (c) { 54739f8ea46SGeert Uytterhoeven case LCD_ESCAPE_CHAR: 54839f8ea46SGeert Uytterhoeven /* start of an escape sequence */ 54939f8ea46SGeert Uytterhoeven priv->esc_seq.len = 0; 5508c483758SRobert Abel priv->esc_seq.buf[priv->esc_seq.len] = '\0'; 55139f8ea46SGeert Uytterhoeven break; 55239f8ea46SGeert Uytterhoeven case '\b': 55339f8ea46SGeert Uytterhoeven /* go back one char and clear it */ 55411588b59SLars Poeschel if (lcd->addr.x > 0) { 55539f8ea46SGeert Uytterhoeven /* 55639f8ea46SGeert Uytterhoeven * check if we're not at the 55739f8ea46SGeert Uytterhoeven * end of the line 55839f8ea46SGeert Uytterhoeven */ 55911588b59SLars Poeschel if (lcd->addr.x < hdc->bwidth) 56039f8ea46SGeert Uytterhoeven /* back one char */ 5612c6a82f2SLars Poeschel hdc->write_cmd(hdc, LCD_CMD_SHIFT); 56211588b59SLars Poeschel lcd->addr.x--; 56339f8ea46SGeert Uytterhoeven } 56439f8ea46SGeert Uytterhoeven /* replace with a space */ 56571ff701bSLars Poeschel hdc->write_data(hdc, ' '); 56639f8ea46SGeert Uytterhoeven /* back one char again */ 5672c6a82f2SLars Poeschel hdc->write_cmd(hdc, LCD_CMD_SHIFT); 56839f8ea46SGeert Uytterhoeven break; 5699629cccaSRobert Abel case '\f': 57039f8ea46SGeert Uytterhoeven /* quickly clear the display */ 57139f8ea46SGeert Uytterhoeven charlcd_clear_fast(lcd); 57239f8ea46SGeert Uytterhoeven break; 57339f8ea46SGeert Uytterhoeven case '\n': 57439f8ea46SGeert Uytterhoeven /* 57539f8ea46SGeert Uytterhoeven * flush the remainder of the current line and 57639f8ea46SGeert Uytterhoeven * go to the beginning of the next line 57739f8ea46SGeert Uytterhoeven */ 57811588b59SLars Poeschel for (; lcd->addr.x < hdc->bwidth; lcd->addr.x++) 579b26deabbSLars Poeschel lcd->ops->print(lcd, ' '); 580b26deabbSLars Poeschel 58111588b59SLars Poeschel lcd->addr.x = 0; 58211588b59SLars Poeschel lcd->addr.y = (lcd->addr.y + 1) % lcd->height; 583d3a2fb81SLars Poeschel lcd->ops->gotoxy(lcd); 58439f8ea46SGeert Uytterhoeven break; 58539f8ea46SGeert Uytterhoeven case '\r': 58639f8ea46SGeert Uytterhoeven /* go to the beginning of the same line */ 58711588b59SLars Poeschel lcd->addr.x = 0; 588d3a2fb81SLars Poeschel lcd->ops->gotoxy(lcd); 58939f8ea46SGeert Uytterhoeven break; 59039f8ea46SGeert Uytterhoeven case '\t': 59139f8ea46SGeert Uytterhoeven /* print a space instead of the tab */ 59239f8ea46SGeert Uytterhoeven charlcd_print(lcd, ' '); 59339f8ea46SGeert Uytterhoeven break; 59439f8ea46SGeert Uytterhoeven default: 59539f8ea46SGeert Uytterhoeven /* simply print this char */ 59639f8ea46SGeert Uytterhoeven charlcd_print(lcd, c); 59739f8ea46SGeert Uytterhoeven break; 59839f8ea46SGeert Uytterhoeven } 59939f8ea46SGeert Uytterhoeven } 60039f8ea46SGeert Uytterhoeven 60139f8ea46SGeert Uytterhoeven /* 60239f8ea46SGeert Uytterhoeven * now we'll see if we're in an escape mode and if the current 60339f8ea46SGeert Uytterhoeven * escape sequence can be understood. 60439f8ea46SGeert Uytterhoeven */ 60539f8ea46SGeert Uytterhoeven if (priv->esc_seq.len >= 2) { 60639f8ea46SGeert Uytterhoeven int processed = 0; 60739f8ea46SGeert Uytterhoeven 60839f8ea46SGeert Uytterhoeven if (!strcmp(priv->esc_seq.buf, "[2J")) { 60939f8ea46SGeert Uytterhoeven /* clear the display */ 61039f8ea46SGeert Uytterhoeven charlcd_clear_fast(lcd); 61139f8ea46SGeert Uytterhoeven processed = 1; 61239f8ea46SGeert Uytterhoeven } else if (!strcmp(priv->esc_seq.buf, "[H")) { 61339f8ea46SGeert Uytterhoeven /* cursor to home */ 61439f8ea46SGeert Uytterhoeven charlcd_home(lcd); 61539f8ea46SGeert Uytterhoeven processed = 1; 61639f8ea46SGeert Uytterhoeven } 61739f8ea46SGeert Uytterhoeven /* codes starting with ^[[L */ 61839f8ea46SGeert Uytterhoeven else if ((priv->esc_seq.len >= 3) && 61939f8ea46SGeert Uytterhoeven (priv->esc_seq.buf[0] == '[') && 62039f8ea46SGeert Uytterhoeven (priv->esc_seq.buf[1] == 'L')) { 62139f8ea46SGeert Uytterhoeven processed = handle_lcd_special_code(lcd); 62239f8ea46SGeert Uytterhoeven } 62339f8ea46SGeert Uytterhoeven 62439f8ea46SGeert Uytterhoeven /* LCD special escape codes */ 62539f8ea46SGeert Uytterhoeven /* 62639f8ea46SGeert Uytterhoeven * flush the escape sequence if it's been processed 62739f8ea46SGeert Uytterhoeven * or if it is getting too long. 62839f8ea46SGeert Uytterhoeven */ 62939f8ea46SGeert Uytterhoeven if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN)) 63039f8ea46SGeert Uytterhoeven priv->esc_seq.len = -1; 63139f8ea46SGeert Uytterhoeven } /* escape codes */ 63239f8ea46SGeert Uytterhoeven } 63339f8ea46SGeert Uytterhoeven 63439f8ea46SGeert Uytterhoeven static struct charlcd *the_charlcd; 63539f8ea46SGeert Uytterhoeven 63639f8ea46SGeert Uytterhoeven static ssize_t charlcd_write(struct file *file, const char __user *buf, 63739f8ea46SGeert Uytterhoeven size_t count, loff_t *ppos) 63839f8ea46SGeert Uytterhoeven { 63939f8ea46SGeert Uytterhoeven const char __user *tmp = buf; 64039f8ea46SGeert Uytterhoeven char c; 64139f8ea46SGeert Uytterhoeven 64239f8ea46SGeert Uytterhoeven for (; count-- > 0; (*ppos)++, tmp++) { 64339f8ea46SGeert Uytterhoeven if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) 64439f8ea46SGeert Uytterhoeven /* 64539f8ea46SGeert Uytterhoeven * let's be a little nice with other processes 64639f8ea46SGeert Uytterhoeven * that need some CPU 64739f8ea46SGeert Uytterhoeven */ 64839f8ea46SGeert Uytterhoeven schedule(); 64939f8ea46SGeert Uytterhoeven 65039f8ea46SGeert Uytterhoeven if (get_user(c, tmp)) 65139f8ea46SGeert Uytterhoeven return -EFAULT; 65239f8ea46SGeert Uytterhoeven 65339f8ea46SGeert Uytterhoeven charlcd_write_char(the_charlcd, c); 65439f8ea46SGeert Uytterhoeven } 65539f8ea46SGeert Uytterhoeven 65639f8ea46SGeert Uytterhoeven return tmp - buf; 65739f8ea46SGeert Uytterhoeven } 65839f8ea46SGeert Uytterhoeven 65939f8ea46SGeert Uytterhoeven static int charlcd_open(struct inode *inode, struct file *file) 66039f8ea46SGeert Uytterhoeven { 661b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(the_charlcd); 66293dc1774SWilly Tarreau int ret; 66339f8ea46SGeert Uytterhoeven 66493dc1774SWilly Tarreau ret = -EBUSY; 66539f8ea46SGeert Uytterhoeven if (!atomic_dec_and_test(&charlcd_available)) 66693dc1774SWilly Tarreau goto fail; /* open only once at a time */ 66739f8ea46SGeert Uytterhoeven 66893dc1774SWilly Tarreau ret = -EPERM; 66939f8ea46SGeert Uytterhoeven if (file->f_mode & FMODE_READ) /* device is write-only */ 67093dc1774SWilly Tarreau goto fail; 67139f8ea46SGeert Uytterhoeven 67239f8ea46SGeert Uytterhoeven if (priv->must_clear) { 67339f8ea46SGeert Uytterhoeven charlcd_clear_display(&priv->lcd); 67439f8ea46SGeert Uytterhoeven priv->must_clear = false; 67539f8ea46SGeert Uytterhoeven } 67639f8ea46SGeert Uytterhoeven return nonseekable_open(inode, file); 67793dc1774SWilly Tarreau 67893dc1774SWilly Tarreau fail: 67993dc1774SWilly Tarreau atomic_inc(&charlcd_available); 68093dc1774SWilly Tarreau return ret; 68139f8ea46SGeert Uytterhoeven } 68239f8ea46SGeert Uytterhoeven 68339f8ea46SGeert Uytterhoeven static int charlcd_release(struct inode *inode, struct file *file) 68439f8ea46SGeert Uytterhoeven { 68539f8ea46SGeert Uytterhoeven atomic_inc(&charlcd_available); 68639f8ea46SGeert Uytterhoeven return 0; 68739f8ea46SGeert Uytterhoeven } 68839f8ea46SGeert Uytterhoeven 68939f8ea46SGeert Uytterhoeven static const struct file_operations charlcd_fops = { 69039f8ea46SGeert Uytterhoeven .write = charlcd_write, 69139f8ea46SGeert Uytterhoeven .open = charlcd_open, 69239f8ea46SGeert Uytterhoeven .release = charlcd_release, 69339f8ea46SGeert Uytterhoeven .llseek = no_llseek, 69439f8ea46SGeert Uytterhoeven }; 69539f8ea46SGeert Uytterhoeven 69639f8ea46SGeert Uytterhoeven static struct miscdevice charlcd_dev = { 69739f8ea46SGeert Uytterhoeven .minor = LCD_MINOR, 69839f8ea46SGeert Uytterhoeven .name = "lcd", 69939f8ea46SGeert Uytterhoeven .fops = &charlcd_fops, 70039f8ea46SGeert Uytterhoeven }; 70139f8ea46SGeert Uytterhoeven 70239f8ea46SGeert Uytterhoeven static void charlcd_puts(struct charlcd *lcd, const char *s) 70339f8ea46SGeert Uytterhoeven { 70439f8ea46SGeert Uytterhoeven const char *tmp = s; 70539f8ea46SGeert Uytterhoeven int count = strlen(s); 70639f8ea46SGeert Uytterhoeven 70739f8ea46SGeert Uytterhoeven for (; count-- > 0; tmp++) { 70839f8ea46SGeert Uytterhoeven if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) 70939f8ea46SGeert Uytterhoeven /* 71039f8ea46SGeert Uytterhoeven * let's be a little nice with other processes 71139f8ea46SGeert Uytterhoeven * that need some CPU 71239f8ea46SGeert Uytterhoeven */ 71339f8ea46SGeert Uytterhoeven schedule(); 71439f8ea46SGeert Uytterhoeven 71539f8ea46SGeert Uytterhoeven charlcd_write_char(lcd, *tmp); 71639f8ea46SGeert Uytterhoeven } 71739f8ea46SGeert Uytterhoeven } 71839f8ea46SGeert Uytterhoeven 719c9171722SMans Rullgard #ifdef CONFIG_PANEL_BOOT_MESSAGE 720c9171722SMans Rullgard #define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE 721c9171722SMans Rullgard #else 722c9171722SMans Rullgard #define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n" 723c9171722SMans Rullgard #endif 724c9171722SMans Rullgard 725cc5d04d8SMans Rullgard #ifdef CONFIG_CHARLCD_BL_ON 726cc5d04d8SMans Rullgard #define LCD_INIT_BL "\x1b[L+" 727cc5d04d8SMans Rullgard #elif defined(CONFIG_CHARLCD_BL_FLASH) 728cc5d04d8SMans Rullgard #define LCD_INIT_BL "\x1b[L*" 729cc5d04d8SMans Rullgard #else 730cc5d04d8SMans Rullgard #define LCD_INIT_BL "\x1b[L-" 731cc5d04d8SMans Rullgard #endif 732cc5d04d8SMans Rullgard 73339f8ea46SGeert Uytterhoeven /* initialize the LCD driver */ 73439f8ea46SGeert Uytterhoeven static int charlcd_init(struct charlcd *lcd) 73539f8ea46SGeert Uytterhoeven { 736b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 73739f8ea46SGeert Uytterhoeven int ret; 73839f8ea46SGeert Uytterhoeven 73939f8ea46SGeert Uytterhoeven if (lcd->ops->backlight) { 74039f8ea46SGeert Uytterhoeven mutex_init(&priv->bl_tempo_lock); 74139f8ea46SGeert Uytterhoeven INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off); 74239f8ea46SGeert Uytterhoeven } 74339f8ea46SGeert Uytterhoeven 74439f8ea46SGeert Uytterhoeven /* 74539f8ea46SGeert Uytterhoeven * before this line, we must NOT send anything to the display. 74639f8ea46SGeert Uytterhoeven * Since charlcd_init_display() needs to write data, we have to 74739f8ea46SGeert Uytterhoeven * enable mark the LCD initialized just before. 74839f8ea46SGeert Uytterhoeven */ 74939f8ea46SGeert Uytterhoeven ret = charlcd_init_display(lcd); 75039f8ea46SGeert Uytterhoeven if (ret) 75139f8ea46SGeert Uytterhoeven return ret; 75239f8ea46SGeert Uytterhoeven 75339f8ea46SGeert Uytterhoeven /* display a short message */ 754cc5d04d8SMans Rullgard charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT); 755c9171722SMans Rullgard 75639f8ea46SGeert Uytterhoeven /* clear the display on the next device opening */ 75739f8ea46SGeert Uytterhoeven priv->must_clear = true; 75839f8ea46SGeert Uytterhoeven charlcd_home(lcd); 75939f8ea46SGeert Uytterhoeven return 0; 76039f8ea46SGeert Uytterhoeven } 76139f8ea46SGeert Uytterhoeven 7622545c1c9SLars Poeschel struct charlcd *charlcd_alloc(void) 76339f8ea46SGeert Uytterhoeven { 76439f8ea46SGeert Uytterhoeven struct charlcd_priv *priv; 76539f8ea46SGeert Uytterhoeven struct charlcd *lcd; 76639f8ea46SGeert Uytterhoeven 7672545c1c9SLars Poeschel priv = kzalloc(sizeof(*priv), GFP_KERNEL); 76839f8ea46SGeert Uytterhoeven if (!priv) 76939f8ea46SGeert Uytterhoeven return NULL; 77039f8ea46SGeert Uytterhoeven 77139f8ea46SGeert Uytterhoeven priv->esc_seq.len = -1; 77239f8ea46SGeert Uytterhoeven 77339f8ea46SGeert Uytterhoeven lcd = &priv->lcd; 77439f8ea46SGeert Uytterhoeven 77539f8ea46SGeert Uytterhoeven return lcd; 77639f8ea46SGeert Uytterhoeven } 77739f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_alloc); 77839f8ea46SGeert Uytterhoeven 7798e44fc85SAndy Shevchenko void charlcd_free(struct charlcd *lcd) 7808e44fc85SAndy Shevchenko { 7818e44fc85SAndy Shevchenko kfree(charlcd_to_priv(lcd)); 7828e44fc85SAndy Shevchenko } 7838e44fc85SAndy Shevchenko EXPORT_SYMBOL_GPL(charlcd_free); 7848e44fc85SAndy Shevchenko 78539f8ea46SGeert Uytterhoeven static int panel_notify_sys(struct notifier_block *this, unsigned long code, 78639f8ea46SGeert Uytterhoeven void *unused) 78739f8ea46SGeert Uytterhoeven { 78839f8ea46SGeert Uytterhoeven struct charlcd *lcd = the_charlcd; 78939f8ea46SGeert Uytterhoeven 79039f8ea46SGeert Uytterhoeven switch (code) { 79139f8ea46SGeert Uytterhoeven case SYS_DOWN: 79239f8ea46SGeert Uytterhoeven charlcd_puts(lcd, 79339f8ea46SGeert Uytterhoeven "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); 79439f8ea46SGeert Uytterhoeven break; 79539f8ea46SGeert Uytterhoeven case SYS_HALT: 79639f8ea46SGeert Uytterhoeven charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); 79739f8ea46SGeert Uytterhoeven break; 79839f8ea46SGeert Uytterhoeven case SYS_POWER_OFF: 79939f8ea46SGeert Uytterhoeven charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); 80039f8ea46SGeert Uytterhoeven break; 80139f8ea46SGeert Uytterhoeven default: 80239f8ea46SGeert Uytterhoeven break; 80339f8ea46SGeert Uytterhoeven } 80439f8ea46SGeert Uytterhoeven return NOTIFY_DONE; 80539f8ea46SGeert Uytterhoeven } 80639f8ea46SGeert Uytterhoeven 80739f8ea46SGeert Uytterhoeven static struct notifier_block panel_notifier = { 80839f8ea46SGeert Uytterhoeven panel_notify_sys, 80939f8ea46SGeert Uytterhoeven NULL, 81039f8ea46SGeert Uytterhoeven 0 81139f8ea46SGeert Uytterhoeven }; 81239f8ea46SGeert Uytterhoeven 81339f8ea46SGeert Uytterhoeven int charlcd_register(struct charlcd *lcd) 81439f8ea46SGeert Uytterhoeven { 81539f8ea46SGeert Uytterhoeven int ret; 81639f8ea46SGeert Uytterhoeven 81739f8ea46SGeert Uytterhoeven ret = charlcd_init(lcd); 81839f8ea46SGeert Uytterhoeven if (ret) 81939f8ea46SGeert Uytterhoeven return ret; 82039f8ea46SGeert Uytterhoeven 82139f8ea46SGeert Uytterhoeven ret = misc_register(&charlcd_dev); 82239f8ea46SGeert Uytterhoeven if (ret) 82339f8ea46SGeert Uytterhoeven return ret; 82439f8ea46SGeert Uytterhoeven 82539f8ea46SGeert Uytterhoeven the_charlcd = lcd; 82639f8ea46SGeert Uytterhoeven register_reboot_notifier(&panel_notifier); 82739f8ea46SGeert Uytterhoeven return 0; 82839f8ea46SGeert Uytterhoeven } 82939f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_register); 83039f8ea46SGeert Uytterhoeven 83139f8ea46SGeert Uytterhoeven int charlcd_unregister(struct charlcd *lcd) 83239f8ea46SGeert Uytterhoeven { 833b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 83439f8ea46SGeert Uytterhoeven 83539f8ea46SGeert Uytterhoeven unregister_reboot_notifier(&panel_notifier); 83639f8ea46SGeert Uytterhoeven charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-"); 83739f8ea46SGeert Uytterhoeven misc_deregister(&charlcd_dev); 83839f8ea46SGeert Uytterhoeven the_charlcd = NULL; 83939f8ea46SGeert Uytterhoeven if (lcd->ops->backlight) { 84039f8ea46SGeert Uytterhoeven cancel_delayed_work_sync(&priv->bl_work); 84139f8ea46SGeert Uytterhoeven priv->lcd.ops->backlight(&priv->lcd, 0); 84239f8ea46SGeert Uytterhoeven } 84339f8ea46SGeert Uytterhoeven 84439f8ea46SGeert Uytterhoeven return 0; 84539f8ea46SGeert Uytterhoeven } 84639f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_unregister); 84739f8ea46SGeert Uytterhoeven 84839f8ea46SGeert Uytterhoeven MODULE_LICENSE("GPL"); 849