2018-02-17 22:33:13 +03:00
// SPDX-License-Identifier: GPL-2.0+
2017-03-10 17:15:17 +03:00
/*
* Character LCD driver for Linux
*
* Copyright ( C ) 2000 - 2008 , Willy Tarreau < w @ 1 wt . eu >
* Copyright ( C ) 2016 - 2017 Glider bvba
*/
# include <linux/atomic.h>
2018-02-28 01:09:52 +03:00
# include <linux/ctype.h>
2017-03-10 17:15:17 +03:00
# include <linux/delay.h>
# include <linux/fs.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/notifier.h>
# include <linux/reboot.h>
# include <linux/slab.h>
# include <linux/uaccess.h>
# include <linux/workqueue.h>
# include <generated/utsrelease.h>
2019-08-06 10:14:44 +03:00
# include "charlcd.h"
2017-03-10 17:15:17 +03:00
# define LCD_MINOR 156
# define DEFAULT_LCD_BWIDTH 40
# define DEFAULT_LCD_HWIDTH 64
/* Keep the backlight on this many seconds for each flash */
# define LCD_BL_TEMPO_PERIOD 4
# define LCD_FLAG_B 0x0004 /* Blink on */
# define LCD_FLAG_C 0x0008 /* Cursor on */
# define LCD_FLAG_D 0x0010 /* Display on */
# define LCD_FLAG_F 0x0020 /* Large font mode */
# define LCD_FLAG_N 0x0040 /* 2-rows mode */
# define LCD_FLAG_L 0x0080 /* Backlight enabled */
/* LCD commands */
# define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */
# define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */
# define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */
# define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */
# define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */
# define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */
# define LCD_CMD_BLINK_ON 0x01 /* Set blink on */
# define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */
# define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */
# define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */
# define LCD_CMD_FUNCTION_SET 0x20 /* Set function */
# define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */
# define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */
# define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */
# define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */
# define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */
# define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */
# define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */
struct charlcd_priv {
struct charlcd lcd ;
struct delayed_work bl_work ;
struct mutex bl_tempo_lock ; /* Protects access to bl_tempo */
bool bl_tempo ;
bool must_clear ;
/* contains the LCD config state */
unsigned long int flags ;
/* Contains the LCD X and Y offset */
struct {
unsigned long int x ;
unsigned long int y ;
} addr ;
/* Current escape sequence and it's length or -1 if outside */
struct {
char buf [ LCD_ESCAPE_LEN + 1 ] ;
int len ;
} esc_seq ;
unsigned long long drvdata [ 0 ] ;
} ;
2019-03-12 17:44:29 +03:00
# define charlcd_to_priv(p) container_of(p, struct charlcd_priv, lcd)
2017-03-10 17:15:17 +03:00
/* Device single-open policy control */
static atomic_t charlcd_available = ATOMIC_INIT ( 1 ) ;
/* sleeps that many milliseconds with a reschedule */
static void long_sleep ( int ms )
{
2018-01-26 18:19:15 +03:00
schedule_timeout_interruptible ( msecs_to_jiffies ( ms ) ) ;
2017-03-10 17:15:17 +03:00
}
/* turn the backlight on or off */
static void charlcd_backlight ( struct charlcd * lcd , int on )
{
2019-03-12 17:44:29 +03:00
struct charlcd_priv * priv = charlcd_to_priv ( lcd ) ;
2017-03-10 17:15:17 +03:00
if ( ! lcd - > ops - > backlight )
return ;
mutex_lock ( & priv - > bl_tempo_lock ) ;
if ( ! priv - > bl_tempo )
lcd - > ops - > backlight ( lcd , on ) ;
mutex_unlock ( & priv - > bl_tempo_lock ) ;
}
static void charlcd_bl_off ( struct work_struct * work )
{
struct delayed_work * dwork = to_delayed_work ( work ) ;
struct charlcd_priv * priv =
container_of ( dwork , struct charlcd_priv , bl_work ) ;
mutex_lock ( & priv - > bl_tempo_lock ) ;
if ( priv - > bl_tempo ) {
priv - > bl_tempo = false ;
if ( ! ( priv - > flags & LCD_FLAG_L ) )
priv - > lcd . ops - > backlight ( & priv - > lcd , 0 ) ;
}
mutex_unlock ( & priv - > bl_tempo_lock ) ;
}
/* turn the backlight on for a little while */
void charlcd_poke ( struct charlcd * lcd )
{
2019-03-12 17:44:29 +03:00
struct charlcd_priv * priv = charlcd_to_priv ( lcd ) ;
2017-03-10 17:15:17 +03:00
if ( ! lcd - > ops - > backlight )
return ;
cancel_delayed_work_sync ( & priv - > bl_work ) ;
mutex_lock ( & priv - > bl_tempo_lock ) ;
if ( ! priv - > bl_tempo & & ! ( priv - > flags & LCD_FLAG_L ) )
lcd - > ops - > backlight ( lcd , 1 ) ;
priv - > bl_tempo = true ;
schedule_delayed_work ( & priv - > bl_work , LCD_BL_TEMPO_PERIOD * HZ ) ;
mutex_unlock ( & priv - > bl_tempo_lock ) ;
}
EXPORT_SYMBOL_GPL ( charlcd_poke ) ;
static void charlcd_gotoxy ( struct charlcd * lcd )
{
2019-03-12 17:44:29 +03:00
struct charlcd_priv * priv = charlcd_to_priv ( lcd ) ;
2017-03-10 17:15:19 +03:00
unsigned int addr ;
/*
* we force the cursor to stay at the end of the
* line if it wants to go farther
*/
addr = priv - > addr . x < lcd - > bwidth ? priv - > addr . x & ( lcd - > hwidth - 1 )
: lcd - > bwidth - 1 ;
if ( priv - > addr . y & 1 )
addr + = lcd - > hwidth ;
if ( priv - > addr . y & 2 )
addr + = lcd - > bwidth ;
lcd - > ops - > write_cmd ( lcd , LCD_CMD_SET_DDRAM_ADDR | addr ) ;
2017-03-10 17:15:17 +03:00
}
static void charlcd_home ( struct charlcd * lcd )
{
2019-03-12 17:44:29 +03:00
struct charlcd_priv * priv = charlcd_to_priv ( lcd ) ;
2017-03-10 17:15:17 +03:00
priv - > addr . x = 0 ;
priv - > addr . y = 0 ;
charlcd_gotoxy ( lcd ) ;
}
static void charlcd_print ( struct charlcd * lcd , char c )
{
2019-03-12 17:44:29 +03:00
struct charlcd_priv * priv = charlcd_to_priv ( lcd ) ;
2017-03-10 17:15:17 +03:00
if ( priv - > addr . x < lcd - > bwidth ) {
if ( lcd - > char_conv )
c = lcd - > char_conv [ ( unsigned char ) c ] ;
lcd - > ops - > write_data ( lcd , c ) ;
priv - > addr . x + + ;
2018-01-15 12:43:48 +03:00
/* prevents the cursor from wrapping onto the next line */
if ( priv - > addr . x = = lcd - > bwidth )
charlcd_gotoxy ( lcd ) ;
2017-03-10 17:15:17 +03:00
}
}
static void charlcd_clear_fast ( struct charlcd * lcd )
{
int pos ;
charlcd_home ( lcd ) ;
if ( lcd - > ops - > clear_fast )
lcd - > ops - > clear_fast ( lcd ) ;
else
2017-03-10 17:15:19 +03:00
for ( pos = 0 ; pos < min ( 2 , lcd - > height ) * lcd - > hwidth ; pos + + )
2017-03-10 17:15:17 +03:00
lcd - > ops - > write_data ( lcd , ' ' ) ;
charlcd_home ( lcd ) ;
}
/* clears the display and resets X/Y */
static void charlcd_clear_display ( struct charlcd * lcd )
{
2019-03-12 17:44:29 +03:00
struct charlcd_priv * priv = charlcd_to_priv ( lcd ) ;
2017-03-10 17:15:17 +03:00
lcd - > ops - > write_cmd ( lcd , LCD_CMD_DISPLAY_CLEAR ) ;
priv - > addr . x = 0 ;
priv - > addr . y = 0 ;
/* we must wait a few milliseconds (15) */
long_sleep ( 15 ) ;
}
static int charlcd_init_display ( struct charlcd * lcd )
{
2017-03-10 17:15:18 +03:00
void ( * write_cmd_raw ) ( struct charlcd * lcd , int cmd ) ;
2019-03-12 17:44:29 +03:00
struct charlcd_priv * priv = charlcd_to_priv ( lcd ) ;
2017-03-10 17:15:18 +03:00
u8 init ;
if ( lcd - > ifwidth ! = 4 & & lcd - > ifwidth ! = 8 )
return - EINVAL ;
2017-03-10 17:15:17 +03:00
priv - > flags = ( ( lcd - > height > 1 ) ? LCD_FLAG_N : 0 ) | LCD_FLAG_D |
LCD_FLAG_C | LCD_FLAG_B ;
long_sleep ( 20 ) ; /* wait 20 ms after power-up for the paranoid */
2017-03-10 17:15:18 +03:00
/*
* 8 - bit mode , 1 line , small fonts ; let ' s do it 3 times , to make sure
* the LCD is in 8 - bit mode afterwards
*/
init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS ;
if ( lcd - > ifwidth = = 4 ) {
init > > = 4 ;
write_cmd_raw = lcd - > ops - > write_cmd_raw4 ;
} else {
write_cmd_raw = lcd - > ops - > write_cmd ;
}
write_cmd_raw ( lcd , init ) ;
2017-03-10 17:15:17 +03:00
long_sleep ( 10 ) ;
2017-03-10 17:15:18 +03:00
write_cmd_raw ( lcd , init ) ;
2017-03-10 17:15:17 +03:00
long_sleep ( 10 ) ;
2017-03-10 17:15:18 +03:00
write_cmd_raw ( lcd , init ) ;
2017-03-10 17:15:17 +03:00
long_sleep ( 10 ) ;
2017-03-10 17:15:18 +03:00
if ( lcd - > ifwidth = = 4 ) {
/* Switch to 4-bit mode, 1 line, small fonts */
lcd - > ops - > write_cmd_raw4 ( lcd , LCD_CMD_FUNCTION_SET > > 4 ) ;
long_sleep ( 10 ) ;
}
2017-03-10 17:15:17 +03:00
/* set font height and lines number */
lcd - > ops - > write_cmd ( lcd ,
2017-03-10 17:15:18 +03:00
LCD_CMD_FUNCTION_SET |
( ( lcd - > ifwidth = = 8 ) ? LCD_CMD_DATA_LEN_8BITS : 0 ) |
2017-03-10 17:15:17 +03:00
( ( priv - > flags & LCD_FLAG_F ) ? LCD_CMD_FONT_5X10_DOTS : 0 ) |
( ( priv - > flags & LCD_FLAG_N ) ? LCD_CMD_TWO_LINES : 0 ) ) ;
long_sleep ( 10 ) ;
/* display off, cursor off, blink off */
lcd - > ops - > write_cmd ( lcd , LCD_CMD_DISPLAY_CTRL ) ;
long_sleep ( 10 ) ;
lcd - > ops - > write_cmd ( lcd ,
LCD_CMD_DISPLAY_CTRL | /* set display mode */
( ( priv - > flags & LCD_FLAG_D ) ? LCD_CMD_DISPLAY_ON : 0 ) |
( ( priv - > flags & LCD_FLAG_C ) ? LCD_CMD_CURSOR_ON : 0 ) |
( ( priv - > flags & LCD_FLAG_B ) ? LCD_CMD_BLINK_ON : 0 ) ) ;
charlcd_backlight ( lcd , ( priv - > flags & LCD_FLAG_L ) ? 1 : 0 ) ;
long_sleep ( 10 ) ;
/* entry mode set : increment, cursor shifting */
lcd - > ops - > write_cmd ( lcd , LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC ) ;
charlcd_clear_display ( lcd ) ;
return 0 ;
}
2018-02-28 01:09:52 +03:00
/*
* Parses an unsigned integer from a string , until a non - digit character
* is found . The empty string is not accepted . No overflow checks are done .
*
* Returns whether the parsing was successful . Only in that case
* the output parameters are written to .
*
* TODO : If the kernel adds an inplace version of kstrtoul ( ) , this function
* could be easily replaced by that .
*/
static bool parse_n ( const char * s , unsigned long * res , const char * * next_s )
{
if ( ! isdigit ( * s ) )
return false ;
* res = 0 ;
while ( isdigit ( * s ) ) {
* res = * res * 10 + ( * s - ' 0 ' ) ;
+ + s ;
}
* next_s = s ;
return true ;
}
/*
* Parses a movement command of the form " (.*); " , where the group can be
* any number of subcommands of the form " (x|y)[0-9]+ " .
*
* Returns whether the command is valid . The position arguments are
* only written if the parsing was successful .
*
* For instance :
* - " ; " returns ( < original x > , < original y > ) .
* - " x1; " returns ( 1 , < original y > ) .
* - " y2x1; " returns ( 1 , 2 ) .
* - " x12y34x56; " returns ( 56 , 34 ) .
* - " " fails .
* - " x " fails .
* - " x; " fails .
* - " x1 " fails .
* - " xy12; " fails .
* - " x12yy12; " fails .
* - " xx " fails .
*/
static bool parse_xy ( const char * s , unsigned long * x , unsigned long * y )
{
unsigned long new_x = * x ;
unsigned long new_y = * y ;
for ( ; ; ) {
if ( ! * s )
return false ;
if ( * s = = ' ; ' )
break ;
if ( * s = = ' x ' ) {
if ( ! parse_n ( s + 1 , & new_x , & s ) )
return false ;
} else if ( * s = = ' y ' ) {
if ( ! parse_n ( s + 1 , & new_y , & s ) )
return false ;
} else {
return false ;
}
}
* x = new_x ;
* y = new_y ;
return true ;
}
2017-03-10 17:15:17 +03:00
/*
* These are the file operation function for user access to / dev / lcd
* This function can also be called from inside the kernel , by
* setting file and ppos to NULL .
*
*/
static inline int handle_lcd_special_code ( struct charlcd * lcd )
{
2019-03-12 17:44:29 +03:00
struct charlcd_priv * priv = charlcd_to_priv ( lcd ) ;
2017-03-10 17:15:17 +03:00
/* LCD special codes */
int processed = 0 ;
char * esc = priv - > esc_seq . buf + 2 ;
int oldflags = priv - > flags ;
/* check for display mode flags */
switch ( * esc ) {
case ' D ' : /* Display ON */
priv - > flags | = LCD_FLAG_D ;
processed = 1 ;
break ;
case ' d ' : /* Display OFF */
priv - > flags & = ~ LCD_FLAG_D ;
processed = 1 ;
break ;
case ' C ' : /* Cursor ON */
priv - > flags | = LCD_FLAG_C ;
processed = 1 ;
break ;
case ' c ' : /* Cursor OFF */
priv - > flags & = ~ LCD_FLAG_C ;
processed = 1 ;
break ;
case ' B ' : /* Blink ON */
priv - > flags | = LCD_FLAG_B ;
processed = 1 ;
break ;
case ' b ' : /* Blink OFF */
priv - > flags & = ~ LCD_FLAG_B ;
processed = 1 ;
break ;
case ' + ' : /* Back light ON */
priv - > flags | = LCD_FLAG_L ;
processed = 1 ;
break ;
case ' - ' : /* Back light OFF */
priv - > flags & = ~ LCD_FLAG_L ;
processed = 1 ;
break ;
case ' * ' : /* Flash back light */
charlcd_poke ( lcd ) ;
processed = 1 ;
break ;
case ' f ' : /* Small Font */
priv - > flags & = ~ LCD_FLAG_F ;
processed = 1 ;
break ;
case ' F ' : /* Large Font */
priv - > flags | = LCD_FLAG_F ;
processed = 1 ;
break ;
case ' n ' : /* One Line */
priv - > flags & = ~ LCD_FLAG_N ;
processed = 1 ;
break ;
case ' N ' : /* Two Lines */
priv - > flags | = LCD_FLAG_N ;
2018-02-26 02:54:29 +03:00
processed = 1 ;
2017-03-10 17:15:17 +03:00
break ;
case ' l ' : /* Shift Cursor Left */
if ( priv - > addr . x > 0 ) {
/* back one char if not at end of line */
if ( priv - > addr . x < lcd - > bwidth )
lcd - > ops - > write_cmd ( lcd , LCD_CMD_SHIFT ) ;
priv - > addr . x - - ;
}
processed = 1 ;
break ;
case ' r ' : /* shift cursor right */
if ( priv - > addr . x < lcd - > width ) {
/* allow the cursor to pass the end of the line */
if ( priv - > addr . x < ( lcd - > bwidth - 1 ) )
lcd - > ops - > write_cmd ( lcd ,
LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT ) ;
priv - > addr . x + + ;
}
processed = 1 ;
break ;
case ' L ' : /* shift display left */
lcd - > ops - > write_cmd ( lcd , LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT ) ;
processed = 1 ;
break ;
case ' R ' : /* shift display right */
lcd - > ops - > write_cmd ( lcd ,
LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT |
LCD_CMD_SHIFT_RIGHT ) ;
processed = 1 ;
break ;
case ' k ' : { /* kill end of line */
int x ;
for ( x = priv - > addr . x ; x < lcd - > bwidth ; x + + )
lcd - > ops - > write_data ( lcd , ' ' ) ;
/* restore cursor position */
charlcd_gotoxy ( lcd ) ;
processed = 1 ;
break ;
}
case ' I ' : /* reinitialize display */
charlcd_init_display ( lcd ) ;
processed = 1 ;
break ;
case ' G ' : {
/* Generator : LGcxxxxx...xx; must have <c> between '0'
* and ' 7 ' , representing the numerical ASCII code of the
* redefined character , and < xx . . . xx > a sequence of 16
* hex digits representing 8 bytes for each character .
* Most LCDs will only use 5 lower bits of the 7 first
* bytes .
*/
unsigned char cgbytes [ 8 ] ;
unsigned char cgaddr ;
int cgoffset ;
int shift ;
char value ;
int addr ;
if ( ! strchr ( esc , ' ; ' ) )
break ;
esc + + ;
cgaddr = * ( esc + + ) - ' 0 ' ;
if ( cgaddr > 7 ) {
processed = 1 ;
break ;
}
cgoffset = 0 ;
shift = 0 ;
value = 0 ;
while ( * esc & & cgoffset < 8 ) {
shift ^ = 4 ;
if ( * esc > = ' 0 ' & & * esc < = ' 9 ' ) {
value | = ( * esc - ' 0 ' ) < < shift ;
2018-02-10 02:50:10 +03:00
} else if ( * esc > = ' A ' & & * esc < = ' F ' ) {
2017-03-10 17:15:17 +03:00
value | = ( * esc - ' A ' + 10 ) < < shift ;
2018-02-10 02:50:10 +03:00
} else if ( * esc > = ' a ' & & * esc < = ' f ' ) {
2017-03-10 17:15:17 +03:00
value | = ( * esc - ' a ' + 10 ) < < shift ;
} else {
esc + + ;
continue ;
}
if ( shift = = 0 ) {
cgbytes [ cgoffset + + ] = value ;
value = 0 ;
}
esc + + ;
}
lcd - > ops - > write_cmd ( lcd , LCD_CMD_SET_CGRAM_ADDR | ( cgaddr * 8 ) ) ;
for ( addr = 0 ; addr < cgoffset ; addr + + )
lcd - > ops - > write_data ( lcd , cgbytes [ addr ] ) ;
/* ensures that we stop writing to CGRAM */
charlcd_gotoxy ( lcd ) ;
processed = 1 ;
break ;
}
case ' x ' : /* gotoxy : LxXXX[yYYY]; */
case ' y ' : /* gotoxy : LyYYY[xXXX]; */
2018-12-05 16:52:47 +03:00
if ( priv - > esc_seq . buf [ priv - > esc_seq . len - 1 ] ! = ' ; ' )
break ;
2018-02-28 01:09:52 +03:00
/* If the command is valid, move to the new address */
if ( parse_xy ( esc , & priv - > addr . x , & priv - > addr . y ) )
charlcd_gotoxy ( lcd ) ;
2017-03-10 17:15:17 +03:00
2018-02-28 01:09:52 +03:00
/* Regardless of its validity, mark as processed */
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
}
/* TODO: This indent party here got ugly, clean it! */
/* Check whether one flag was changed */
if ( oldflags = = priv - > flags )
return processed ;
/* check whether one of B,C,D flags were changed */
if ( ( oldflags ^ priv - > flags ) &
( LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D ) )
/* set display mode */
lcd - > ops - > write_cmd ( lcd ,
LCD_CMD_DISPLAY_CTRL |
( ( priv - > flags & LCD_FLAG_D ) ? LCD_CMD_DISPLAY_ON : 0 ) |
( ( priv - > flags & LCD_FLAG_C ) ? LCD_CMD_CURSOR_ON : 0 ) |
( ( priv - > flags & LCD_FLAG_B ) ? LCD_CMD_BLINK_ON : 0 ) ) ;
/* check whether one of F,N flags was changed */
else if ( ( oldflags ^ priv - > flags ) & ( LCD_FLAG_F | LCD_FLAG_N ) )
lcd - > ops - > write_cmd ( lcd ,
2017-03-10 17:15:18 +03:00
LCD_CMD_FUNCTION_SET |
( ( lcd - > ifwidth = = 8 ) ? LCD_CMD_DATA_LEN_8BITS : 0 ) |
2017-03-10 17:15:17 +03:00
( ( priv - > flags & LCD_FLAG_F ) ? LCD_CMD_FONT_5X10_DOTS : 0 ) |
( ( priv - > flags & LCD_FLAG_N ) ? LCD_CMD_TWO_LINES : 0 ) ) ;
/* check whether L flag was changed */
else if ( ( oldflags ^ priv - > flags ) & LCD_FLAG_L )
charlcd_backlight ( lcd , ! ! ( priv - > flags & LCD_FLAG_L ) ) ;
return processed ;
}
static void charlcd_write_char ( struct charlcd * lcd , char c )
{
2019-03-12 17:44:29 +03:00
struct charlcd_priv * priv = charlcd_to_priv ( lcd ) ;
2017-03-10 17:15:17 +03:00
/* first, we'll test if we're in escape mode */
if ( ( c ! = ' \n ' ) & & priv - > esc_seq . len > = 0 ) {
/* yes, let's add this char to the buffer */
priv - > esc_seq . buf [ priv - > esc_seq . len + + ] = c ;
2018-02-10 02:50:11 +03:00
priv - > esc_seq . buf [ priv - > esc_seq . len ] = ' \0 ' ;
2017-03-10 17:15:17 +03:00
} else {
/* aborts any previous escape sequence */
priv - > esc_seq . len = - 1 ;
switch ( c ) {
case LCD_ESCAPE_CHAR :
/* start of an escape sequence */
priv - > esc_seq . len = 0 ;
2018-02-10 02:50:11 +03:00
priv - > esc_seq . buf [ priv - > esc_seq . len ] = ' \0 ' ;
2017-03-10 17:15:17 +03:00
break ;
case ' \b ' :
/* go back one char and clear it */
if ( priv - > addr . x > 0 ) {
/*
* check if we ' re not at the
* end of the line
*/
if ( priv - > addr . x < lcd - > bwidth )
/* back one char */
lcd - > ops - > write_cmd ( lcd , LCD_CMD_SHIFT ) ;
priv - > addr . x - - ;
}
/* replace with a space */
lcd - > ops - > write_data ( lcd , ' ' ) ;
/* back one char again */
lcd - > ops - > write_cmd ( lcd , LCD_CMD_SHIFT ) ;
break ;
2018-02-10 02:50:12 +03:00
case ' \f ' :
2017-03-10 17:15:17 +03:00
/* quickly clear the display */
charlcd_clear_fast ( lcd ) ;
break ;
case ' \n ' :
/*
* flush the remainder of the current line and
* go to the beginning of the next line
*/
for ( ; priv - > addr . x < lcd - > bwidth ; priv - > addr . x + + )
lcd - > ops - > write_data ( lcd , ' ' ) ;
priv - > addr . x = 0 ;
priv - > addr . y = ( priv - > addr . y + 1 ) % lcd - > height ;
charlcd_gotoxy ( lcd ) ;
break ;
case ' \r ' :
/* go to the beginning of the same line */
priv - > addr . x = 0 ;
charlcd_gotoxy ( lcd ) ;
break ;
case ' \t ' :
/* print a space instead of the tab */
charlcd_print ( lcd , ' ' ) ;
break ;
default :
/* simply print this char */
charlcd_print ( lcd , c ) ;
break ;
}
}
/*
* now we ' ll see if we ' re in an escape mode and if the current
* escape sequence can be understood .
*/
if ( priv - > esc_seq . len > = 2 ) {
int processed = 0 ;
if ( ! strcmp ( priv - > esc_seq . buf , " [2J " ) ) {
/* clear the display */
charlcd_clear_fast ( lcd ) ;
processed = 1 ;
} else if ( ! strcmp ( priv - > esc_seq . buf , " [H " ) ) {
/* cursor to home */
charlcd_home ( lcd ) ;
processed = 1 ;
}
/* codes starting with ^[[L */
else if ( ( priv - > esc_seq . len > = 3 ) & &
( priv - > esc_seq . buf [ 0 ] = = ' [ ' ) & &
( priv - > esc_seq . buf [ 1 ] = = ' L ' ) ) {
processed = handle_lcd_special_code ( lcd ) ;
}
/* LCD special escape codes */
/*
* flush the escape sequence if it ' s been processed
* or if it is getting too long .
*/
if ( processed | | ( priv - > esc_seq . len > = LCD_ESCAPE_LEN ) )
priv - > esc_seq . len = - 1 ;
} /* escape codes */
}
static struct charlcd * the_charlcd ;
static ssize_t charlcd_write ( struct file * file , const char __user * buf ,
size_t count , loff_t * ppos )
{
const char __user * tmp = buf ;
char c ;
for ( ; count - - > 0 ; ( * ppos ) + + , tmp + + ) {
if ( ! in_interrupt ( ) & & ( ( ( count + 1 ) & 0x1f ) = = 0 ) )
/*
* let ' s be a little nice with other processes
* that need some CPU
*/
schedule ( ) ;
if ( get_user ( c , tmp ) )
return - EFAULT ;
charlcd_write_char ( the_charlcd , c ) ;
}
return tmp - buf ;
}
static int charlcd_open ( struct inode * inode , struct file * file )
{
2019-03-12 17:44:29 +03:00
struct charlcd_priv * priv = charlcd_to_priv ( the_charlcd ) ;
2017-09-07 16:37:30 +03:00
int ret ;
2017-03-10 17:15:17 +03:00
2017-09-07 16:37:30 +03:00
ret = - EBUSY ;
2017-03-10 17:15:17 +03:00
if ( ! atomic_dec_and_test ( & charlcd_available ) )
2017-09-07 16:37:30 +03:00
goto fail ; /* open only once at a time */
2017-03-10 17:15:17 +03:00
2017-09-07 16:37:30 +03:00
ret = - EPERM ;
2017-03-10 17:15:17 +03:00
if ( file - > f_mode & FMODE_READ ) /* device is write-only */
2017-09-07 16:37:30 +03:00
goto fail ;
2017-03-10 17:15:17 +03:00
if ( priv - > must_clear ) {
charlcd_clear_display ( & priv - > lcd ) ;
priv - > must_clear = false ;
}
return nonseekable_open ( inode , file ) ;
2017-09-07 16:37:30 +03:00
fail :
atomic_inc ( & charlcd_available ) ;
return ret ;
2017-03-10 17:15:17 +03:00
}
static int charlcd_release ( struct inode * inode , struct file * file )
{
atomic_inc ( & charlcd_available ) ;
return 0 ;
}
static const struct file_operations charlcd_fops = {
. write = charlcd_write ,
. open = charlcd_open ,
. release = charlcd_release ,
. llseek = no_llseek ,
} ;
static struct miscdevice charlcd_dev = {
. minor = LCD_MINOR ,
. name = " lcd " ,
. fops = & charlcd_fops ,
} ;
static void charlcd_puts ( struct charlcd * lcd , const char * s )
{
const char * tmp = s ;
int count = strlen ( s ) ;
for ( ; count - - > 0 ; tmp + + ) {
if ( ! in_interrupt ( ) & & ( ( ( count + 1 ) & 0x1f ) = = 0 ) )
/*
* let ' s be a little nice with other processes
* that need some CPU
*/
schedule ( ) ;
charlcd_write_char ( lcd , * tmp ) ;
}
}
2019-03-01 21:48:15 +03:00
# ifdef CONFIG_PANEL_BOOT_MESSAGE
# define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
# else
# define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n"
# endif
2019-03-01 21:48:16 +03:00
# ifdef CONFIG_CHARLCD_BL_ON
# define LCD_INIT_BL "\x1b[L+"
# elif defined(CONFIG_CHARLCD_BL_FLASH)
# define LCD_INIT_BL "\x1b[L*"
# else
# define LCD_INIT_BL "\x1b[L-"
# endif
2017-03-10 17:15:17 +03:00
/* initialize the LCD driver */
static int charlcd_init ( struct charlcd * lcd )
{
2019-03-12 17:44:29 +03:00
struct charlcd_priv * priv = charlcd_to_priv ( lcd ) ;
2017-03-10 17:15:17 +03:00
int ret ;
if ( lcd - > ops - > backlight ) {
mutex_init ( & priv - > bl_tempo_lock ) ;
INIT_DELAYED_WORK ( & priv - > bl_work , charlcd_bl_off ) ;
}
/*
* before this line , we must NOT send anything to the display .
* Since charlcd_init_display ( ) needs to write data , we have to
* enable mark the LCD initialized just before .
*/
ret = charlcd_init_display ( lcd ) ;
if ( ret )
return ret ;
/* display a short message */
2019-03-01 21:48:16 +03:00
charlcd_puts ( lcd , " \x1b [Lc \x1b [Lb " LCD_INIT_BL LCD_INIT_TEXT ) ;
2019-03-01 21:48:15 +03:00
2017-03-10 17:15:17 +03:00
/* clear the display on the next device opening */
priv - > must_clear = true ;
charlcd_home ( lcd ) ;
return 0 ;
}
struct charlcd * charlcd_alloc ( unsigned int drvdata_size )
{
struct charlcd_priv * priv ;
struct charlcd * lcd ;
priv = kzalloc ( sizeof ( * priv ) + drvdata_size , GFP_KERNEL ) ;
if ( ! priv )
return NULL ;
priv - > esc_seq . len = - 1 ;
lcd = & priv - > lcd ;
2017-03-10 17:15:18 +03:00
lcd - > ifwidth = 8 ;
2017-03-10 17:15:17 +03:00
lcd - > bwidth = DEFAULT_LCD_BWIDTH ;
lcd - > hwidth = DEFAULT_LCD_HWIDTH ;
lcd - > drvdata = priv - > drvdata ;
return lcd ;
}
EXPORT_SYMBOL_GPL ( charlcd_alloc ) ;
2019-03-12 17:44:30 +03:00
void charlcd_free ( struct charlcd * lcd )
{
kfree ( charlcd_to_priv ( lcd ) ) ;
}
EXPORT_SYMBOL_GPL ( charlcd_free ) ;
2017-03-10 17:15:17 +03:00
static int panel_notify_sys ( struct notifier_block * this , unsigned long code ,
void * unused )
{
struct charlcd * lcd = the_charlcd ;
switch ( code ) {
case SYS_DOWN :
charlcd_puts ( lcd ,
" \x0c Reloading \n System... \x1b [Lc \x1b [Lb \x1b [L+ " ) ;
break ;
case SYS_HALT :
charlcd_puts ( lcd , " \x0c System Halted. \x1b [Lc \x1b [Lb \x1b [L+ " ) ;
break ;
case SYS_POWER_OFF :
charlcd_puts ( lcd , " \x0c Power off. \x1b [Lc \x1b [Lb \x1b [L+ " ) ;
break ;
default :
break ;
}
return NOTIFY_DONE ;
}
static struct notifier_block panel_notifier = {
panel_notify_sys ,
NULL ,
0
} ;
int charlcd_register ( struct charlcd * lcd )
{
int ret ;
ret = charlcd_init ( lcd ) ;
if ( ret )
return ret ;
ret = misc_register ( & charlcd_dev ) ;
if ( ret )
return ret ;
the_charlcd = lcd ;
register_reboot_notifier ( & panel_notifier ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( charlcd_register ) ;
int charlcd_unregister ( struct charlcd * lcd )
{
2019-03-12 17:44:29 +03:00
struct charlcd_priv * priv = charlcd_to_priv ( lcd ) ;
2017-03-10 17:15:17 +03:00
unregister_reboot_notifier ( & panel_notifier ) ;
charlcd_puts ( lcd , " \x0c LCD driver unloaded. \x1b [Lc \x1b [Lb \x1b [L- " ) ;
misc_deregister ( & charlcd_dev ) ;
the_charlcd = NULL ;
if ( lcd - > ops - > backlight ) {
cancel_delayed_work_sync ( & priv - > bl_work ) ;
priv - > lcd . ops - > backlight ( & priv - > lcd , 0 ) ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( charlcd_unregister ) ;
MODULE_LICENSE ( " GPL " ) ;