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/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>
2024-04-09 20:39:21 +03:00
# ifndef CONFIG_PANEL_BOOT_MESSAGE
2017-03-10 17:15:17 +03:00
# include <generated/utsrelease.h>
2024-04-09 20:39:21 +03:00
# endif
2017-03-10 17:15:17 +03:00
2019-08-06 10:14:44 +03:00
# include "charlcd.h"
2017-03-10 17:15:17 +03:00
/* Keep the backlight on this many seconds for each flash */
# define LCD_BL_TEMPO_PERIOD 4
# 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 */
2021-11-10 01:07:31 +03:00
unsigned long flags ;
2017-03-10 17:15:17 +03:00
/* Current escape sequence and it's length or -1 if outside */
struct {
char buf [ LCD_ESCAPE_LEN + 1 ] ;
int len ;
} esc_seq ;
2020-02-12 22:52:31 +03:00
unsigned long long drvdata [ ] ;
2017-03-10 17:15:17 +03:00
} ;
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 ) ;
/* turn the backlight on or off */
2020-11-03 12:58:15 +03:00
void charlcd_backlight ( struct charlcd * lcd , enum charlcd_onoff on )
2017-03-10 17:15:17 +03:00
{
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 ) ;
}
2020-11-03 12:58:15 +03:00
EXPORT_SYMBOL_GPL ( charlcd_backlight ) ;
2017-03-10 17:15:17 +03:00
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 ) )
2020-11-03 12:58:16 +03:00
priv - > lcd . ops - > backlight ( & priv - > lcd , CHARLCD_OFF ) ;
2017-03-10 17:15:17 +03:00
}
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 ) )
2020-11-03 12:58:16 +03:00
lcd - > ops - > backlight ( lcd , CHARLCD_ON ) ;
2017-03-10 17:15:17 +03:00
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_home ( struct charlcd * lcd )
{
2020-11-03 12:58:10 +03:00
lcd - > addr . x = 0 ;
lcd - > addr . y = 0 ;
2020-11-03 12:58:13 +03:00
lcd - > ops - > home ( lcd ) ;
2017-03-10 17:15:17 +03:00
}
static void charlcd_print ( struct charlcd * lcd , char c )
{
2020-11-03 12:58:26 +03:00
if ( lcd - > addr . x > = lcd - > width )
return ;
2020-11-03 12:58:11 +03:00
if ( lcd - > char_conv )
c = lcd - > char_conv [ ( unsigned char ) c ] ;
2017-03-10 17:15:17 +03:00
2020-11-03 12:58:11 +03:00
if ( ! lcd - > ops - > print ( lcd , c ) )
2020-11-03 12:58:10 +03:00
lcd - > addr . x + + ;
2018-01-15 12:43:48 +03:00
2020-11-03 12:58:11 +03:00
/* prevents the cursor from wrapping onto the next line */
2020-11-03 12:58:24 +03:00
if ( lcd - > addr . x = = lcd - > width )
2020-11-03 12:58:25 +03:00
lcd - > ops - > gotoxy ( lcd , lcd - > addr . x - 1 , lcd - > addr . y ) ;
2017-03-10 17:15:17 +03:00
}
2020-11-03 12:58:23 +03:00
static void charlcd_clear_display ( struct charlcd * lcd )
2017-03-10 17:15:17 +03:00
{
2020-11-03 12:58:23 +03:00
lcd - > ops - > clear_display ( lcd ) ;
lcd - > addr . x = 0 ;
lcd - > addr . y = 0 ;
2017-03-10 17:15:17 +03:00
}
2018-02-28 01:09:52 +03:00
/*
* 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 ;
2019-12-05 03:50:36 +03:00
char * p ;
2018-02-28 01:09:52 +03:00
for ( ; ; ) {
if ( ! * s )
return false ;
if ( * s = = ' ; ' )
break ;
if ( * s = = ' x ' ) {
2019-12-05 03:50:36 +03:00
new_x = simple_strtoul ( s + 1 , & p , 10 ) ;
if ( p = = s + 1 )
2018-02-28 01:09:52 +03:00
return false ;
2019-12-05 03:50:36 +03:00
s = p ;
2018-02-28 01:09:52 +03:00
} else if ( * s = = ' y ' ) {
2019-12-05 03:50:36 +03:00
new_y = simple_strtoul ( s + 1 , & p , 10 ) ;
if ( p = = s + 1 )
2018-02-28 01:09:52 +03:00
return false ;
2019-12-05 03:50:36 +03:00
s = p ;
2018-02-28 01:09:52 +03:00
} 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 ;
2020-11-03 12:58:18 +03:00
if ( priv - > flags ! = oldflags )
lcd - > ops - > display ( lcd , CHARLCD_ON ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' d ' : /* Display OFF */
priv - > flags & = ~ LCD_FLAG_D ;
2020-11-03 12:58:18 +03:00
if ( priv - > flags ! = oldflags )
lcd - > ops - > display ( lcd , CHARLCD_OFF ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' C ' : /* Cursor ON */
priv - > flags | = LCD_FLAG_C ;
2020-11-03 12:58:18 +03:00
if ( priv - > flags ! = oldflags )
lcd - > ops - > cursor ( lcd , CHARLCD_ON ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' c ' : /* Cursor OFF */
priv - > flags & = ~ LCD_FLAG_C ;
2020-11-03 12:58:18 +03:00
if ( priv - > flags ! = oldflags )
lcd - > ops - > cursor ( lcd , CHARLCD_OFF ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' B ' : /* Blink ON */
priv - > flags | = LCD_FLAG_B ;
2020-11-03 12:58:18 +03:00
if ( priv - > flags ! = oldflags )
lcd - > ops - > blink ( lcd , CHARLCD_ON ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' b ' : /* Blink OFF */
priv - > flags & = ~ LCD_FLAG_B ;
2020-11-03 12:58:18 +03:00
if ( priv - > flags ! = oldflags )
lcd - > ops - > blink ( lcd , CHARLCD_OFF ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' + ' : /* Back light ON */
priv - > flags | = LCD_FLAG_L ;
2020-11-03 12:58:21 +03:00
if ( priv - > flags ! = oldflags )
charlcd_backlight ( lcd , CHARLCD_ON ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' - ' : /* Back light OFF */
priv - > flags & = ~ LCD_FLAG_L ;
2020-11-03 12:58:21 +03:00
if ( priv - > flags ! = oldflags )
charlcd_backlight ( lcd , CHARLCD_OFF ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' * ' : /* Flash back light */
charlcd_poke ( lcd ) ;
processed = 1 ;
break ;
case ' f ' : /* Small Font */
priv - > flags & = ~ LCD_FLAG_F ;
2020-11-03 12:58:18 +03:00
if ( priv - > flags ! = oldflags )
lcd - > ops - > fontsize ( lcd , CHARLCD_FONTSIZE_SMALL ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' F ' : /* Large Font */
priv - > flags | = LCD_FLAG_F ;
2020-11-03 12:58:18 +03:00
if ( priv - > flags ! = oldflags )
lcd - > ops - > fontsize ( lcd , CHARLCD_FONTSIZE_LARGE ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' n ' : /* One Line */
priv - > flags & = ~ LCD_FLAG_N ;
2020-11-03 12:58:18 +03:00
if ( priv - > flags ! = oldflags )
lcd - > ops - > lines ( lcd , CHARLCD_LINES_1 ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' N ' : /* Two Lines */
priv - > flags | = LCD_FLAG_N ;
2020-11-03 12:58:18 +03:00
if ( priv - > flags ! = oldflags )
lcd - > ops - > lines ( lcd , CHARLCD_LINES_2 ) ;
2018-02-26 02:54:29 +03:00
processed = 1 ;
2017-03-10 17:15:17 +03:00
break ;
case ' l ' : /* Shift Cursor Left */
2020-11-03 12:58:10 +03:00
if ( lcd - > addr . x > 0 ) {
2020-11-03 12:58:18 +03:00
if ( ! lcd - > ops - > shift_cursor ( lcd , CHARLCD_SHIFT_LEFT ) )
lcd - > addr . x - - ;
2017-03-10 17:15:17 +03:00
}
2020-11-03 12:58:18 +03:00
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' r ' : /* shift cursor right */
2020-11-03 12:58:10 +03:00
if ( lcd - > addr . x < lcd - > width ) {
2020-11-03 12:58:18 +03:00
if ( ! lcd - > ops - > shift_cursor ( lcd , CHARLCD_SHIFT_RIGHT ) )
lcd - > addr . x + + ;
2017-03-10 17:15:17 +03:00
}
2020-11-03 12:58:18 +03:00
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' L ' : /* shift display left */
2020-11-03 12:58:18 +03:00
lcd - > ops - > shift_display ( lcd , CHARLCD_SHIFT_LEFT ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' R ' : /* shift display right */
2020-11-03 12:58:18 +03:00
lcd - > ops - > shift_display ( lcd , CHARLCD_SHIFT_RIGHT ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
case ' k ' : { /* kill end of line */
2020-11-03 12:58:11 +03:00
int x , xs , ys ;
2017-03-10 17:15:17 +03:00
2020-11-03 12:58:11 +03:00
xs = lcd - > addr . x ;
ys = lcd - > addr . y ;
2020-11-03 12:58:24 +03:00
for ( x = lcd - > addr . x ; x < lcd - > width ; x + + )
2020-11-03 12:58:11 +03:00
lcd - > ops - > print ( lcd , ' ' ) ;
2017-03-10 17:15:17 +03:00
/* restore cursor position */
2020-11-03 12:58:11 +03:00
lcd - > addr . x = xs ;
lcd - > addr . y = ys ;
2020-11-03 12:58:25 +03:00
lcd - > ops - > gotoxy ( lcd , lcd - > addr . x , lcd - > addr . y ) ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
}
case ' I ' : /* reinitialize display */
2020-11-03 12:58:17 +03:00
lcd - > ops - > init_display ( lcd ) ;
priv - > flags = ( ( lcd - > height > 1 ) ? LCD_FLAG_N : 0 ) | LCD_FLAG_D |
LCD_FLAG_C | LCD_FLAG_B ;
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
2020-11-03 12:58:20 +03:00
case ' G ' :
if ( lcd - > ops - > redefine_char )
processed = lcd - > ops - > redefine_char ( lcd , esc ) ;
else
2017-03-10 17:15:17 +03:00
processed = 1 ;
break ;
2020-11-03 12:58:20 +03:00
2017-03-10 17:15:17 +03:00
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 */
2020-11-03 12:58:10 +03:00
if ( parse_xy ( esc , & lcd - > addr . x , & lcd - > addr . y ) )
2020-11-03 12:58:25 +03:00
lcd - > ops - > gotoxy ( lcd , lcd - > addr . x , lcd - > addr . y ) ;
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 ;
}
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 */
2020-11-03 12:58:10 +03:00
if ( lcd - > addr . x > 0 ) {
2020-11-03 12:58:18 +03:00
/* back one char */
if ( ! lcd - > ops - > shift_cursor ( lcd ,
CHARLCD_SHIFT_LEFT ) )
lcd - > addr . x - - ;
2017-03-10 17:15:17 +03:00
}
/* replace with a space */
2020-11-03 12:58:18 +03:00
charlcd_print ( lcd , ' ' ) ;
2017-03-10 17:15:17 +03:00
/* back one char again */
2020-11-03 12:58:18 +03:00
if ( ! lcd - > ops - > shift_cursor ( lcd , CHARLCD_SHIFT_LEFT ) )
lcd - > addr . x - - ;
2017-03-10 17:15:17 +03:00
break ;
2018-02-10 02:50:12 +03:00
case ' \f ' :
2017-03-10 17:15:17 +03:00
/* quickly clear the display */
2020-11-03 12:58:23 +03:00
charlcd_clear_display ( lcd ) ;
2017-03-10 17:15:17 +03:00
break ;
case ' \n ' :
/*
* flush the remainder of the current line and
* go to the beginning of the next line
*/
2020-11-03 12:58:24 +03:00
for ( ; lcd - > addr . x < lcd - > width ; lcd - > addr . x + + )
2020-11-03 12:58:11 +03:00
lcd - > ops - > print ( lcd , ' ' ) ;
2020-11-03 12:58:10 +03:00
lcd - > addr . x = 0 ;
lcd - > addr . y = ( lcd - > addr . y + 1 ) % lcd - > height ;
2020-11-03 12:58:25 +03:00
lcd - > ops - > gotoxy ( lcd , lcd - > addr . x , lcd - > addr . y ) ;
2017-03-10 17:15:17 +03:00
break ;
case ' \r ' :
/* go to the beginning of the same line */
2020-11-03 12:58:10 +03:00
lcd - > addr . x = 0 ;
2020-11-03 12:58:25 +03:00
lcd - > ops - > gotoxy ( lcd , lcd - > addr . x , lcd - > addr . y ) ;
2017-03-10 17:15:17 +03:00
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 */
2020-11-03 12:58:23 +03:00
charlcd_clear_display ( lcd ) ;
2017-03-10 17:15:17 +03:00
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 + + ) {
2021-02-16 21:27:14 +03:00
if ( ( ( count + 1 ) & 0x1f ) = = 0 ) {
2017-03-10 17:15:17 +03:00
/*
2021-02-16 21:27:14 +03:00
* charlcd_write ( ) is invoked as a VFS - > write ( ) callback
* and as such it is always invoked from preemptible
* context and may sleep .
2017-03-10 17:15:17 +03:00
*/
2021-02-16 21:27:14 +03:00
cond_resched ( ) ;
}
2017-03-10 17:15:17 +03:00
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 ) {
2020-11-03 12:58:14 +03:00
priv - > lcd . ops - > clear_display ( & priv - > lcd ) ;
2017-03-10 17:15:17 +03:00
priv - > must_clear = false ;
2020-11-03 12:58:14 +03:00
priv - > lcd . addr . x = 0 ;
priv - > lcd . addr . y = 0 ;
2017-03-10 17:15:17 +03:00
}
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 + + ) {
2021-02-16 21:27:14 +03:00
if ( ( ( count + 1 ) & 0x1f ) = = 0 )
cond_resched ( ) ;
2017-03-10 17:15:17 +03:00
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 ;
2020-11-03 12:58:17 +03:00
priv - > flags = ( ( lcd - > height > 1 ) ? LCD_FLAG_N : 0 ) | LCD_FLAG_D |
LCD_FLAG_C | LCD_FLAG_B ;
2017-03-10 17:15:17 +03:00
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 .
*/
2021-11-10 01:07:32 +03:00
if ( WARN_ON ( ! lcd - > ops - > init_display ) )
return - EINVAL ;
2020-11-03 12:58:17 +03:00
ret = lcd - > ops - > init_display ( lcd ) ;
2017-03-10 17:15:17 +03:00
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 ;
}
2020-11-03 12:58:06 +03:00
struct charlcd * charlcd_alloc ( void )
2017-03-10 17:15:17 +03:00
{
struct charlcd_priv * priv ;
struct charlcd * lcd ;
2020-11-03 12:58:06 +03:00
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
2017-03-10 17:15:17 +03:00
if ( ! priv )
return NULL ;
priv - > esc_seq . len = - 1 ;
lcd = & priv - > lcd ;
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 = {
2021-07-15 17:41:52 +03:00
. notifier_call = panel_notify_sys ,
2017-03-10 17:15:17 +03:00
} ;
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 ) ;
2020-11-03 12:58:16 +03:00
priv - > lcd . ops - > backlight ( & priv - > lcd , CHARLCD_OFF ) ;
2017-03-10 17:15:17 +03:00
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( charlcd_unregister ) ;
2024-04-09 19:14:44 +03:00
MODULE_DESCRIPTION ( " Character LCD core support " ) ;
2017-03-10 17:15:17 +03:00
MODULE_LICENSE ( " GPL " ) ;