139f8ea46SGeert Uytterhoeven /* 239f8ea46SGeert Uytterhoeven * Character LCD driver for Linux 339f8ea46SGeert Uytterhoeven * 439f8ea46SGeert Uytterhoeven * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu> 539f8ea46SGeert Uytterhoeven * Copyright (C) 2016-2017 Glider bvba 639f8ea46SGeert Uytterhoeven * 739f8ea46SGeert Uytterhoeven * This program is free software; you can redistribute it and/or 839f8ea46SGeert Uytterhoeven * modify it under the terms of the GNU General Public License 939f8ea46SGeert Uytterhoeven * as published by the Free Software Foundation; either version 1039f8ea46SGeert Uytterhoeven * 2 of the License, or (at your option) any later version. 1139f8ea46SGeert Uytterhoeven */ 1239f8ea46SGeert Uytterhoeven 1339f8ea46SGeert Uytterhoeven #include <linux/atomic.h> 1439f8ea46SGeert Uytterhoeven #include <linux/delay.h> 1539f8ea46SGeert Uytterhoeven #include <linux/fs.h> 1639f8ea46SGeert Uytterhoeven #include <linux/miscdevice.h> 1739f8ea46SGeert Uytterhoeven #include <linux/module.h> 1839f8ea46SGeert Uytterhoeven #include <linux/notifier.h> 1939f8ea46SGeert Uytterhoeven #include <linux/reboot.h> 2039f8ea46SGeert Uytterhoeven #include <linux/slab.h> 2139f8ea46SGeert Uytterhoeven #include <linux/uaccess.h> 2239f8ea46SGeert Uytterhoeven #include <linux/workqueue.h> 2339f8ea46SGeert Uytterhoeven 2439f8ea46SGeert Uytterhoeven #include <generated/utsrelease.h> 2539f8ea46SGeert Uytterhoeven 2639f8ea46SGeert Uytterhoeven #include <misc/charlcd.h> 2739f8ea46SGeert Uytterhoeven 2839f8ea46SGeert Uytterhoeven #define LCD_MINOR 156 2939f8ea46SGeert Uytterhoeven 3039f8ea46SGeert Uytterhoeven #define DEFAULT_LCD_BWIDTH 40 3139f8ea46SGeert Uytterhoeven #define DEFAULT_LCD_HWIDTH 64 3239f8ea46SGeert Uytterhoeven 3339f8ea46SGeert Uytterhoeven /* Keep the backlight on this many seconds for each flash */ 3439f8ea46SGeert Uytterhoeven #define LCD_BL_TEMPO_PERIOD 4 3539f8ea46SGeert Uytterhoeven 3639f8ea46SGeert Uytterhoeven #define LCD_FLAG_B 0x0004 /* Blink on */ 3739f8ea46SGeert Uytterhoeven #define LCD_FLAG_C 0x0008 /* Cursor on */ 3839f8ea46SGeert Uytterhoeven #define LCD_FLAG_D 0x0010 /* Display on */ 3939f8ea46SGeert Uytterhoeven #define LCD_FLAG_F 0x0020 /* Large font mode */ 4039f8ea46SGeert Uytterhoeven #define LCD_FLAG_N 0x0040 /* 2-rows mode */ 4139f8ea46SGeert Uytterhoeven #define LCD_FLAG_L 0x0080 /* Backlight enabled */ 4239f8ea46SGeert Uytterhoeven 4339f8ea46SGeert Uytterhoeven /* LCD commands */ 4439f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */ 4539f8ea46SGeert Uytterhoeven 4639f8ea46SGeert Uytterhoeven #define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */ 4739f8ea46SGeert Uytterhoeven #define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */ 4839f8ea46SGeert Uytterhoeven 4939f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */ 5039f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */ 5139f8ea46SGeert Uytterhoeven #define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */ 5239f8ea46SGeert Uytterhoeven #define LCD_CMD_BLINK_ON 0x01 /* Set blink on */ 5339f8ea46SGeert Uytterhoeven 5439f8ea46SGeert Uytterhoeven #define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */ 5539f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */ 5639f8ea46SGeert Uytterhoeven #define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */ 5739f8ea46SGeert Uytterhoeven 5839f8ea46SGeert Uytterhoeven #define LCD_CMD_FUNCTION_SET 0x20 /* Set function */ 5939f8ea46SGeert Uytterhoeven #define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */ 6039f8ea46SGeert Uytterhoeven #define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */ 6139f8ea46SGeert Uytterhoeven #define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */ 6239f8ea46SGeert Uytterhoeven 6339f8ea46SGeert Uytterhoeven #define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */ 6439f8ea46SGeert Uytterhoeven 6539f8ea46SGeert Uytterhoeven #define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */ 6639f8ea46SGeert Uytterhoeven 6739f8ea46SGeert Uytterhoeven #define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */ 6839f8ea46SGeert Uytterhoeven #define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */ 6939f8ea46SGeert Uytterhoeven 7039f8ea46SGeert Uytterhoeven struct charlcd_priv { 7139f8ea46SGeert Uytterhoeven struct charlcd lcd; 7239f8ea46SGeert Uytterhoeven 7339f8ea46SGeert Uytterhoeven struct delayed_work bl_work; 7439f8ea46SGeert Uytterhoeven struct mutex bl_tempo_lock; /* Protects access to bl_tempo */ 7539f8ea46SGeert Uytterhoeven bool bl_tempo; 7639f8ea46SGeert Uytterhoeven 7739f8ea46SGeert Uytterhoeven bool must_clear; 7839f8ea46SGeert Uytterhoeven 7939f8ea46SGeert Uytterhoeven /* contains the LCD config state */ 8039f8ea46SGeert Uytterhoeven unsigned long int flags; 8139f8ea46SGeert Uytterhoeven 8239f8ea46SGeert Uytterhoeven /* Contains the LCD X and Y offset */ 8339f8ea46SGeert Uytterhoeven struct { 8439f8ea46SGeert Uytterhoeven unsigned long int x; 8539f8ea46SGeert Uytterhoeven unsigned long int y; 8639f8ea46SGeert Uytterhoeven } addr; 8739f8ea46SGeert Uytterhoeven 8839f8ea46SGeert Uytterhoeven /* Current escape sequence and it's length or -1 if outside */ 8939f8ea46SGeert Uytterhoeven struct { 9039f8ea46SGeert Uytterhoeven char buf[LCD_ESCAPE_LEN + 1]; 9139f8ea46SGeert Uytterhoeven int len; 9239f8ea46SGeert Uytterhoeven } esc_seq; 9339f8ea46SGeert Uytterhoeven 9439f8ea46SGeert Uytterhoeven unsigned long long drvdata[0]; 9539f8ea46SGeert Uytterhoeven }; 9639f8ea46SGeert Uytterhoeven 9739f8ea46SGeert Uytterhoeven #define to_priv(p) container_of(p, struct charlcd_priv, lcd) 9839f8ea46SGeert Uytterhoeven 9939f8ea46SGeert Uytterhoeven /* Device single-open policy control */ 10039f8ea46SGeert Uytterhoeven static atomic_t charlcd_available = ATOMIC_INIT(1); 10139f8ea46SGeert Uytterhoeven 10239f8ea46SGeert Uytterhoeven /* sleeps that many milliseconds with a reschedule */ 10339f8ea46SGeert Uytterhoeven static void long_sleep(int ms) 10439f8ea46SGeert Uytterhoeven { 10539f8ea46SGeert Uytterhoeven if (in_interrupt()) 10639f8ea46SGeert Uytterhoeven mdelay(ms); 10739f8ea46SGeert Uytterhoeven else 10839f8ea46SGeert Uytterhoeven schedule_timeout_interruptible(msecs_to_jiffies(ms)); 10939f8ea46SGeert Uytterhoeven } 11039f8ea46SGeert Uytterhoeven 11139f8ea46SGeert Uytterhoeven /* turn the backlight on or off */ 11239f8ea46SGeert Uytterhoeven static void charlcd_backlight(struct charlcd *lcd, int on) 11339f8ea46SGeert Uytterhoeven { 11439f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = to_priv(lcd); 11539f8ea46SGeert Uytterhoeven 11639f8ea46SGeert Uytterhoeven if (!lcd->ops->backlight) 11739f8ea46SGeert Uytterhoeven return; 11839f8ea46SGeert Uytterhoeven 11939f8ea46SGeert Uytterhoeven mutex_lock(&priv->bl_tempo_lock); 12039f8ea46SGeert Uytterhoeven if (!priv->bl_tempo) 12139f8ea46SGeert Uytterhoeven lcd->ops->backlight(lcd, on); 12239f8ea46SGeert Uytterhoeven mutex_unlock(&priv->bl_tempo_lock); 12339f8ea46SGeert Uytterhoeven } 12439f8ea46SGeert Uytterhoeven 12539f8ea46SGeert Uytterhoeven static void charlcd_bl_off(struct work_struct *work) 12639f8ea46SGeert Uytterhoeven { 12739f8ea46SGeert Uytterhoeven struct delayed_work *dwork = to_delayed_work(work); 12839f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = 12939f8ea46SGeert Uytterhoeven container_of(dwork, struct charlcd_priv, bl_work); 13039f8ea46SGeert Uytterhoeven 13139f8ea46SGeert Uytterhoeven mutex_lock(&priv->bl_tempo_lock); 13239f8ea46SGeert Uytterhoeven if (priv->bl_tempo) { 13339f8ea46SGeert Uytterhoeven priv->bl_tempo = false; 13439f8ea46SGeert Uytterhoeven if (!(priv->flags & LCD_FLAG_L)) 13539f8ea46SGeert Uytterhoeven priv->lcd.ops->backlight(&priv->lcd, 0); 13639f8ea46SGeert Uytterhoeven } 13739f8ea46SGeert Uytterhoeven mutex_unlock(&priv->bl_tempo_lock); 13839f8ea46SGeert Uytterhoeven } 13939f8ea46SGeert Uytterhoeven 14039f8ea46SGeert Uytterhoeven /* turn the backlight on for a little while */ 14139f8ea46SGeert Uytterhoeven void charlcd_poke(struct charlcd *lcd) 14239f8ea46SGeert Uytterhoeven { 14339f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = to_priv(lcd); 14439f8ea46SGeert Uytterhoeven 14539f8ea46SGeert Uytterhoeven if (!lcd->ops->backlight) 14639f8ea46SGeert Uytterhoeven return; 14739f8ea46SGeert Uytterhoeven 14839f8ea46SGeert Uytterhoeven cancel_delayed_work_sync(&priv->bl_work); 14939f8ea46SGeert Uytterhoeven 15039f8ea46SGeert Uytterhoeven mutex_lock(&priv->bl_tempo_lock); 15139f8ea46SGeert Uytterhoeven if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L)) 15239f8ea46SGeert Uytterhoeven lcd->ops->backlight(lcd, 1); 15339f8ea46SGeert Uytterhoeven priv->bl_tempo = true; 15439f8ea46SGeert Uytterhoeven schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ); 15539f8ea46SGeert Uytterhoeven mutex_unlock(&priv->bl_tempo_lock); 15639f8ea46SGeert Uytterhoeven } 15739f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_poke); 15839f8ea46SGeert Uytterhoeven 15939f8ea46SGeert Uytterhoeven static void charlcd_gotoxy(struct charlcd *lcd) 16039f8ea46SGeert Uytterhoeven { 16139f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = to_priv(lcd); 1621d3b2af2SGeert Uytterhoeven unsigned int addr; 16339f8ea46SGeert Uytterhoeven 16439f8ea46SGeert Uytterhoeven /* 16539f8ea46SGeert Uytterhoeven * we force the cursor to stay at the end of the 16639f8ea46SGeert Uytterhoeven * line if it wants to go farther 16739f8ea46SGeert Uytterhoeven */ 1681d3b2af2SGeert Uytterhoeven addr = priv->addr.x < lcd->bwidth ? priv->addr.x & (lcd->hwidth - 1) 1691d3b2af2SGeert Uytterhoeven : lcd->bwidth - 1; 1701d3b2af2SGeert Uytterhoeven if (priv->addr.y & 1) 1711d3b2af2SGeert Uytterhoeven addr += lcd->hwidth; 1721d3b2af2SGeert Uytterhoeven if (priv->addr.y & 2) 1731d3b2af2SGeert Uytterhoeven addr += lcd->bwidth; 1741d3b2af2SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_SET_DDRAM_ADDR | addr); 17539f8ea46SGeert Uytterhoeven } 17639f8ea46SGeert Uytterhoeven 17739f8ea46SGeert Uytterhoeven static void charlcd_home(struct charlcd *lcd) 17839f8ea46SGeert Uytterhoeven { 17939f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = to_priv(lcd); 18039f8ea46SGeert Uytterhoeven 18139f8ea46SGeert Uytterhoeven priv->addr.x = 0; 18239f8ea46SGeert Uytterhoeven priv->addr.y = 0; 18339f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 18439f8ea46SGeert Uytterhoeven } 18539f8ea46SGeert Uytterhoeven 18639f8ea46SGeert Uytterhoeven static void charlcd_print(struct charlcd *lcd, char c) 18739f8ea46SGeert Uytterhoeven { 18839f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = to_priv(lcd); 18939f8ea46SGeert Uytterhoeven 19039f8ea46SGeert Uytterhoeven if (priv->addr.x < lcd->bwidth) { 19139f8ea46SGeert Uytterhoeven if (lcd->char_conv) 19239f8ea46SGeert Uytterhoeven c = lcd->char_conv[(unsigned char)c]; 19339f8ea46SGeert Uytterhoeven lcd->ops->write_data(lcd, c); 19439f8ea46SGeert Uytterhoeven priv->addr.x++; 19539f8ea46SGeert Uytterhoeven } 19639f8ea46SGeert Uytterhoeven /* prevents the cursor from wrapping onto the next line */ 19739f8ea46SGeert Uytterhoeven if (priv->addr.x == lcd->bwidth) 19839f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 19939f8ea46SGeert Uytterhoeven } 20039f8ea46SGeert Uytterhoeven 20139f8ea46SGeert Uytterhoeven static void charlcd_clear_fast(struct charlcd *lcd) 20239f8ea46SGeert Uytterhoeven { 20339f8ea46SGeert Uytterhoeven int pos; 20439f8ea46SGeert Uytterhoeven 20539f8ea46SGeert Uytterhoeven charlcd_home(lcd); 20639f8ea46SGeert Uytterhoeven 20739f8ea46SGeert Uytterhoeven if (lcd->ops->clear_fast) 20839f8ea46SGeert Uytterhoeven lcd->ops->clear_fast(lcd); 20939f8ea46SGeert Uytterhoeven else 2101d3b2af2SGeert Uytterhoeven for (pos = 0; pos < min(2, lcd->height) * lcd->hwidth; pos++) 21139f8ea46SGeert Uytterhoeven lcd->ops->write_data(lcd, ' '); 21239f8ea46SGeert Uytterhoeven 21339f8ea46SGeert Uytterhoeven charlcd_home(lcd); 21439f8ea46SGeert Uytterhoeven } 21539f8ea46SGeert Uytterhoeven 21639f8ea46SGeert Uytterhoeven /* clears the display and resets X/Y */ 21739f8ea46SGeert Uytterhoeven static void charlcd_clear_display(struct charlcd *lcd) 21839f8ea46SGeert Uytterhoeven { 21939f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = to_priv(lcd); 22039f8ea46SGeert Uytterhoeven 22139f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CLEAR); 22239f8ea46SGeert Uytterhoeven priv->addr.x = 0; 22339f8ea46SGeert Uytterhoeven priv->addr.y = 0; 22439f8ea46SGeert Uytterhoeven /* we must wait a few milliseconds (15) */ 22539f8ea46SGeert Uytterhoeven long_sleep(15); 22639f8ea46SGeert Uytterhoeven } 22739f8ea46SGeert Uytterhoeven 22839f8ea46SGeert Uytterhoeven static int charlcd_init_display(struct charlcd *lcd) 22939f8ea46SGeert Uytterhoeven { 230ac201479SGeert Uytterhoeven void (*write_cmd_raw)(struct charlcd *lcd, int cmd); 23139f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = to_priv(lcd); 232ac201479SGeert Uytterhoeven u8 init; 233ac201479SGeert Uytterhoeven 234ac201479SGeert Uytterhoeven if (lcd->ifwidth != 4 && lcd->ifwidth != 8) 235ac201479SGeert Uytterhoeven return -EINVAL; 23639f8ea46SGeert Uytterhoeven 23739f8ea46SGeert Uytterhoeven priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | 23839f8ea46SGeert Uytterhoeven LCD_FLAG_C | LCD_FLAG_B; 23939f8ea46SGeert Uytterhoeven 24039f8ea46SGeert Uytterhoeven long_sleep(20); /* wait 20 ms after power-up for the paranoid */ 24139f8ea46SGeert Uytterhoeven 242ac201479SGeert Uytterhoeven /* 243ac201479SGeert Uytterhoeven * 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure 244ac201479SGeert Uytterhoeven * the LCD is in 8-bit mode afterwards 245ac201479SGeert Uytterhoeven */ 246ac201479SGeert Uytterhoeven init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS; 247ac201479SGeert Uytterhoeven if (lcd->ifwidth == 4) { 248ac201479SGeert Uytterhoeven init >>= 4; 249ac201479SGeert Uytterhoeven write_cmd_raw = lcd->ops->write_cmd_raw4; 250ac201479SGeert Uytterhoeven } else { 251ac201479SGeert Uytterhoeven write_cmd_raw = lcd->ops->write_cmd; 252ac201479SGeert Uytterhoeven } 253ac201479SGeert Uytterhoeven write_cmd_raw(lcd, init); 25439f8ea46SGeert Uytterhoeven long_sleep(10); 255ac201479SGeert Uytterhoeven write_cmd_raw(lcd, init); 25639f8ea46SGeert Uytterhoeven long_sleep(10); 257ac201479SGeert Uytterhoeven write_cmd_raw(lcd, init); 25839f8ea46SGeert Uytterhoeven long_sleep(10); 25939f8ea46SGeert Uytterhoeven 260ac201479SGeert Uytterhoeven if (lcd->ifwidth == 4) { 261ac201479SGeert Uytterhoeven /* Switch to 4-bit mode, 1 line, small fonts */ 262ac201479SGeert Uytterhoeven lcd->ops->write_cmd_raw4(lcd, LCD_CMD_FUNCTION_SET >> 4); 263ac201479SGeert Uytterhoeven long_sleep(10); 264ac201479SGeert Uytterhoeven } 265ac201479SGeert Uytterhoeven 26639f8ea46SGeert Uytterhoeven /* set font height and lines number */ 26739f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, 268ac201479SGeert Uytterhoeven LCD_CMD_FUNCTION_SET | 269ac201479SGeert Uytterhoeven ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | 27039f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | 27139f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); 27239f8ea46SGeert Uytterhoeven long_sleep(10); 27339f8ea46SGeert Uytterhoeven 27439f8ea46SGeert Uytterhoeven /* display off, cursor off, blink off */ 27539f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CTRL); 27639f8ea46SGeert Uytterhoeven long_sleep(10); 27739f8ea46SGeert Uytterhoeven 27839f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, 27939f8ea46SGeert Uytterhoeven LCD_CMD_DISPLAY_CTRL | /* set display mode */ 28039f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | 28139f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | 28239f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); 28339f8ea46SGeert Uytterhoeven 28439f8ea46SGeert Uytterhoeven charlcd_backlight(lcd, (priv->flags & LCD_FLAG_L) ? 1 : 0); 28539f8ea46SGeert Uytterhoeven 28639f8ea46SGeert Uytterhoeven long_sleep(10); 28739f8ea46SGeert Uytterhoeven 28839f8ea46SGeert Uytterhoeven /* entry mode set : increment, cursor shifting */ 28939f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC); 29039f8ea46SGeert Uytterhoeven 29139f8ea46SGeert Uytterhoeven charlcd_clear_display(lcd); 29239f8ea46SGeert Uytterhoeven return 0; 29339f8ea46SGeert Uytterhoeven } 29439f8ea46SGeert Uytterhoeven 29539f8ea46SGeert Uytterhoeven /* 29639f8ea46SGeert Uytterhoeven * These are the file operation function for user access to /dev/lcd 29739f8ea46SGeert Uytterhoeven * This function can also be called from inside the kernel, by 29839f8ea46SGeert Uytterhoeven * setting file and ppos to NULL. 29939f8ea46SGeert Uytterhoeven * 30039f8ea46SGeert Uytterhoeven */ 30139f8ea46SGeert Uytterhoeven 30239f8ea46SGeert Uytterhoeven static inline int handle_lcd_special_code(struct charlcd *lcd) 30339f8ea46SGeert Uytterhoeven { 30439f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = to_priv(lcd); 30539f8ea46SGeert Uytterhoeven 30639f8ea46SGeert Uytterhoeven /* LCD special codes */ 30739f8ea46SGeert Uytterhoeven 30839f8ea46SGeert Uytterhoeven int processed = 0; 30939f8ea46SGeert Uytterhoeven 31039f8ea46SGeert Uytterhoeven char *esc = priv->esc_seq.buf + 2; 31139f8ea46SGeert Uytterhoeven int oldflags = priv->flags; 31239f8ea46SGeert Uytterhoeven 31339f8ea46SGeert Uytterhoeven /* check for display mode flags */ 31439f8ea46SGeert Uytterhoeven switch (*esc) { 31539f8ea46SGeert Uytterhoeven case 'D': /* Display ON */ 31639f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_D; 31739f8ea46SGeert Uytterhoeven processed = 1; 31839f8ea46SGeert Uytterhoeven break; 31939f8ea46SGeert Uytterhoeven case 'd': /* Display OFF */ 32039f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_D; 32139f8ea46SGeert Uytterhoeven processed = 1; 32239f8ea46SGeert Uytterhoeven break; 32339f8ea46SGeert Uytterhoeven case 'C': /* Cursor ON */ 32439f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_C; 32539f8ea46SGeert Uytterhoeven processed = 1; 32639f8ea46SGeert Uytterhoeven break; 32739f8ea46SGeert Uytterhoeven case 'c': /* Cursor OFF */ 32839f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_C; 32939f8ea46SGeert Uytterhoeven processed = 1; 33039f8ea46SGeert Uytterhoeven break; 33139f8ea46SGeert Uytterhoeven case 'B': /* Blink ON */ 33239f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_B; 33339f8ea46SGeert Uytterhoeven processed = 1; 33439f8ea46SGeert Uytterhoeven break; 33539f8ea46SGeert Uytterhoeven case 'b': /* Blink OFF */ 33639f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_B; 33739f8ea46SGeert Uytterhoeven processed = 1; 33839f8ea46SGeert Uytterhoeven break; 33939f8ea46SGeert Uytterhoeven case '+': /* Back light ON */ 34039f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_L; 34139f8ea46SGeert Uytterhoeven processed = 1; 34239f8ea46SGeert Uytterhoeven break; 34339f8ea46SGeert Uytterhoeven case '-': /* Back light OFF */ 34439f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_L; 34539f8ea46SGeert Uytterhoeven processed = 1; 34639f8ea46SGeert Uytterhoeven break; 34739f8ea46SGeert Uytterhoeven case '*': /* Flash back light */ 34839f8ea46SGeert Uytterhoeven charlcd_poke(lcd); 34939f8ea46SGeert Uytterhoeven processed = 1; 35039f8ea46SGeert Uytterhoeven break; 35139f8ea46SGeert Uytterhoeven case 'f': /* Small Font */ 35239f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_F; 35339f8ea46SGeert Uytterhoeven processed = 1; 35439f8ea46SGeert Uytterhoeven break; 35539f8ea46SGeert Uytterhoeven case 'F': /* Large Font */ 35639f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_F; 35739f8ea46SGeert Uytterhoeven processed = 1; 35839f8ea46SGeert Uytterhoeven break; 35939f8ea46SGeert Uytterhoeven case 'n': /* One Line */ 36039f8ea46SGeert Uytterhoeven priv->flags &= ~LCD_FLAG_N; 36139f8ea46SGeert Uytterhoeven processed = 1; 36239f8ea46SGeert Uytterhoeven break; 36339f8ea46SGeert Uytterhoeven case 'N': /* Two Lines */ 36439f8ea46SGeert Uytterhoeven priv->flags |= LCD_FLAG_N; 36539f8ea46SGeert Uytterhoeven break; 36639f8ea46SGeert Uytterhoeven case 'l': /* Shift Cursor Left */ 36739f8ea46SGeert Uytterhoeven if (priv->addr.x > 0) { 36839f8ea46SGeert Uytterhoeven /* back one char if not at end of line */ 36939f8ea46SGeert Uytterhoeven if (priv->addr.x < lcd->bwidth) 37039f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); 37139f8ea46SGeert Uytterhoeven priv->addr.x--; 37239f8ea46SGeert Uytterhoeven } 37339f8ea46SGeert Uytterhoeven processed = 1; 37439f8ea46SGeert Uytterhoeven break; 37539f8ea46SGeert Uytterhoeven case 'r': /* shift cursor right */ 37639f8ea46SGeert Uytterhoeven if (priv->addr.x < lcd->width) { 37739f8ea46SGeert Uytterhoeven /* allow the cursor to pass the end of the line */ 37839f8ea46SGeert Uytterhoeven if (priv->addr.x < (lcd->bwidth - 1)) 37939f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, 38039f8ea46SGeert Uytterhoeven LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT); 38139f8ea46SGeert Uytterhoeven priv->addr.x++; 38239f8ea46SGeert Uytterhoeven } 38339f8ea46SGeert Uytterhoeven processed = 1; 38439f8ea46SGeert Uytterhoeven break; 38539f8ea46SGeert Uytterhoeven case 'L': /* shift display left */ 38639f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT); 38739f8ea46SGeert Uytterhoeven processed = 1; 38839f8ea46SGeert Uytterhoeven break; 38939f8ea46SGeert Uytterhoeven case 'R': /* shift display right */ 39039f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, 39139f8ea46SGeert Uytterhoeven LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT | 39239f8ea46SGeert Uytterhoeven LCD_CMD_SHIFT_RIGHT); 39339f8ea46SGeert Uytterhoeven processed = 1; 39439f8ea46SGeert Uytterhoeven break; 39539f8ea46SGeert Uytterhoeven case 'k': { /* kill end of line */ 39639f8ea46SGeert Uytterhoeven int x; 39739f8ea46SGeert Uytterhoeven 39839f8ea46SGeert Uytterhoeven for (x = priv->addr.x; x < lcd->bwidth; x++) 39939f8ea46SGeert Uytterhoeven lcd->ops->write_data(lcd, ' '); 40039f8ea46SGeert Uytterhoeven 40139f8ea46SGeert Uytterhoeven /* restore cursor position */ 40239f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 40339f8ea46SGeert Uytterhoeven processed = 1; 40439f8ea46SGeert Uytterhoeven break; 40539f8ea46SGeert Uytterhoeven } 40639f8ea46SGeert Uytterhoeven case 'I': /* reinitialize display */ 40739f8ea46SGeert Uytterhoeven charlcd_init_display(lcd); 40839f8ea46SGeert Uytterhoeven processed = 1; 40939f8ea46SGeert Uytterhoeven break; 41039f8ea46SGeert Uytterhoeven case 'G': { 41139f8ea46SGeert Uytterhoeven /* Generator : LGcxxxxx...xx; must have <c> between '0' 41239f8ea46SGeert Uytterhoeven * and '7', representing the numerical ASCII code of the 41339f8ea46SGeert Uytterhoeven * redefined character, and <xx...xx> a sequence of 16 41439f8ea46SGeert Uytterhoeven * hex digits representing 8 bytes for each character. 41539f8ea46SGeert Uytterhoeven * Most LCDs will only use 5 lower bits of the 7 first 41639f8ea46SGeert Uytterhoeven * bytes. 41739f8ea46SGeert Uytterhoeven */ 41839f8ea46SGeert Uytterhoeven 41939f8ea46SGeert Uytterhoeven unsigned char cgbytes[8]; 42039f8ea46SGeert Uytterhoeven unsigned char cgaddr; 42139f8ea46SGeert Uytterhoeven int cgoffset; 42239f8ea46SGeert Uytterhoeven int shift; 42339f8ea46SGeert Uytterhoeven char value; 42439f8ea46SGeert Uytterhoeven int addr; 42539f8ea46SGeert Uytterhoeven 42639f8ea46SGeert Uytterhoeven if (!strchr(esc, ';')) 42739f8ea46SGeert Uytterhoeven break; 42839f8ea46SGeert Uytterhoeven 42939f8ea46SGeert Uytterhoeven esc++; 43039f8ea46SGeert Uytterhoeven 43139f8ea46SGeert Uytterhoeven cgaddr = *(esc++) - '0'; 43239f8ea46SGeert Uytterhoeven if (cgaddr > 7) { 43339f8ea46SGeert Uytterhoeven processed = 1; 43439f8ea46SGeert Uytterhoeven break; 43539f8ea46SGeert Uytterhoeven } 43639f8ea46SGeert Uytterhoeven 43739f8ea46SGeert Uytterhoeven cgoffset = 0; 43839f8ea46SGeert Uytterhoeven shift = 0; 43939f8ea46SGeert Uytterhoeven value = 0; 44039f8ea46SGeert Uytterhoeven while (*esc && cgoffset < 8) { 44139f8ea46SGeert Uytterhoeven shift ^= 4; 44239f8ea46SGeert Uytterhoeven if (*esc >= '0' && *esc <= '9') { 44339f8ea46SGeert Uytterhoeven value |= (*esc - '0') << shift; 44439f8ea46SGeert Uytterhoeven } else if (*esc >= 'A' && *esc <= 'Z') { 44539f8ea46SGeert Uytterhoeven value |= (*esc - 'A' + 10) << shift; 44639f8ea46SGeert Uytterhoeven } else if (*esc >= 'a' && *esc <= 'z') { 44739f8ea46SGeert Uytterhoeven value |= (*esc - 'a' + 10) << shift; 44839f8ea46SGeert Uytterhoeven } else { 44939f8ea46SGeert Uytterhoeven esc++; 45039f8ea46SGeert Uytterhoeven continue; 45139f8ea46SGeert Uytterhoeven } 45239f8ea46SGeert Uytterhoeven 45339f8ea46SGeert Uytterhoeven if (shift == 0) { 45439f8ea46SGeert Uytterhoeven cgbytes[cgoffset++] = value; 45539f8ea46SGeert Uytterhoeven value = 0; 45639f8ea46SGeert Uytterhoeven } 45739f8ea46SGeert Uytterhoeven 45839f8ea46SGeert Uytterhoeven esc++; 45939f8ea46SGeert Uytterhoeven } 46039f8ea46SGeert Uytterhoeven 46139f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8)); 46239f8ea46SGeert Uytterhoeven for (addr = 0; addr < cgoffset; addr++) 46339f8ea46SGeert Uytterhoeven lcd->ops->write_data(lcd, cgbytes[addr]); 46439f8ea46SGeert Uytterhoeven 46539f8ea46SGeert Uytterhoeven /* ensures that we stop writing to CGRAM */ 46639f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 46739f8ea46SGeert Uytterhoeven processed = 1; 46839f8ea46SGeert Uytterhoeven break; 46939f8ea46SGeert Uytterhoeven } 47039f8ea46SGeert Uytterhoeven case 'x': /* gotoxy : LxXXX[yYYY]; */ 47139f8ea46SGeert Uytterhoeven case 'y': /* gotoxy : LyYYY[xXXX]; */ 47239f8ea46SGeert Uytterhoeven if (!strchr(esc, ';')) 47339f8ea46SGeert Uytterhoeven break; 47439f8ea46SGeert Uytterhoeven 47539f8ea46SGeert Uytterhoeven while (*esc) { 47639f8ea46SGeert Uytterhoeven if (*esc == 'x') { 47739f8ea46SGeert Uytterhoeven esc++; 47839f8ea46SGeert Uytterhoeven if (kstrtoul(esc, 10, &priv->addr.x) < 0) 47939f8ea46SGeert Uytterhoeven break; 48039f8ea46SGeert Uytterhoeven } else if (*esc == 'y') { 48139f8ea46SGeert Uytterhoeven esc++; 48239f8ea46SGeert Uytterhoeven if (kstrtoul(esc, 10, &priv->addr.y) < 0) 48339f8ea46SGeert Uytterhoeven break; 48439f8ea46SGeert Uytterhoeven } else { 48539f8ea46SGeert Uytterhoeven break; 48639f8ea46SGeert Uytterhoeven } 48739f8ea46SGeert Uytterhoeven } 48839f8ea46SGeert Uytterhoeven 48939f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 49039f8ea46SGeert Uytterhoeven processed = 1; 49139f8ea46SGeert Uytterhoeven break; 49239f8ea46SGeert Uytterhoeven } 49339f8ea46SGeert Uytterhoeven 49439f8ea46SGeert Uytterhoeven /* TODO: This indent party here got ugly, clean it! */ 49539f8ea46SGeert Uytterhoeven /* Check whether one flag was changed */ 49639f8ea46SGeert Uytterhoeven if (oldflags == priv->flags) 49739f8ea46SGeert Uytterhoeven return processed; 49839f8ea46SGeert Uytterhoeven 49939f8ea46SGeert Uytterhoeven /* check whether one of B,C,D flags were changed */ 50039f8ea46SGeert Uytterhoeven if ((oldflags ^ priv->flags) & 50139f8ea46SGeert Uytterhoeven (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D)) 50239f8ea46SGeert Uytterhoeven /* set display mode */ 50339f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, 50439f8ea46SGeert Uytterhoeven LCD_CMD_DISPLAY_CTRL | 50539f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | 50639f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | 50739f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); 50839f8ea46SGeert Uytterhoeven /* check whether one of F,N flags was changed */ 50939f8ea46SGeert Uytterhoeven else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N)) 51039f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, 511ac201479SGeert Uytterhoeven LCD_CMD_FUNCTION_SET | 512ac201479SGeert Uytterhoeven ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | 51339f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | 51439f8ea46SGeert Uytterhoeven ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); 51539f8ea46SGeert Uytterhoeven /* check whether L flag was changed */ 51639f8ea46SGeert Uytterhoeven else if ((oldflags ^ priv->flags) & LCD_FLAG_L) 51739f8ea46SGeert Uytterhoeven charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L)); 51839f8ea46SGeert Uytterhoeven 51939f8ea46SGeert Uytterhoeven return processed; 52039f8ea46SGeert Uytterhoeven } 52139f8ea46SGeert Uytterhoeven 52239f8ea46SGeert Uytterhoeven static void charlcd_write_char(struct charlcd *lcd, char c) 52339f8ea46SGeert Uytterhoeven { 52439f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = to_priv(lcd); 52539f8ea46SGeert Uytterhoeven 52639f8ea46SGeert Uytterhoeven /* first, we'll test if we're in escape mode */ 52739f8ea46SGeert Uytterhoeven if ((c != '\n') && priv->esc_seq.len >= 0) { 52839f8ea46SGeert Uytterhoeven /* yes, let's add this char to the buffer */ 52939f8ea46SGeert Uytterhoeven priv->esc_seq.buf[priv->esc_seq.len++] = c; 53039f8ea46SGeert Uytterhoeven priv->esc_seq.buf[priv->esc_seq.len] = 0; 53139f8ea46SGeert Uytterhoeven } else { 53239f8ea46SGeert Uytterhoeven /* aborts any previous escape sequence */ 53339f8ea46SGeert Uytterhoeven priv->esc_seq.len = -1; 53439f8ea46SGeert Uytterhoeven 53539f8ea46SGeert Uytterhoeven switch (c) { 53639f8ea46SGeert Uytterhoeven case LCD_ESCAPE_CHAR: 53739f8ea46SGeert Uytterhoeven /* start of an escape sequence */ 53839f8ea46SGeert Uytterhoeven priv->esc_seq.len = 0; 53939f8ea46SGeert Uytterhoeven priv->esc_seq.buf[priv->esc_seq.len] = 0; 54039f8ea46SGeert Uytterhoeven break; 54139f8ea46SGeert Uytterhoeven case '\b': 54239f8ea46SGeert Uytterhoeven /* go back one char and clear it */ 54339f8ea46SGeert Uytterhoeven if (priv->addr.x > 0) { 54439f8ea46SGeert Uytterhoeven /* 54539f8ea46SGeert Uytterhoeven * check if we're not at the 54639f8ea46SGeert Uytterhoeven * end of the line 54739f8ea46SGeert Uytterhoeven */ 54839f8ea46SGeert Uytterhoeven if (priv->addr.x < lcd->bwidth) 54939f8ea46SGeert Uytterhoeven /* back one char */ 55039f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); 55139f8ea46SGeert Uytterhoeven priv->addr.x--; 55239f8ea46SGeert Uytterhoeven } 55339f8ea46SGeert Uytterhoeven /* replace with a space */ 55439f8ea46SGeert Uytterhoeven lcd->ops->write_data(lcd, ' '); 55539f8ea46SGeert Uytterhoeven /* back one char again */ 55639f8ea46SGeert Uytterhoeven lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); 55739f8ea46SGeert Uytterhoeven break; 55839f8ea46SGeert Uytterhoeven case '\014': 55939f8ea46SGeert Uytterhoeven /* quickly clear the display */ 56039f8ea46SGeert Uytterhoeven charlcd_clear_fast(lcd); 56139f8ea46SGeert Uytterhoeven break; 56239f8ea46SGeert Uytterhoeven case '\n': 56339f8ea46SGeert Uytterhoeven /* 56439f8ea46SGeert Uytterhoeven * flush the remainder of the current line and 56539f8ea46SGeert Uytterhoeven * go to the beginning of the next line 56639f8ea46SGeert Uytterhoeven */ 56739f8ea46SGeert Uytterhoeven for (; priv->addr.x < lcd->bwidth; priv->addr.x++) 56839f8ea46SGeert Uytterhoeven lcd->ops->write_data(lcd, ' '); 56939f8ea46SGeert Uytterhoeven priv->addr.x = 0; 57039f8ea46SGeert Uytterhoeven priv->addr.y = (priv->addr.y + 1) % lcd->height; 57139f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 57239f8ea46SGeert Uytterhoeven break; 57339f8ea46SGeert Uytterhoeven case '\r': 57439f8ea46SGeert Uytterhoeven /* go to the beginning of the same line */ 57539f8ea46SGeert Uytterhoeven priv->addr.x = 0; 57639f8ea46SGeert Uytterhoeven charlcd_gotoxy(lcd); 57739f8ea46SGeert Uytterhoeven break; 57839f8ea46SGeert Uytterhoeven case '\t': 57939f8ea46SGeert Uytterhoeven /* print a space instead of the tab */ 58039f8ea46SGeert Uytterhoeven charlcd_print(lcd, ' '); 58139f8ea46SGeert Uytterhoeven break; 58239f8ea46SGeert Uytterhoeven default: 58339f8ea46SGeert Uytterhoeven /* simply print this char */ 58439f8ea46SGeert Uytterhoeven charlcd_print(lcd, c); 58539f8ea46SGeert Uytterhoeven break; 58639f8ea46SGeert Uytterhoeven } 58739f8ea46SGeert Uytterhoeven } 58839f8ea46SGeert Uytterhoeven 58939f8ea46SGeert Uytterhoeven /* 59039f8ea46SGeert Uytterhoeven * now we'll see if we're in an escape mode and if the current 59139f8ea46SGeert Uytterhoeven * escape sequence can be understood. 59239f8ea46SGeert Uytterhoeven */ 59339f8ea46SGeert Uytterhoeven if (priv->esc_seq.len >= 2) { 59439f8ea46SGeert Uytterhoeven int processed = 0; 59539f8ea46SGeert Uytterhoeven 59639f8ea46SGeert Uytterhoeven if (!strcmp(priv->esc_seq.buf, "[2J")) { 59739f8ea46SGeert Uytterhoeven /* clear the display */ 59839f8ea46SGeert Uytterhoeven charlcd_clear_fast(lcd); 59939f8ea46SGeert Uytterhoeven processed = 1; 60039f8ea46SGeert Uytterhoeven } else if (!strcmp(priv->esc_seq.buf, "[H")) { 60139f8ea46SGeert Uytterhoeven /* cursor to home */ 60239f8ea46SGeert Uytterhoeven charlcd_home(lcd); 60339f8ea46SGeert Uytterhoeven processed = 1; 60439f8ea46SGeert Uytterhoeven } 60539f8ea46SGeert Uytterhoeven /* codes starting with ^[[L */ 60639f8ea46SGeert Uytterhoeven else if ((priv->esc_seq.len >= 3) && 60739f8ea46SGeert Uytterhoeven (priv->esc_seq.buf[0] == '[') && 60839f8ea46SGeert Uytterhoeven (priv->esc_seq.buf[1] == 'L')) { 60939f8ea46SGeert Uytterhoeven processed = handle_lcd_special_code(lcd); 61039f8ea46SGeert Uytterhoeven } 61139f8ea46SGeert Uytterhoeven 61239f8ea46SGeert Uytterhoeven /* LCD special escape codes */ 61339f8ea46SGeert Uytterhoeven /* 61439f8ea46SGeert Uytterhoeven * flush the escape sequence if it's been processed 61539f8ea46SGeert Uytterhoeven * or if it is getting too long. 61639f8ea46SGeert Uytterhoeven */ 61739f8ea46SGeert Uytterhoeven if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN)) 61839f8ea46SGeert Uytterhoeven priv->esc_seq.len = -1; 61939f8ea46SGeert Uytterhoeven } /* escape codes */ 62039f8ea46SGeert Uytterhoeven } 62139f8ea46SGeert Uytterhoeven 62239f8ea46SGeert Uytterhoeven static struct charlcd *the_charlcd; 62339f8ea46SGeert Uytterhoeven 62439f8ea46SGeert Uytterhoeven static ssize_t charlcd_write(struct file *file, const char __user *buf, 62539f8ea46SGeert Uytterhoeven size_t count, loff_t *ppos) 62639f8ea46SGeert Uytterhoeven { 62739f8ea46SGeert Uytterhoeven const char __user *tmp = buf; 62839f8ea46SGeert Uytterhoeven char c; 62939f8ea46SGeert Uytterhoeven 63039f8ea46SGeert Uytterhoeven for (; count-- > 0; (*ppos)++, tmp++) { 63139f8ea46SGeert Uytterhoeven if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) 63239f8ea46SGeert Uytterhoeven /* 63339f8ea46SGeert Uytterhoeven * let's be a little nice with other processes 63439f8ea46SGeert Uytterhoeven * that need some CPU 63539f8ea46SGeert Uytterhoeven */ 63639f8ea46SGeert Uytterhoeven schedule(); 63739f8ea46SGeert Uytterhoeven 63839f8ea46SGeert Uytterhoeven if (get_user(c, tmp)) 63939f8ea46SGeert Uytterhoeven return -EFAULT; 64039f8ea46SGeert Uytterhoeven 64139f8ea46SGeert Uytterhoeven charlcd_write_char(the_charlcd, c); 64239f8ea46SGeert Uytterhoeven } 64339f8ea46SGeert Uytterhoeven 64439f8ea46SGeert Uytterhoeven return tmp - buf; 64539f8ea46SGeert Uytterhoeven } 64639f8ea46SGeert Uytterhoeven 64739f8ea46SGeert Uytterhoeven static int charlcd_open(struct inode *inode, struct file *file) 64839f8ea46SGeert Uytterhoeven { 64939f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = to_priv(the_charlcd); 65039f8ea46SGeert Uytterhoeven 65139f8ea46SGeert Uytterhoeven if (!atomic_dec_and_test(&charlcd_available)) 65239f8ea46SGeert Uytterhoeven return -EBUSY; /* open only once at a time */ 65339f8ea46SGeert Uytterhoeven 65439f8ea46SGeert Uytterhoeven if (file->f_mode & FMODE_READ) /* device is write-only */ 65539f8ea46SGeert Uytterhoeven return -EPERM; 65639f8ea46SGeert Uytterhoeven 65739f8ea46SGeert Uytterhoeven if (priv->must_clear) { 65839f8ea46SGeert Uytterhoeven charlcd_clear_display(&priv->lcd); 65939f8ea46SGeert Uytterhoeven priv->must_clear = false; 66039f8ea46SGeert Uytterhoeven } 66139f8ea46SGeert Uytterhoeven return nonseekable_open(inode, file); 66239f8ea46SGeert Uytterhoeven } 66339f8ea46SGeert Uytterhoeven 66439f8ea46SGeert Uytterhoeven static int charlcd_release(struct inode *inode, struct file *file) 66539f8ea46SGeert Uytterhoeven { 66639f8ea46SGeert Uytterhoeven atomic_inc(&charlcd_available); 66739f8ea46SGeert Uytterhoeven return 0; 66839f8ea46SGeert Uytterhoeven } 66939f8ea46SGeert Uytterhoeven 67039f8ea46SGeert Uytterhoeven static const struct file_operations charlcd_fops = { 67139f8ea46SGeert Uytterhoeven .write = charlcd_write, 67239f8ea46SGeert Uytterhoeven .open = charlcd_open, 67339f8ea46SGeert Uytterhoeven .release = charlcd_release, 67439f8ea46SGeert Uytterhoeven .llseek = no_llseek, 67539f8ea46SGeert Uytterhoeven }; 67639f8ea46SGeert Uytterhoeven 67739f8ea46SGeert Uytterhoeven static struct miscdevice charlcd_dev = { 67839f8ea46SGeert Uytterhoeven .minor = LCD_MINOR, 67939f8ea46SGeert Uytterhoeven .name = "lcd", 68039f8ea46SGeert Uytterhoeven .fops = &charlcd_fops, 68139f8ea46SGeert Uytterhoeven }; 68239f8ea46SGeert Uytterhoeven 68339f8ea46SGeert Uytterhoeven static void charlcd_puts(struct charlcd *lcd, const char *s) 68439f8ea46SGeert Uytterhoeven { 68539f8ea46SGeert Uytterhoeven const char *tmp = s; 68639f8ea46SGeert Uytterhoeven int count = strlen(s); 68739f8ea46SGeert Uytterhoeven 68839f8ea46SGeert Uytterhoeven for (; count-- > 0; tmp++) { 68939f8ea46SGeert Uytterhoeven if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) 69039f8ea46SGeert Uytterhoeven /* 69139f8ea46SGeert Uytterhoeven * let's be a little nice with other processes 69239f8ea46SGeert Uytterhoeven * that need some CPU 69339f8ea46SGeert Uytterhoeven */ 69439f8ea46SGeert Uytterhoeven schedule(); 69539f8ea46SGeert Uytterhoeven 69639f8ea46SGeert Uytterhoeven charlcd_write_char(lcd, *tmp); 69739f8ea46SGeert Uytterhoeven } 69839f8ea46SGeert Uytterhoeven } 69939f8ea46SGeert Uytterhoeven 70039f8ea46SGeert Uytterhoeven /* initialize the LCD driver */ 70139f8ea46SGeert Uytterhoeven static int charlcd_init(struct charlcd *lcd) 70239f8ea46SGeert Uytterhoeven { 70339f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = to_priv(lcd); 70439f8ea46SGeert Uytterhoeven int ret; 70539f8ea46SGeert Uytterhoeven 70639f8ea46SGeert Uytterhoeven if (lcd->ops->backlight) { 70739f8ea46SGeert Uytterhoeven mutex_init(&priv->bl_tempo_lock); 70839f8ea46SGeert Uytterhoeven INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off); 70939f8ea46SGeert Uytterhoeven } 71039f8ea46SGeert Uytterhoeven 71139f8ea46SGeert Uytterhoeven /* 71239f8ea46SGeert Uytterhoeven * before this line, we must NOT send anything to the display. 71339f8ea46SGeert Uytterhoeven * Since charlcd_init_display() needs to write data, we have to 71439f8ea46SGeert Uytterhoeven * enable mark the LCD initialized just before. 71539f8ea46SGeert Uytterhoeven */ 71639f8ea46SGeert Uytterhoeven ret = charlcd_init_display(lcd); 71739f8ea46SGeert Uytterhoeven if (ret) 71839f8ea46SGeert Uytterhoeven return ret; 71939f8ea46SGeert Uytterhoeven 72039f8ea46SGeert Uytterhoeven /* display a short message */ 72139f8ea46SGeert Uytterhoeven #ifdef CONFIG_PANEL_CHANGE_MESSAGE 72239f8ea46SGeert Uytterhoeven #ifdef CONFIG_PANEL_BOOT_MESSAGE 72339f8ea46SGeert Uytterhoeven charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE); 72439f8ea46SGeert Uytterhoeven #endif 72539f8ea46SGeert Uytterhoeven #else 72639f8ea46SGeert Uytterhoeven charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE "\n"); 72739f8ea46SGeert Uytterhoeven #endif 72839f8ea46SGeert Uytterhoeven /* clear the display on the next device opening */ 72939f8ea46SGeert Uytterhoeven priv->must_clear = true; 73039f8ea46SGeert Uytterhoeven charlcd_home(lcd); 73139f8ea46SGeert Uytterhoeven return 0; 73239f8ea46SGeert Uytterhoeven } 73339f8ea46SGeert Uytterhoeven 73439f8ea46SGeert Uytterhoeven struct charlcd *charlcd_alloc(unsigned int drvdata_size) 73539f8ea46SGeert Uytterhoeven { 73639f8ea46SGeert Uytterhoeven struct charlcd_priv *priv; 73739f8ea46SGeert Uytterhoeven struct charlcd *lcd; 73839f8ea46SGeert Uytterhoeven 73939f8ea46SGeert Uytterhoeven priv = kzalloc(sizeof(*priv) + drvdata_size, GFP_KERNEL); 74039f8ea46SGeert Uytterhoeven if (!priv) 74139f8ea46SGeert Uytterhoeven return NULL; 74239f8ea46SGeert Uytterhoeven 74339f8ea46SGeert Uytterhoeven priv->esc_seq.len = -1; 74439f8ea46SGeert Uytterhoeven 74539f8ea46SGeert Uytterhoeven lcd = &priv->lcd; 746ac201479SGeert Uytterhoeven lcd->ifwidth = 8; 74739f8ea46SGeert Uytterhoeven lcd->bwidth = DEFAULT_LCD_BWIDTH; 74839f8ea46SGeert Uytterhoeven lcd->hwidth = DEFAULT_LCD_HWIDTH; 74939f8ea46SGeert Uytterhoeven lcd->drvdata = priv->drvdata; 75039f8ea46SGeert Uytterhoeven 75139f8ea46SGeert Uytterhoeven return lcd; 75239f8ea46SGeert Uytterhoeven } 75339f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_alloc); 75439f8ea46SGeert Uytterhoeven 75539f8ea46SGeert Uytterhoeven static int panel_notify_sys(struct notifier_block *this, unsigned long code, 75639f8ea46SGeert Uytterhoeven void *unused) 75739f8ea46SGeert Uytterhoeven { 75839f8ea46SGeert Uytterhoeven struct charlcd *lcd = the_charlcd; 75939f8ea46SGeert Uytterhoeven 76039f8ea46SGeert Uytterhoeven switch (code) { 76139f8ea46SGeert Uytterhoeven case SYS_DOWN: 76239f8ea46SGeert Uytterhoeven charlcd_puts(lcd, 76339f8ea46SGeert Uytterhoeven "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); 76439f8ea46SGeert Uytterhoeven break; 76539f8ea46SGeert Uytterhoeven case SYS_HALT: 76639f8ea46SGeert Uytterhoeven charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); 76739f8ea46SGeert Uytterhoeven break; 76839f8ea46SGeert Uytterhoeven case SYS_POWER_OFF: 76939f8ea46SGeert Uytterhoeven charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); 77039f8ea46SGeert Uytterhoeven break; 77139f8ea46SGeert Uytterhoeven default: 77239f8ea46SGeert Uytterhoeven break; 77339f8ea46SGeert Uytterhoeven } 77439f8ea46SGeert Uytterhoeven return NOTIFY_DONE; 77539f8ea46SGeert Uytterhoeven } 77639f8ea46SGeert Uytterhoeven 77739f8ea46SGeert Uytterhoeven static struct notifier_block panel_notifier = { 77839f8ea46SGeert Uytterhoeven panel_notify_sys, 77939f8ea46SGeert Uytterhoeven NULL, 78039f8ea46SGeert Uytterhoeven 0 78139f8ea46SGeert Uytterhoeven }; 78239f8ea46SGeert Uytterhoeven 78339f8ea46SGeert Uytterhoeven int charlcd_register(struct charlcd *lcd) 78439f8ea46SGeert Uytterhoeven { 78539f8ea46SGeert Uytterhoeven int ret; 78639f8ea46SGeert Uytterhoeven 78739f8ea46SGeert Uytterhoeven ret = charlcd_init(lcd); 78839f8ea46SGeert Uytterhoeven if (ret) 78939f8ea46SGeert Uytterhoeven return ret; 79039f8ea46SGeert Uytterhoeven 79139f8ea46SGeert Uytterhoeven ret = misc_register(&charlcd_dev); 79239f8ea46SGeert Uytterhoeven if (ret) 79339f8ea46SGeert Uytterhoeven return ret; 79439f8ea46SGeert Uytterhoeven 79539f8ea46SGeert Uytterhoeven the_charlcd = lcd; 79639f8ea46SGeert Uytterhoeven register_reboot_notifier(&panel_notifier); 79739f8ea46SGeert Uytterhoeven return 0; 79839f8ea46SGeert Uytterhoeven } 79939f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_register); 80039f8ea46SGeert Uytterhoeven 80139f8ea46SGeert Uytterhoeven int charlcd_unregister(struct charlcd *lcd) 80239f8ea46SGeert Uytterhoeven { 80339f8ea46SGeert Uytterhoeven struct charlcd_priv *priv = to_priv(lcd); 80439f8ea46SGeert Uytterhoeven 80539f8ea46SGeert Uytterhoeven unregister_reboot_notifier(&panel_notifier); 80639f8ea46SGeert Uytterhoeven charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-"); 80739f8ea46SGeert Uytterhoeven misc_deregister(&charlcd_dev); 80839f8ea46SGeert Uytterhoeven the_charlcd = NULL; 80939f8ea46SGeert Uytterhoeven if (lcd->ops->backlight) { 81039f8ea46SGeert Uytterhoeven cancel_delayed_work_sync(&priv->bl_work); 81139f8ea46SGeert Uytterhoeven priv->lcd.ops->backlight(&priv->lcd, 0); 81239f8ea46SGeert Uytterhoeven } 81339f8ea46SGeert Uytterhoeven 81439f8ea46SGeert Uytterhoeven return 0; 81539f8ea46SGeert Uytterhoeven } 81639f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_unregister); 81739f8ea46SGeert Uytterhoeven 81839f8ea46SGeert Uytterhoeven MODULE_LICENSE("GPL"); 819