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" 2439f8ea46SGeert Uytterhoeven 2539f8ea46SGeert Uytterhoeven #define LCD_MINOR 156 2639f8ea46SGeert Uytterhoeven 2739f8ea46SGeert Uytterhoeven #define DEFAULT_LCD_BWIDTH 40 2839f8ea46SGeert Uytterhoeven #define DEFAULT_LCD_HWIDTH 64 2939f8ea46SGeert Uytterhoeven 3039f8ea46SGeert Uytterhoeven /* Keep the backlight on this many seconds for each flash */ 3139f8ea46SGeert Uytterhoeven #define LCD_BL_TEMPO_PERIOD 4 3239f8ea46SGeert Uytterhoeven 3339f8ea46SGeert Uytterhoeven #define LCD_FLAG_B 0x0004 /* Blink on */ 3439f8ea46SGeert Uytterhoeven #define LCD_FLAG_C 0x0008 /* Cursor on */ 3539f8ea46SGeert Uytterhoeven #define LCD_FLAG_D 0x0010 /* Display on */ 3639f8ea46SGeert Uytterhoeven #define LCD_FLAG_F 0x0020 /* Large font mode */ 3739f8ea46SGeert Uytterhoeven #define LCD_FLAG_N 0x0040 /* 2-rows mode */ 3839f8ea46SGeert Uytterhoeven #define LCD_FLAG_L 0x0080 /* Backlight enabled */ 3939f8ea46SGeert Uytterhoeven 4039f8ea46SGeert Uytterhoeven /* LCD commands */ 4139f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */ 4239f8ea46SGeert Uytterhoeven 4339f8ea46SGeert Uytterhoeven #define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */ 4439f8ea46SGeert Uytterhoeven #define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */ 4539f8ea46SGeert Uytterhoeven 4639f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */ 4739f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */ 4839f8ea46SGeert Uytterhoeven #define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */ 4939f8ea46SGeert Uytterhoeven #define LCD_CMD_BLINK_ON 0x01 /* Set blink on */ 5039f8ea46SGeert Uytterhoeven 5139f8ea46SGeert Uytterhoeven #define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */ 5239f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */ 5339f8ea46SGeert Uytterhoeven #define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */ 5439f8ea46SGeert Uytterhoeven 5539f8ea46SGeert Uytterhoeven #define LCD_CMD_FUNCTION_SET 0x20 /* Set function */ 5639f8ea46SGeert Uytterhoeven #define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */ 5739f8ea46SGeert Uytterhoeven #define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */ 5839f8ea46SGeert Uytterhoeven #define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */ 5939f8ea46SGeert Uytterhoeven 6039f8ea46SGeert Uytterhoeven #define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */ 6139f8ea46SGeert Uytterhoeven 6239f8ea46SGeert Uytterhoeven #define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */ 6339f8ea46SGeert Uytterhoeven 6439f8ea46SGeert Uytterhoeven #define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */ 6539f8ea46SGeert Uytterhoeven #define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */ 6639f8ea46SGeert Uytterhoeven 6739f8ea46SGeert Uytterhoeven struct charlcd_priv { 6839f8ea46SGeert Uytterhoeven struct charlcd lcd; 6939f8ea46SGeert Uytterhoeven 7039f8ea46SGeert Uytterhoeven struct delayed_work bl_work; 7139f8ea46SGeert Uytterhoeven struct mutex bl_tempo_lock; /* Protects access to bl_tempo */ 7239f8ea46SGeert Uytterhoeven bool bl_tempo; 7339f8ea46SGeert Uytterhoeven 7439f8ea46SGeert Uytterhoeven bool must_clear; 7539f8ea46SGeert Uytterhoeven 7639f8ea46SGeert Uytterhoeven /* contains the LCD config state */ 7739f8ea46SGeert Uytterhoeven unsigned long int flags; 7839f8ea46SGeert Uytterhoeven 7939f8ea46SGeert Uytterhoeven /* Contains the LCD X and Y offset */ 8039f8ea46SGeert Uytterhoeven struct { 8139f8ea46SGeert Uytterhoeven unsigned long int x; 8239f8ea46SGeert Uytterhoeven unsigned long int y; 8339f8ea46SGeert Uytterhoeven } addr; 8439f8ea46SGeert Uytterhoeven 8539f8ea46SGeert Uytterhoeven /* Current escape sequence and it's length or -1 if outside */ 8639f8ea46SGeert Uytterhoeven struct { 8739f8ea46SGeert Uytterhoeven char buf[LCD_ESCAPE_LEN + 1]; 8839f8ea46SGeert Uytterhoeven int len; 8939f8ea46SGeert Uytterhoeven } esc_seq; 9039f8ea46SGeert Uytterhoeven 9139f8ea46SGeert Uytterhoeven unsigned long long drvdata[0]; 9239f8ea46SGeert Uytterhoeven }; 9339f8ea46SGeert Uytterhoeven 94b658a211SAndy Shevchenko #define charlcd_to_priv(p) container_of(p, struct charlcd_priv, lcd) 9539f8ea46SGeert Uytterhoeven 9639f8ea46SGeert Uytterhoeven /* Device single-open policy control */ 9739f8ea46SGeert Uytterhoeven static atomic_t charlcd_available = ATOMIC_INIT(1); 9839f8ea46SGeert Uytterhoeven 9939f8ea46SGeert Uytterhoeven /* sleeps that many milliseconds with a reschedule */ 10039f8ea46SGeert Uytterhoeven static void long_sleep(int ms) 10139f8ea46SGeert Uytterhoeven { 10239f8ea46SGeert Uytterhoeven schedule_timeout_interruptible(msecs_to_jiffies(ms)); 10339f8ea46SGeert Uytterhoeven } 10439f8ea46SGeert Uytterhoeven 10539f8ea46SGeert Uytterhoeven /* turn the backlight on or off */ 10639f8ea46SGeert Uytterhoeven static void charlcd_backlight(struct charlcd *lcd, int on) 10739f8ea46SGeert Uytterhoeven { 108b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 10939f8ea46SGeert Uytterhoeven 11039f8ea46SGeert Uytterhoeven if (!lcd->ops->backlight) 11139f8ea46SGeert Uytterhoeven return; 11239f8ea46SGeert Uytterhoeven 11339f8ea46SGeert Uytterhoeven mutex_lock(&priv->bl_tempo_lock); 11439f8ea46SGeert Uytterhoeven if (!priv->bl_tempo) 11539f8ea46SGeert Uytterhoeven lcd->ops->backlight(lcd, on); 11639f8ea46SGeert Uytterhoeven mutex_unlock(&priv->bl_tempo_lock); 11739f8ea46SGeert Uytterhoeven } 11839f8ea46SGeert Uytterhoeven 11939f8ea46SGeert Uytterhoeven static void charlcd_bl_off(struct work_struct *work) 12039f8ea46SGeert Uytterhoeven { 12139f8ea46SGeert Uytterhoeven struct delayed_work *dwork = to_delayed_work(work); 12239f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = 12339f8ea46SGeert Uytterhoeven container_of(dwork, struct charlcd_priv, bl_work); 12439f8ea46SGeert Uytterhoeven 12539f8ea46SGeert Uytterhoeven mutex_lock(&priv->bl_tempo_lock); 12639f8ea46SGeert Uytterhoeven if (priv->bl_tempo) { 12739f8ea46SGeert Uytterhoeven priv->bl_tempo = false; 12839f8ea46SGeert Uytterhoeven if (!(priv->flags & LCD_FLAG_L)) 12939f8ea46SGeert Uytterhoeven priv->lcd.ops->backlight(&priv->lcd, 0); 13039f8ea46SGeert Uytterhoeven } 13139f8ea46SGeert Uytterhoeven mutex_unlock(&priv->bl_tempo_lock); 13239f8ea46SGeert Uytterhoeven } 13339f8ea46SGeert Uytterhoeven 13439f8ea46SGeert Uytterhoeven /* turn the backlight on for a little while */ 13539f8ea46SGeert Uytterhoeven void charlcd_poke(struct charlcd *lcd) 13639f8ea46SGeert Uytterhoeven { 137b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 13839f8ea46SGeert Uytterhoeven 13939f8ea46SGeert Uytterhoeven if (!lcd->ops->backlight) 14039f8ea46SGeert Uytterhoeven return; 14139f8ea46SGeert Uytterhoeven 14239f8ea46SGeert Uytterhoeven cancel_delayed_work_sync(&priv->bl_work); 14339f8ea46SGeert Uytterhoeven 14439f8ea46SGeert Uytterhoeven mutex_lock(&priv->bl_tempo_lock); 14539f8ea46SGeert Uytterhoeven if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L)) 14639f8ea46SGeert Uytterhoeven lcd->ops->backlight(lcd, 1); 14739f8ea46SGeert Uytterhoeven priv->bl_tempo = true; 14839f8ea46SGeert Uytterhoeven schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ); 14939f8ea46SGeert Uytterhoeven mutex_unlock(&priv->bl_tempo_lock); 15039f8ea46SGeert Uytterhoeven } 15139f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_poke); 15239f8ea46SGeert Uytterhoeven 15339f8ea46SGeert Uytterhoeven static void charlcd_gotoxy(struct charlcd *lcd) 15439f8ea46SGeert Uytterhoeven { 155b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 1561d3b2af2SGeert Uytterhoeven unsigned int addr; 15739f8ea46SGeert Uytterhoeven 15839f8ea46SGeert Uytterhoeven /* 15939f8ea46SGeert Uytterhoeven * we force the cursor to stay at the end of the 16039f8ea46SGeert Uytterhoeven * line if it wants to go farther 16139f8ea46SGeert Uytterhoeven */ 1621d3b2af2SGeert Uytterhoeven addr = priv->addr.x < lcd->bwidth ? priv->addr.x & (lcd->hwidth - 1) 1631d3b2af2SGeert Uytterhoeven : lcd->bwidth - 1; 1641d3b2af2SGeert Uytterhoeven if (priv->addr.y & 1) 1651d3b2af2SGeert Uytterhoeven addr += lcd->hwidth; 1661d3b2af2SGeert Uytterhoeven if (priv->addr.y & 2) 1671d3b2af2SGeert Uytterhoeven addr += lcd->bwidth; 1681d3b2af2SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_SET_DDRAM_ADDR | addr); 16939f8ea46SGeert Uytterhoeven } 17039f8ea46SGeert Uytterhoeven 17139f8ea46SGeert Uytterhoeven static void charlcd_home(struct charlcd *lcd) 17239f8ea46SGeert Uytterhoeven { 173b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 17439f8ea46SGeert Uytterhoeven 17539f8ea46SGeert Uytterhoeven priv->addr.x = 0; 17639f8ea46SGeert Uytterhoeven priv->addr.y = 0; 17739f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 17839f8ea46SGeert Uytterhoeven } 17939f8ea46SGeert Uytterhoeven 18039f8ea46SGeert Uytterhoeven static void charlcd_print(struct charlcd *lcd, char c) 18139f8ea46SGeert Uytterhoeven { 182b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 18339f8ea46SGeert Uytterhoeven 18439f8ea46SGeert Uytterhoeven if (priv->addr.x < lcd->bwidth) { 18539f8ea46SGeert Uytterhoeven if (lcd->char_conv) 18639f8ea46SGeert Uytterhoeven c = lcd->char_conv[(unsigned char)c]; 18739f8ea46SGeert Uytterhoeven lcd->ops->write_data(lcd, c); 18839f8ea46SGeert Uytterhoeven priv->addr.x++; 18954bc937fSSean Young 19039f8ea46SGeert Uytterhoeven /* prevents the cursor from wrapping onto the next line */ 19139f8ea46SGeert Uytterhoeven if (priv->addr.x == lcd->bwidth) 19239f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 19339f8ea46SGeert Uytterhoeven } 19454bc937fSSean Young } 19539f8ea46SGeert Uytterhoeven 19639f8ea46SGeert Uytterhoeven static void charlcd_clear_fast(struct charlcd *lcd) 19739f8ea46SGeert Uytterhoeven { 19839f8ea46SGeert Uytterhoeven int pos; 19939f8ea46SGeert Uytterhoeven 20039f8ea46SGeert Uytterhoeven charlcd_home(lcd); 20139f8ea46SGeert Uytterhoeven 20239f8ea46SGeert Uytterhoeven if (lcd->ops->clear_fast) 20339f8ea46SGeert Uytterhoeven lcd->ops->clear_fast(lcd); 20439f8ea46SGeert Uytterhoeven else 2051d3b2af2SGeert Uytterhoeven for (pos = 0; pos < min(2, lcd->height) * lcd->hwidth; pos++) 20639f8ea46SGeert Uytterhoeven lcd->ops->write_data(lcd, ' '); 20739f8ea46SGeert Uytterhoeven 20839f8ea46SGeert Uytterhoeven charlcd_home(lcd); 20939f8ea46SGeert Uytterhoeven } 21039f8ea46SGeert Uytterhoeven 21139f8ea46SGeert Uytterhoeven /* clears the display and resets X/Y */ 21239f8ea46SGeert Uytterhoeven static void charlcd_clear_display(struct charlcd *lcd) 21339f8ea46SGeert Uytterhoeven { 214b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 21539f8ea46SGeert Uytterhoeven 21639f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CLEAR); 21739f8ea46SGeert Uytterhoeven priv->addr.x = 0; 21839f8ea46SGeert Uytterhoeven priv->addr.y = 0; 21939f8ea46SGeert Uytterhoeven /* we must wait a few milliseconds (15) */ 22039f8ea46SGeert Uytterhoeven long_sleep(15); 22139f8ea46SGeert Uytterhoeven } 22239f8ea46SGeert Uytterhoeven 22339f8ea46SGeert Uytterhoeven static int charlcd_init_display(struct charlcd *lcd) 22439f8ea46SGeert Uytterhoeven { 225ac201479SGeert Uytterhoeven void (*write_cmd_raw)(struct charlcd *lcd, int cmd); 226b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 227ac201479SGeert Uytterhoeven u8 init; 228ac201479SGeert Uytterhoeven 229ac201479SGeert Uytterhoeven if (lcd->ifwidth != 4 && lcd->ifwidth != 8) 230ac201479SGeert Uytterhoeven return -EINVAL; 23139f8ea46SGeert Uytterhoeven 23239f8ea46SGeert Uytterhoeven priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | 23339f8ea46SGeert Uytterhoeven LCD_FLAG_C | LCD_FLAG_B; 23439f8ea46SGeert Uytterhoeven 23539f8ea46SGeert Uytterhoeven long_sleep(20); /* wait 20 ms after power-up for the paranoid */ 23639f8ea46SGeert Uytterhoeven 237ac201479SGeert Uytterhoeven /* 238ac201479SGeert Uytterhoeven * 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure 239ac201479SGeert Uytterhoeven * the LCD is in 8-bit mode afterwards 240ac201479SGeert Uytterhoeven */ 241ac201479SGeert Uytterhoeven init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS; 242ac201479SGeert Uytterhoeven if (lcd->ifwidth == 4) { 243ac201479SGeert Uytterhoeven init >>= 4; 244ac201479SGeert Uytterhoeven write_cmd_raw = lcd->ops->write_cmd_raw4; 245ac201479SGeert Uytterhoeven } else { 246ac201479SGeert Uytterhoeven write_cmd_raw = lcd->ops->write_cmd; 247ac201479SGeert Uytterhoeven } 248ac201479SGeert Uytterhoeven write_cmd_raw(lcd, init); 24939f8ea46SGeert Uytterhoeven long_sleep(10); 250ac201479SGeert Uytterhoeven write_cmd_raw(lcd, init); 25139f8ea46SGeert Uytterhoeven long_sleep(10); 252ac201479SGeert Uytterhoeven write_cmd_raw(lcd, init); 25339f8ea46SGeert Uytterhoeven long_sleep(10); 25439f8ea46SGeert Uytterhoeven 255ac201479SGeert Uytterhoeven if (lcd->ifwidth == 4) { 256ac201479SGeert Uytterhoeven /* Switch to 4-bit mode, 1 line, small fonts */ 257ac201479SGeert Uytterhoeven lcd->ops->write_cmd_raw4(lcd, LCD_CMD_FUNCTION_SET >> 4); 258ac201479SGeert Uytterhoeven long_sleep(10); 259ac201479SGeert Uytterhoeven } 260ac201479SGeert Uytterhoeven 26139f8ea46SGeert Uytterhoeven /* set font height and lines number */ 26239f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, 263ac201479SGeert Uytterhoeven LCD_CMD_FUNCTION_SET | 264ac201479SGeert Uytterhoeven ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | 26539f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | 26639f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); 26739f8ea46SGeert Uytterhoeven long_sleep(10); 26839f8ea46SGeert Uytterhoeven 26939f8ea46SGeert Uytterhoeven /* display off, cursor off, blink off */ 27039f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CTRL); 27139f8ea46SGeert Uytterhoeven long_sleep(10); 27239f8ea46SGeert Uytterhoeven 27339f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, 27439f8ea46SGeert Uytterhoeven LCD_CMD_DISPLAY_CTRL | /* set display mode */ 27539f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | 27639f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | 27739f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); 27839f8ea46SGeert Uytterhoeven 27939f8ea46SGeert Uytterhoeven charlcd_backlight(lcd, (priv->flags & LCD_FLAG_L) ? 1 : 0); 28039f8ea46SGeert Uytterhoeven 28139f8ea46SGeert Uytterhoeven long_sleep(10); 28239f8ea46SGeert Uytterhoeven 28339f8ea46SGeert Uytterhoeven /* entry mode set : increment, cursor shifting */ 28439f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC); 28539f8ea46SGeert Uytterhoeven 28639f8ea46SGeert Uytterhoeven charlcd_clear_display(lcd); 28739f8ea46SGeert Uytterhoeven return 0; 28839f8ea46SGeert Uytterhoeven } 28939f8ea46SGeert Uytterhoeven 29039f8ea46SGeert Uytterhoeven /* 291b34050faSMiguel Ojeda * Parses an unsigned integer from a string, until a non-digit character 292b34050faSMiguel Ojeda * is found. The empty string is not accepted. No overflow checks are done. 293b34050faSMiguel Ojeda * 294b34050faSMiguel Ojeda * Returns whether the parsing was successful. Only in that case 295b34050faSMiguel Ojeda * the output parameters are written to. 296b34050faSMiguel Ojeda * 297b34050faSMiguel Ojeda * TODO: If the kernel adds an inplace version of kstrtoul(), this function 298b34050faSMiguel Ojeda * could be easily replaced by that. 299b34050faSMiguel Ojeda */ 300b34050faSMiguel Ojeda static bool parse_n(const char *s, unsigned long *res, const char **next_s) 301b34050faSMiguel Ojeda { 302b34050faSMiguel Ojeda if (!isdigit(*s)) 303b34050faSMiguel Ojeda return false; 304b34050faSMiguel Ojeda 305b34050faSMiguel Ojeda *res = 0; 306b34050faSMiguel Ojeda while (isdigit(*s)) { 307b34050faSMiguel Ojeda *res = *res * 10 + (*s - '0'); 308b34050faSMiguel Ojeda ++s; 309b34050faSMiguel Ojeda } 310b34050faSMiguel Ojeda 311b34050faSMiguel Ojeda *next_s = s; 312b34050faSMiguel Ojeda return true; 313b34050faSMiguel Ojeda } 314b34050faSMiguel Ojeda 315b34050faSMiguel Ojeda /* 316b34050faSMiguel Ojeda * Parses a movement command of the form "(.*);", where the group can be 317b34050faSMiguel Ojeda * any number of subcommands of the form "(x|y)[0-9]+". 318b34050faSMiguel Ojeda * 319b34050faSMiguel Ojeda * Returns whether the command is valid. The position arguments are 320b34050faSMiguel Ojeda * only written if the parsing was successful. 321b34050faSMiguel Ojeda * 322b34050faSMiguel Ojeda * For instance: 323b34050faSMiguel Ojeda * - ";" returns (<original x>, <original y>). 324b34050faSMiguel Ojeda * - "x1;" returns (1, <original y>). 325b34050faSMiguel Ojeda * - "y2x1;" returns (1, 2). 326b34050faSMiguel Ojeda * - "x12y34x56;" returns (56, 34). 327b34050faSMiguel Ojeda * - "" fails. 328b34050faSMiguel Ojeda * - "x" fails. 329b34050faSMiguel Ojeda * - "x;" fails. 330b34050faSMiguel Ojeda * - "x1" fails. 331b34050faSMiguel Ojeda * - "xy12;" fails. 332b34050faSMiguel Ojeda * - "x12yy12;" fails. 333b34050faSMiguel Ojeda * - "xx" fails. 334b34050faSMiguel Ojeda */ 335b34050faSMiguel Ojeda static bool parse_xy(const char *s, unsigned long *x, unsigned long *y) 336b34050faSMiguel Ojeda { 337b34050faSMiguel Ojeda unsigned long new_x = *x; 338b34050faSMiguel Ojeda unsigned long new_y = *y; 339b34050faSMiguel Ojeda 340b34050faSMiguel Ojeda for (;;) { 341b34050faSMiguel Ojeda if (!*s) 342b34050faSMiguel Ojeda return false; 343b34050faSMiguel Ojeda 344b34050faSMiguel Ojeda if (*s == ';') 345b34050faSMiguel Ojeda break; 346b34050faSMiguel Ojeda 347b34050faSMiguel Ojeda if (*s == 'x') { 348b34050faSMiguel Ojeda if (!parse_n(s + 1, &new_x, &s)) 349b34050faSMiguel Ojeda return false; 350b34050faSMiguel Ojeda } else if (*s == 'y') { 351b34050faSMiguel Ojeda if (!parse_n(s + 1, &new_y, &s)) 352b34050faSMiguel Ojeda return false; 353b34050faSMiguel Ojeda } else { 354b34050faSMiguel Ojeda return false; 355b34050faSMiguel Ojeda } 356b34050faSMiguel Ojeda } 357b34050faSMiguel Ojeda 358b34050faSMiguel Ojeda *x = new_x; 359b34050faSMiguel Ojeda *y = new_y; 360b34050faSMiguel Ojeda return true; 361b34050faSMiguel Ojeda } 362b34050faSMiguel Ojeda 363b34050faSMiguel Ojeda /* 36439f8ea46SGeert Uytterhoeven * These are the file operation function for user access to /dev/lcd 36539f8ea46SGeert Uytterhoeven * This function can also be called from inside the kernel, by 36639f8ea46SGeert Uytterhoeven * setting file and ppos to NULL. 36739f8ea46SGeert Uytterhoeven * 36839f8ea46SGeert Uytterhoeven */ 36939f8ea46SGeert Uytterhoeven 37039f8ea46SGeert Uytterhoeven static inline int handle_lcd_special_code(struct charlcd *lcd) 37139f8ea46SGeert Uytterhoeven { 372b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 37339f8ea46SGeert Uytterhoeven 37439f8ea46SGeert Uytterhoeven /* LCD special codes */ 37539f8ea46SGeert Uytterhoeven 37639f8ea46SGeert Uytterhoeven int processed = 0; 37739f8ea46SGeert Uytterhoeven 37839f8ea46SGeert Uytterhoeven char *esc = priv->esc_seq.buf + 2; 37939f8ea46SGeert Uytterhoeven int oldflags = priv->flags; 38039f8ea46SGeert Uytterhoeven 38139f8ea46SGeert Uytterhoeven /* check for display mode flags */ 38239f8ea46SGeert Uytterhoeven switch (*esc) { 38339f8ea46SGeert Uytterhoeven case 'D': /* Display ON */ 38439f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_D; 38539f8ea46SGeert Uytterhoeven processed = 1; 38639f8ea46SGeert Uytterhoeven break; 38739f8ea46SGeert Uytterhoeven case 'd': /* Display OFF */ 38839f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_D; 38939f8ea46SGeert Uytterhoeven processed = 1; 39039f8ea46SGeert Uytterhoeven break; 39139f8ea46SGeert Uytterhoeven case 'C': /* Cursor ON */ 39239f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_C; 39339f8ea46SGeert Uytterhoeven processed = 1; 39439f8ea46SGeert Uytterhoeven break; 39539f8ea46SGeert Uytterhoeven case 'c': /* Cursor OFF */ 39639f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_C; 39739f8ea46SGeert Uytterhoeven processed = 1; 39839f8ea46SGeert Uytterhoeven break; 39939f8ea46SGeert Uytterhoeven case 'B': /* Blink ON */ 40039f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_B; 40139f8ea46SGeert Uytterhoeven processed = 1; 40239f8ea46SGeert Uytterhoeven break; 40339f8ea46SGeert Uytterhoeven case 'b': /* Blink OFF */ 40439f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_B; 40539f8ea46SGeert Uytterhoeven processed = 1; 40639f8ea46SGeert Uytterhoeven break; 40739f8ea46SGeert Uytterhoeven case '+': /* Back light ON */ 40839f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_L; 40939f8ea46SGeert Uytterhoeven processed = 1; 41039f8ea46SGeert Uytterhoeven break; 41139f8ea46SGeert Uytterhoeven case '-': /* Back light OFF */ 41239f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_L; 41339f8ea46SGeert Uytterhoeven processed = 1; 41439f8ea46SGeert Uytterhoeven break; 41539f8ea46SGeert Uytterhoeven case '*': /* Flash back light */ 41639f8ea46SGeert Uytterhoeven charlcd_poke(lcd); 41739f8ea46SGeert Uytterhoeven processed = 1; 41839f8ea46SGeert Uytterhoeven break; 41939f8ea46SGeert Uytterhoeven case 'f': /* Small Font */ 42039f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_F; 42139f8ea46SGeert Uytterhoeven processed = 1; 42239f8ea46SGeert Uytterhoeven break; 42339f8ea46SGeert Uytterhoeven case 'F': /* Large Font */ 42439f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_F; 42539f8ea46SGeert Uytterhoeven processed = 1; 42639f8ea46SGeert Uytterhoeven break; 42739f8ea46SGeert Uytterhoeven case 'n': /* One Line */ 42839f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_N; 42939f8ea46SGeert Uytterhoeven processed = 1; 43039f8ea46SGeert Uytterhoeven break; 43139f8ea46SGeert Uytterhoeven case 'N': /* Two Lines */ 43239f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_N; 43399b9b490SRobert Abel processed = 1; 43439f8ea46SGeert Uytterhoeven break; 43539f8ea46SGeert Uytterhoeven case 'l': /* Shift Cursor Left */ 43639f8ea46SGeert Uytterhoeven if (priv->addr.x > 0) { 43739f8ea46SGeert Uytterhoeven /* back one char if not at end of line */ 43839f8ea46SGeert Uytterhoeven if (priv->addr.x < lcd->bwidth) 43939f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); 44039f8ea46SGeert Uytterhoeven priv->addr.x--; 44139f8ea46SGeert Uytterhoeven } 44239f8ea46SGeert Uytterhoeven processed = 1; 44339f8ea46SGeert Uytterhoeven break; 44439f8ea46SGeert Uytterhoeven case 'r': /* shift cursor right */ 44539f8ea46SGeert Uytterhoeven if (priv->addr.x < lcd->width) { 44639f8ea46SGeert Uytterhoeven /* allow the cursor to pass the end of the line */ 44739f8ea46SGeert Uytterhoeven if (priv->addr.x < (lcd->bwidth - 1)) 44839f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, 44939f8ea46SGeert Uytterhoeven LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT); 45039f8ea46SGeert Uytterhoeven priv->addr.x++; 45139f8ea46SGeert Uytterhoeven } 45239f8ea46SGeert Uytterhoeven processed = 1; 45339f8ea46SGeert Uytterhoeven break; 45439f8ea46SGeert Uytterhoeven case 'L': /* shift display left */ 45539f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT); 45639f8ea46SGeert Uytterhoeven processed = 1; 45739f8ea46SGeert Uytterhoeven break; 45839f8ea46SGeert Uytterhoeven case 'R': /* shift display right */ 45939f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, 46039f8ea46SGeert Uytterhoeven LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT | 46139f8ea46SGeert Uytterhoeven LCD_CMD_SHIFT_RIGHT); 46239f8ea46SGeert Uytterhoeven processed = 1; 46339f8ea46SGeert Uytterhoeven break; 46439f8ea46SGeert Uytterhoeven case 'k': { /* kill end of line */ 46539f8ea46SGeert Uytterhoeven int x; 46639f8ea46SGeert Uytterhoeven 46739f8ea46SGeert Uytterhoeven for (x = priv->addr.x; x < lcd->bwidth; x++) 46839f8ea46SGeert Uytterhoeven lcd->ops->write_data(lcd, ' '); 46939f8ea46SGeert Uytterhoeven 47039f8ea46SGeert Uytterhoeven /* restore cursor position */ 47139f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 47239f8ea46SGeert Uytterhoeven processed = 1; 47339f8ea46SGeert Uytterhoeven break; 47439f8ea46SGeert Uytterhoeven } 47539f8ea46SGeert Uytterhoeven case 'I': /* reinitialize display */ 47639f8ea46SGeert Uytterhoeven charlcd_init_display(lcd); 47739f8ea46SGeert Uytterhoeven processed = 1; 47839f8ea46SGeert Uytterhoeven break; 47939f8ea46SGeert Uytterhoeven case 'G': { 48039f8ea46SGeert Uytterhoeven /* Generator : LGcxxxxx...xx; must have <c> between '0' 48139f8ea46SGeert Uytterhoeven * and '7', representing the numerical ASCII code of the 48239f8ea46SGeert Uytterhoeven * redefined character, and <xx...xx> a sequence of 16 48339f8ea46SGeert Uytterhoeven * hex digits representing 8 bytes for each character. 48439f8ea46SGeert Uytterhoeven * Most LCDs will only use 5 lower bits of the 7 first 48539f8ea46SGeert Uytterhoeven * bytes. 48639f8ea46SGeert Uytterhoeven */ 48739f8ea46SGeert Uytterhoeven 48839f8ea46SGeert Uytterhoeven unsigned char cgbytes[8]; 48939f8ea46SGeert Uytterhoeven unsigned char cgaddr; 49039f8ea46SGeert Uytterhoeven int cgoffset; 49139f8ea46SGeert Uytterhoeven int shift; 49239f8ea46SGeert Uytterhoeven char value; 49339f8ea46SGeert Uytterhoeven int addr; 49439f8ea46SGeert Uytterhoeven 49539f8ea46SGeert Uytterhoeven if (!strchr(esc, ';')) 49639f8ea46SGeert Uytterhoeven break; 49739f8ea46SGeert Uytterhoeven 49839f8ea46SGeert Uytterhoeven esc++; 49939f8ea46SGeert Uytterhoeven 50039f8ea46SGeert Uytterhoeven cgaddr = *(esc++) - '0'; 50139f8ea46SGeert Uytterhoeven if (cgaddr > 7) { 50239f8ea46SGeert Uytterhoeven processed = 1; 50339f8ea46SGeert Uytterhoeven break; 50439f8ea46SGeert Uytterhoeven } 50539f8ea46SGeert Uytterhoeven 50639f8ea46SGeert Uytterhoeven cgoffset = 0; 50739f8ea46SGeert Uytterhoeven shift = 0; 50839f8ea46SGeert Uytterhoeven value = 0; 50939f8ea46SGeert Uytterhoeven while (*esc && cgoffset < 8) { 51039f8ea46SGeert Uytterhoeven shift ^= 4; 51139f8ea46SGeert Uytterhoeven if (*esc >= '0' && *esc <= '9') { 51239f8ea46SGeert Uytterhoeven value |= (*esc - '0') << shift; 5132e8c04f7SRobert Abel } else if (*esc >= 'A' && *esc <= 'F') { 51439f8ea46SGeert Uytterhoeven value |= (*esc - 'A' + 10) << shift; 5152e8c04f7SRobert Abel } else if (*esc >= 'a' && *esc <= 'f') { 51639f8ea46SGeert Uytterhoeven value |= (*esc - 'a' + 10) << shift; 51739f8ea46SGeert Uytterhoeven } else { 51839f8ea46SGeert Uytterhoeven esc++; 51939f8ea46SGeert Uytterhoeven continue; 52039f8ea46SGeert Uytterhoeven } 52139f8ea46SGeert Uytterhoeven 52239f8ea46SGeert Uytterhoeven if (shift == 0) { 52339f8ea46SGeert Uytterhoeven cgbytes[cgoffset++] = value; 52439f8ea46SGeert Uytterhoeven value = 0; 52539f8ea46SGeert Uytterhoeven } 52639f8ea46SGeert Uytterhoeven 52739f8ea46SGeert Uytterhoeven esc++; 52839f8ea46SGeert Uytterhoeven } 52939f8ea46SGeert Uytterhoeven 53039f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8)); 53139f8ea46SGeert Uytterhoeven for (addr = 0; addr < cgoffset; addr++) 53239f8ea46SGeert Uytterhoeven lcd->ops->write_data(lcd, cgbytes[addr]); 53339f8ea46SGeert Uytterhoeven 53439f8ea46SGeert Uytterhoeven /* ensures that we stop writing to CGRAM */ 53539f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 53639f8ea46SGeert Uytterhoeven processed = 1; 53739f8ea46SGeert Uytterhoeven break; 53839f8ea46SGeert Uytterhoeven } 53939f8ea46SGeert Uytterhoeven case 'x': /* gotoxy : LxXXX[yYYY]; */ 54039f8ea46SGeert Uytterhoeven case 'y': /* gotoxy : LyYYY[xXXX]; */ 5419bc30ab8SMans Rullgard if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';') 5429bc30ab8SMans Rullgard break; 5439bc30ab8SMans Rullgard 544b34050faSMiguel Ojeda /* If the command is valid, move to the new address */ 545b34050faSMiguel Ojeda if (parse_xy(esc, &priv->addr.x, &priv->addr.y)) 54639f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 547b34050faSMiguel Ojeda 548b34050faSMiguel Ojeda /* Regardless of its validity, mark as processed */ 54939f8ea46SGeert Uytterhoeven processed = 1; 55039f8ea46SGeert Uytterhoeven break; 55139f8ea46SGeert Uytterhoeven } 55239f8ea46SGeert Uytterhoeven 55339f8ea46SGeert Uytterhoeven /* TODO: This indent party here got ugly, clean it! */ 55439f8ea46SGeert Uytterhoeven /* Check whether one flag was changed */ 55539f8ea46SGeert Uytterhoeven if (oldflags == priv->flags) 55639f8ea46SGeert Uytterhoeven return processed; 55739f8ea46SGeert Uytterhoeven 55839f8ea46SGeert Uytterhoeven /* check whether one of B,C,D flags were changed */ 55939f8ea46SGeert Uytterhoeven if ((oldflags ^ priv->flags) & 56039f8ea46SGeert Uytterhoeven (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D)) 56139f8ea46SGeert Uytterhoeven /* set display mode */ 56239f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, 56339f8ea46SGeert Uytterhoeven LCD_CMD_DISPLAY_CTRL | 56439f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | 56539f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | 56639f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); 56739f8ea46SGeert Uytterhoeven /* check whether one of F,N flags was changed */ 56839f8ea46SGeert Uytterhoeven else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N)) 56939f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, 570ac201479SGeert Uytterhoeven LCD_CMD_FUNCTION_SET | 571ac201479SGeert Uytterhoeven ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | 57239f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | 57339f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); 57439f8ea46SGeert Uytterhoeven /* check whether L flag was changed */ 57539f8ea46SGeert Uytterhoeven else if ((oldflags ^ priv->flags) & LCD_FLAG_L) 57639f8ea46SGeert Uytterhoeven charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L)); 57739f8ea46SGeert Uytterhoeven 57839f8ea46SGeert Uytterhoeven return processed; 57939f8ea46SGeert Uytterhoeven } 58039f8ea46SGeert Uytterhoeven 58139f8ea46SGeert Uytterhoeven static void charlcd_write_char(struct charlcd *lcd, char c) 58239f8ea46SGeert Uytterhoeven { 583b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 58439f8ea46SGeert Uytterhoeven 58539f8ea46SGeert Uytterhoeven /* first, we'll test if we're in escape mode */ 58639f8ea46SGeert Uytterhoeven if ((c != '\n') && priv->esc_seq.len >= 0) { 58739f8ea46SGeert Uytterhoeven /* yes, let's add this char to the buffer */ 58839f8ea46SGeert Uytterhoeven priv->esc_seq.buf[priv->esc_seq.len++] = c; 5898c483758SRobert Abel priv->esc_seq.buf[priv->esc_seq.len] = '\0'; 59039f8ea46SGeert Uytterhoeven } else { 59139f8ea46SGeert Uytterhoeven /* aborts any previous escape sequence */ 59239f8ea46SGeert Uytterhoeven priv->esc_seq.len = -1; 59339f8ea46SGeert Uytterhoeven 59439f8ea46SGeert Uytterhoeven switch (c) { 59539f8ea46SGeert Uytterhoeven case LCD_ESCAPE_CHAR: 59639f8ea46SGeert Uytterhoeven /* start of an escape sequence */ 59739f8ea46SGeert Uytterhoeven priv->esc_seq.len = 0; 5988c483758SRobert Abel priv->esc_seq.buf[priv->esc_seq.len] = '\0'; 59939f8ea46SGeert Uytterhoeven break; 60039f8ea46SGeert Uytterhoeven case '\b': 60139f8ea46SGeert Uytterhoeven /* go back one char and clear it */ 60239f8ea46SGeert Uytterhoeven if (priv->addr.x > 0) { 60339f8ea46SGeert Uytterhoeven /* 60439f8ea46SGeert Uytterhoeven * check if we're not at the 60539f8ea46SGeert Uytterhoeven * end of the line 60639f8ea46SGeert Uytterhoeven */ 60739f8ea46SGeert Uytterhoeven if (priv->addr.x < lcd->bwidth) 60839f8ea46SGeert Uytterhoeven /* back one char */ 60939f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); 61039f8ea46SGeert Uytterhoeven priv->addr.x--; 61139f8ea46SGeert Uytterhoeven } 61239f8ea46SGeert Uytterhoeven /* replace with a space */ 61339f8ea46SGeert Uytterhoeven lcd->ops->write_data(lcd, ' '); 61439f8ea46SGeert Uytterhoeven /* back one char again */ 61539f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); 61639f8ea46SGeert Uytterhoeven break; 6179629cccaSRobert Abel case '\f': 61839f8ea46SGeert Uytterhoeven /* quickly clear the display */ 61939f8ea46SGeert Uytterhoeven charlcd_clear_fast(lcd); 62039f8ea46SGeert Uytterhoeven break; 62139f8ea46SGeert Uytterhoeven case '\n': 62239f8ea46SGeert Uytterhoeven /* 62339f8ea46SGeert Uytterhoeven * flush the remainder of the current line and 62439f8ea46SGeert Uytterhoeven * go to the beginning of the next line 62539f8ea46SGeert Uytterhoeven */ 62639f8ea46SGeert Uytterhoeven for (; priv->addr.x < lcd->bwidth; priv->addr.x++) 62739f8ea46SGeert Uytterhoeven lcd->ops->write_data(lcd, ' '); 62839f8ea46SGeert Uytterhoeven priv->addr.x = 0; 62939f8ea46SGeert Uytterhoeven priv->addr.y = (priv->addr.y + 1) % lcd->height; 63039f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 63139f8ea46SGeert Uytterhoeven break; 63239f8ea46SGeert Uytterhoeven case '\r': 63339f8ea46SGeert Uytterhoeven /* go to the beginning of the same line */ 63439f8ea46SGeert Uytterhoeven priv->addr.x = 0; 63539f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 63639f8ea46SGeert Uytterhoeven break; 63739f8ea46SGeert Uytterhoeven case '\t': 63839f8ea46SGeert Uytterhoeven /* print a space instead of the tab */ 63939f8ea46SGeert Uytterhoeven charlcd_print(lcd, ' '); 64039f8ea46SGeert Uytterhoeven break; 64139f8ea46SGeert Uytterhoeven default: 64239f8ea46SGeert Uytterhoeven /* simply print this char */ 64339f8ea46SGeert Uytterhoeven charlcd_print(lcd, c); 64439f8ea46SGeert Uytterhoeven break; 64539f8ea46SGeert Uytterhoeven } 64639f8ea46SGeert Uytterhoeven } 64739f8ea46SGeert Uytterhoeven 64839f8ea46SGeert Uytterhoeven /* 64939f8ea46SGeert Uytterhoeven * now we'll see if we're in an escape mode and if the current 65039f8ea46SGeert Uytterhoeven * escape sequence can be understood. 65139f8ea46SGeert Uytterhoeven */ 65239f8ea46SGeert Uytterhoeven if (priv->esc_seq.len >= 2) { 65339f8ea46SGeert Uytterhoeven int processed = 0; 65439f8ea46SGeert Uytterhoeven 65539f8ea46SGeert Uytterhoeven if (!strcmp(priv->esc_seq.buf, "[2J")) { 65639f8ea46SGeert Uytterhoeven /* clear the display */ 65739f8ea46SGeert Uytterhoeven charlcd_clear_fast(lcd); 65839f8ea46SGeert Uytterhoeven processed = 1; 65939f8ea46SGeert Uytterhoeven } else if (!strcmp(priv->esc_seq.buf, "[H")) { 66039f8ea46SGeert Uytterhoeven /* cursor to home */ 66139f8ea46SGeert Uytterhoeven charlcd_home(lcd); 66239f8ea46SGeert Uytterhoeven processed = 1; 66339f8ea46SGeert Uytterhoeven } 66439f8ea46SGeert Uytterhoeven /* codes starting with ^[[L */ 66539f8ea46SGeert Uytterhoeven else if ((priv->esc_seq.len >= 3) && 66639f8ea46SGeert Uytterhoeven (priv->esc_seq.buf[0] == '[') && 66739f8ea46SGeert Uytterhoeven (priv->esc_seq.buf[1] == 'L')) { 66839f8ea46SGeert Uytterhoeven processed = handle_lcd_special_code(lcd); 66939f8ea46SGeert Uytterhoeven } 67039f8ea46SGeert Uytterhoeven 67139f8ea46SGeert Uytterhoeven /* LCD special escape codes */ 67239f8ea46SGeert Uytterhoeven /* 67339f8ea46SGeert Uytterhoeven * flush the escape sequence if it's been processed 67439f8ea46SGeert Uytterhoeven * or if it is getting too long. 67539f8ea46SGeert Uytterhoeven */ 67639f8ea46SGeert Uytterhoeven if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN)) 67739f8ea46SGeert Uytterhoeven priv->esc_seq.len = -1; 67839f8ea46SGeert Uytterhoeven } /* escape codes */ 67939f8ea46SGeert Uytterhoeven } 68039f8ea46SGeert Uytterhoeven 68139f8ea46SGeert Uytterhoeven static struct charlcd *the_charlcd; 68239f8ea46SGeert Uytterhoeven 68339f8ea46SGeert Uytterhoeven static ssize_t charlcd_write(struct file *file, const char __user *buf, 68439f8ea46SGeert Uytterhoeven size_t count, loff_t *ppos) 68539f8ea46SGeert Uytterhoeven { 68639f8ea46SGeert Uytterhoeven const char __user *tmp = buf; 68739f8ea46SGeert Uytterhoeven char c; 68839f8ea46SGeert Uytterhoeven 68939f8ea46SGeert Uytterhoeven for (; count-- > 0; (*ppos)++, tmp++) { 69039f8ea46SGeert Uytterhoeven if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) 69139f8ea46SGeert Uytterhoeven /* 69239f8ea46SGeert Uytterhoeven * let's be a little nice with other processes 69339f8ea46SGeert Uytterhoeven * that need some CPU 69439f8ea46SGeert Uytterhoeven */ 69539f8ea46SGeert Uytterhoeven schedule(); 69639f8ea46SGeert Uytterhoeven 69739f8ea46SGeert Uytterhoeven if (get_user(c, tmp)) 69839f8ea46SGeert Uytterhoeven return -EFAULT; 69939f8ea46SGeert Uytterhoeven 70039f8ea46SGeert Uytterhoeven charlcd_write_char(the_charlcd, c); 70139f8ea46SGeert Uytterhoeven } 70239f8ea46SGeert Uytterhoeven 70339f8ea46SGeert Uytterhoeven return tmp - buf; 70439f8ea46SGeert Uytterhoeven } 70539f8ea46SGeert Uytterhoeven 70639f8ea46SGeert Uytterhoeven static int charlcd_open(struct inode *inode, struct file *file) 70739f8ea46SGeert Uytterhoeven { 708b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(the_charlcd); 70993dc1774SWilly Tarreau int ret; 71039f8ea46SGeert Uytterhoeven 71193dc1774SWilly Tarreau ret = -EBUSY; 71239f8ea46SGeert Uytterhoeven if (!atomic_dec_and_test(&charlcd_available)) 71393dc1774SWilly Tarreau goto fail; /* open only once at a time */ 71439f8ea46SGeert Uytterhoeven 71593dc1774SWilly Tarreau ret = -EPERM; 71639f8ea46SGeert Uytterhoeven if (file->f_mode & FMODE_READ) /* device is write-only */ 71793dc1774SWilly Tarreau goto fail; 71839f8ea46SGeert Uytterhoeven 71939f8ea46SGeert Uytterhoeven if (priv->must_clear) { 72039f8ea46SGeert Uytterhoeven charlcd_clear_display(&priv->lcd); 72139f8ea46SGeert Uytterhoeven priv->must_clear = false; 72239f8ea46SGeert Uytterhoeven } 72339f8ea46SGeert Uytterhoeven return nonseekable_open(inode, file); 72493dc1774SWilly Tarreau 72593dc1774SWilly Tarreau fail: 72693dc1774SWilly Tarreau atomic_inc(&charlcd_available); 72793dc1774SWilly Tarreau return ret; 72839f8ea46SGeert Uytterhoeven } 72939f8ea46SGeert Uytterhoeven 73039f8ea46SGeert Uytterhoeven static int charlcd_release(struct inode *inode, struct file *file) 73139f8ea46SGeert Uytterhoeven { 73239f8ea46SGeert Uytterhoeven atomic_inc(&charlcd_available); 73339f8ea46SGeert Uytterhoeven return 0; 73439f8ea46SGeert Uytterhoeven } 73539f8ea46SGeert Uytterhoeven 73639f8ea46SGeert Uytterhoeven static const struct file_operations charlcd_fops = { 73739f8ea46SGeert Uytterhoeven .write = charlcd_write, 73839f8ea46SGeert Uytterhoeven .open = charlcd_open, 73939f8ea46SGeert Uytterhoeven .release = charlcd_release, 74039f8ea46SGeert Uytterhoeven .llseek = no_llseek, 74139f8ea46SGeert Uytterhoeven }; 74239f8ea46SGeert Uytterhoeven 74339f8ea46SGeert Uytterhoeven static struct miscdevice charlcd_dev = { 74439f8ea46SGeert Uytterhoeven .minor = LCD_MINOR, 74539f8ea46SGeert Uytterhoeven .name = "lcd", 74639f8ea46SGeert Uytterhoeven .fops = &charlcd_fops, 74739f8ea46SGeert Uytterhoeven }; 74839f8ea46SGeert Uytterhoeven 74939f8ea46SGeert Uytterhoeven static void charlcd_puts(struct charlcd *lcd, const char *s) 75039f8ea46SGeert Uytterhoeven { 75139f8ea46SGeert Uytterhoeven const char *tmp = s; 75239f8ea46SGeert Uytterhoeven int count = strlen(s); 75339f8ea46SGeert Uytterhoeven 75439f8ea46SGeert Uytterhoeven for (; count-- > 0; tmp++) { 75539f8ea46SGeert Uytterhoeven if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) 75639f8ea46SGeert Uytterhoeven /* 75739f8ea46SGeert Uytterhoeven * let's be a little nice with other processes 75839f8ea46SGeert Uytterhoeven * that need some CPU 75939f8ea46SGeert Uytterhoeven */ 76039f8ea46SGeert Uytterhoeven schedule(); 76139f8ea46SGeert Uytterhoeven 76239f8ea46SGeert Uytterhoeven charlcd_write_char(lcd, *tmp); 76339f8ea46SGeert Uytterhoeven } 76439f8ea46SGeert Uytterhoeven } 76539f8ea46SGeert Uytterhoeven 766c9171722SMans Rullgard #ifdef CONFIG_PANEL_BOOT_MESSAGE 767c9171722SMans Rullgard #define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE 768c9171722SMans Rullgard #else 769c9171722SMans Rullgard #define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n" 770c9171722SMans Rullgard #endif 771c9171722SMans Rullgard 772cc5d04d8SMans Rullgard #ifdef CONFIG_CHARLCD_BL_ON 773cc5d04d8SMans Rullgard #define LCD_INIT_BL "\x1b[L+" 774cc5d04d8SMans Rullgard #elif defined(CONFIG_CHARLCD_BL_FLASH) 775cc5d04d8SMans Rullgard #define LCD_INIT_BL "\x1b[L*" 776cc5d04d8SMans Rullgard #else 777cc5d04d8SMans Rullgard #define LCD_INIT_BL "\x1b[L-" 778cc5d04d8SMans Rullgard #endif 779cc5d04d8SMans Rullgard 78039f8ea46SGeert Uytterhoeven /* initialize the LCD driver */ 78139f8ea46SGeert Uytterhoeven static int charlcd_init(struct charlcd *lcd) 78239f8ea46SGeert Uytterhoeven { 783b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 78439f8ea46SGeert Uytterhoeven int ret; 78539f8ea46SGeert Uytterhoeven 78639f8ea46SGeert Uytterhoeven if (lcd->ops->backlight) { 78739f8ea46SGeert Uytterhoeven mutex_init(&priv->bl_tempo_lock); 78839f8ea46SGeert Uytterhoeven INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off); 78939f8ea46SGeert Uytterhoeven } 79039f8ea46SGeert Uytterhoeven 79139f8ea46SGeert Uytterhoeven /* 79239f8ea46SGeert Uytterhoeven * before this line, we must NOT send anything to the display. 79339f8ea46SGeert Uytterhoeven * Since charlcd_init_display() needs to write data, we have to 79439f8ea46SGeert Uytterhoeven * enable mark the LCD initialized just before. 79539f8ea46SGeert Uytterhoeven */ 79639f8ea46SGeert Uytterhoeven ret = charlcd_init_display(lcd); 79739f8ea46SGeert Uytterhoeven if (ret) 79839f8ea46SGeert Uytterhoeven return ret; 79939f8ea46SGeert Uytterhoeven 80039f8ea46SGeert Uytterhoeven /* display a short message */ 801cc5d04d8SMans Rullgard charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT); 802c9171722SMans Rullgard 80339f8ea46SGeert Uytterhoeven /* clear the display on the next device opening */ 80439f8ea46SGeert Uytterhoeven priv->must_clear = true; 80539f8ea46SGeert Uytterhoeven charlcd_home(lcd); 80639f8ea46SGeert Uytterhoeven return 0; 80739f8ea46SGeert Uytterhoeven } 80839f8ea46SGeert Uytterhoeven 80939f8ea46SGeert Uytterhoeven struct charlcd *charlcd_alloc(unsigned int drvdata_size) 81039f8ea46SGeert Uytterhoeven { 81139f8ea46SGeert Uytterhoeven struct charlcd_priv *priv; 81239f8ea46SGeert Uytterhoeven struct charlcd *lcd; 81339f8ea46SGeert Uytterhoeven 81439f8ea46SGeert Uytterhoeven priv = kzalloc(sizeof(*priv) + drvdata_size, GFP_KERNEL); 81539f8ea46SGeert Uytterhoeven if (!priv) 81639f8ea46SGeert Uytterhoeven return NULL; 81739f8ea46SGeert Uytterhoeven 81839f8ea46SGeert Uytterhoeven priv->esc_seq.len = -1; 81939f8ea46SGeert Uytterhoeven 82039f8ea46SGeert Uytterhoeven lcd = &priv->lcd; 821ac201479SGeert Uytterhoeven lcd->ifwidth = 8; 82239f8ea46SGeert Uytterhoeven lcd->bwidth = DEFAULT_LCD_BWIDTH; 82339f8ea46SGeert Uytterhoeven lcd->hwidth = DEFAULT_LCD_HWIDTH; 82439f8ea46SGeert Uytterhoeven lcd->drvdata = priv->drvdata; 82539f8ea46SGeert Uytterhoeven 82639f8ea46SGeert Uytterhoeven return lcd; 82739f8ea46SGeert Uytterhoeven } 82839f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_alloc); 82939f8ea46SGeert Uytterhoeven 8308e44fc85SAndy Shevchenko void charlcd_free(struct charlcd *lcd) 8318e44fc85SAndy Shevchenko { 8328e44fc85SAndy Shevchenko kfree(charlcd_to_priv(lcd)); 8338e44fc85SAndy Shevchenko } 8348e44fc85SAndy Shevchenko EXPORT_SYMBOL_GPL(charlcd_free); 8358e44fc85SAndy Shevchenko 83639f8ea46SGeert Uytterhoeven static int panel_notify_sys(struct notifier_block *this, unsigned long code, 83739f8ea46SGeert Uytterhoeven void *unused) 83839f8ea46SGeert Uytterhoeven { 83939f8ea46SGeert Uytterhoeven struct charlcd *lcd = the_charlcd; 84039f8ea46SGeert Uytterhoeven 84139f8ea46SGeert Uytterhoeven switch (code) { 84239f8ea46SGeert Uytterhoeven case SYS_DOWN: 84339f8ea46SGeert Uytterhoeven charlcd_puts(lcd, 84439f8ea46SGeert Uytterhoeven "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); 84539f8ea46SGeert Uytterhoeven break; 84639f8ea46SGeert Uytterhoeven case SYS_HALT: 84739f8ea46SGeert Uytterhoeven charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); 84839f8ea46SGeert Uytterhoeven break; 84939f8ea46SGeert Uytterhoeven case SYS_POWER_OFF: 85039f8ea46SGeert Uytterhoeven charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); 85139f8ea46SGeert Uytterhoeven break; 85239f8ea46SGeert Uytterhoeven default: 85339f8ea46SGeert Uytterhoeven break; 85439f8ea46SGeert Uytterhoeven } 85539f8ea46SGeert Uytterhoeven return NOTIFY_DONE; 85639f8ea46SGeert Uytterhoeven } 85739f8ea46SGeert Uytterhoeven 85839f8ea46SGeert Uytterhoeven static struct notifier_block panel_notifier = { 85939f8ea46SGeert Uytterhoeven panel_notify_sys, 86039f8ea46SGeert Uytterhoeven NULL, 86139f8ea46SGeert Uytterhoeven 0 86239f8ea46SGeert Uytterhoeven }; 86339f8ea46SGeert Uytterhoeven 86439f8ea46SGeert Uytterhoeven int charlcd_register(struct charlcd *lcd) 86539f8ea46SGeert Uytterhoeven { 86639f8ea46SGeert Uytterhoeven int ret; 86739f8ea46SGeert Uytterhoeven 86839f8ea46SGeert Uytterhoeven ret = charlcd_init(lcd); 86939f8ea46SGeert Uytterhoeven if (ret) 87039f8ea46SGeert Uytterhoeven return ret; 87139f8ea46SGeert Uytterhoeven 87239f8ea46SGeert Uytterhoeven ret = misc_register(&charlcd_dev); 87339f8ea46SGeert Uytterhoeven if (ret) 87439f8ea46SGeert Uytterhoeven return ret; 87539f8ea46SGeert Uytterhoeven 87639f8ea46SGeert Uytterhoeven the_charlcd = lcd; 87739f8ea46SGeert Uytterhoeven register_reboot_notifier(&panel_notifier); 87839f8ea46SGeert Uytterhoeven return 0; 87939f8ea46SGeert Uytterhoeven } 88039f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_register); 88139f8ea46SGeert Uytterhoeven 88239f8ea46SGeert Uytterhoeven int charlcd_unregister(struct charlcd *lcd) 88339f8ea46SGeert Uytterhoeven { 884b658a211SAndy Shevchenko struct charlcd_priv *priv = charlcd_to_priv(lcd); 88539f8ea46SGeert Uytterhoeven 88639f8ea46SGeert Uytterhoeven unregister_reboot_notifier(&panel_notifier); 88739f8ea46SGeert Uytterhoeven charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-"); 88839f8ea46SGeert Uytterhoeven misc_deregister(&charlcd_dev); 88939f8ea46SGeert Uytterhoeven the_charlcd = NULL; 89039f8ea46SGeert Uytterhoeven if (lcd->ops->backlight) { 89139f8ea46SGeert Uytterhoeven cancel_delayed_work_sync(&priv->bl_work); 89239f8ea46SGeert Uytterhoeven priv->lcd.ops->backlight(&priv->lcd, 0); 89339f8ea46SGeert Uytterhoeven } 89439f8ea46SGeert Uytterhoeven 89539f8ea46SGeert Uytterhoeven return 0; 89639f8ea46SGeert Uytterhoeven } 89739f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_unregister); 89839f8ea46SGeert Uytterhoeven 89939f8ea46SGeert Uytterhoeven MODULE_LICENSE("GPL"); 900