162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Character LCD driver for Linux 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu> 662306a36Sopenharmony_ci * Copyright (C) 2016-2017 Glider bvba 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/atomic.h> 1062306a36Sopenharmony_ci#include <linux/ctype.h> 1162306a36Sopenharmony_ci#include <linux/fs.h> 1262306a36Sopenharmony_ci#include <linux/miscdevice.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/notifier.h> 1562306a36Sopenharmony_ci#include <linux/reboot.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci#include <linux/uaccess.h> 1862306a36Sopenharmony_ci#include <linux/workqueue.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <generated/utsrelease.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#include "charlcd.h" 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci/* Keep the backlight on this many seconds for each flash */ 2562306a36Sopenharmony_ci#define LCD_BL_TEMPO_PERIOD 4 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */ 2862306a36Sopenharmony_ci#define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */ 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistruct charlcd_priv { 3162306a36Sopenharmony_ci struct charlcd lcd; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci struct delayed_work bl_work; 3462306a36Sopenharmony_ci struct mutex bl_tempo_lock; /* Protects access to bl_tempo */ 3562306a36Sopenharmony_ci bool bl_tempo; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci bool must_clear; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci /* contains the LCD config state */ 4062306a36Sopenharmony_ci unsigned long flags; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci /* Current escape sequence and it's length or -1 if outside */ 4362306a36Sopenharmony_ci struct { 4462306a36Sopenharmony_ci char buf[LCD_ESCAPE_LEN + 1]; 4562306a36Sopenharmony_ci int len; 4662306a36Sopenharmony_ci } esc_seq; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci unsigned long long drvdata[]; 4962306a36Sopenharmony_ci}; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci#define charlcd_to_priv(p) container_of(p, struct charlcd_priv, lcd) 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci/* Device single-open policy control */ 5462306a36Sopenharmony_cistatic atomic_t charlcd_available = ATOMIC_INIT(1); 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci/* turn the backlight on or off */ 5762306a36Sopenharmony_civoid charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on) 5862306a36Sopenharmony_ci{ 5962306a36Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci if (!lcd->ops->backlight) 6262306a36Sopenharmony_ci return; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci mutex_lock(&priv->bl_tempo_lock); 6562306a36Sopenharmony_ci if (!priv->bl_tempo) 6662306a36Sopenharmony_ci lcd->ops->backlight(lcd, on); 6762306a36Sopenharmony_ci mutex_unlock(&priv->bl_tempo_lock); 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(charlcd_backlight); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistatic void charlcd_bl_off(struct work_struct *work) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci struct delayed_work *dwork = to_delayed_work(work); 7462306a36Sopenharmony_ci struct charlcd_priv *priv = 7562306a36Sopenharmony_ci container_of(dwork, struct charlcd_priv, bl_work); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci mutex_lock(&priv->bl_tempo_lock); 7862306a36Sopenharmony_ci if (priv->bl_tempo) { 7962306a36Sopenharmony_ci priv->bl_tempo = false; 8062306a36Sopenharmony_ci if (!(priv->flags & LCD_FLAG_L)) 8162306a36Sopenharmony_ci priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF); 8262306a36Sopenharmony_ci } 8362306a36Sopenharmony_ci mutex_unlock(&priv->bl_tempo_lock); 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci/* turn the backlight on for a little while */ 8762306a36Sopenharmony_civoid charlcd_poke(struct charlcd *lcd) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci if (!lcd->ops->backlight) 9262306a36Sopenharmony_ci return; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci cancel_delayed_work_sync(&priv->bl_work); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci mutex_lock(&priv->bl_tempo_lock); 9762306a36Sopenharmony_ci if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L)) 9862306a36Sopenharmony_ci lcd->ops->backlight(lcd, CHARLCD_ON); 9962306a36Sopenharmony_ci priv->bl_tempo = true; 10062306a36Sopenharmony_ci schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ); 10162306a36Sopenharmony_ci mutex_unlock(&priv->bl_tempo_lock); 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(charlcd_poke); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic void charlcd_home(struct charlcd *lcd) 10662306a36Sopenharmony_ci{ 10762306a36Sopenharmony_ci lcd->addr.x = 0; 10862306a36Sopenharmony_ci lcd->addr.y = 0; 10962306a36Sopenharmony_ci lcd->ops->home(lcd); 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic void charlcd_print(struct charlcd *lcd, char c) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci if (lcd->addr.x >= lcd->width) 11562306a36Sopenharmony_ci return; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci if (lcd->char_conv) 11862306a36Sopenharmony_ci c = lcd->char_conv[(unsigned char)c]; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci if (!lcd->ops->print(lcd, c)) 12162306a36Sopenharmony_ci lcd->addr.x++; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci /* prevents the cursor from wrapping onto the next line */ 12462306a36Sopenharmony_ci if (lcd->addr.x == lcd->width) 12562306a36Sopenharmony_ci lcd->ops->gotoxy(lcd, lcd->addr.x - 1, lcd->addr.y); 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic void charlcd_clear_display(struct charlcd *lcd) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci lcd->ops->clear_display(lcd); 13162306a36Sopenharmony_ci lcd->addr.x = 0; 13262306a36Sopenharmony_ci lcd->addr.y = 0; 13362306a36Sopenharmony_ci} 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci/* 13662306a36Sopenharmony_ci * Parses a movement command of the form "(.*);", where the group can be 13762306a36Sopenharmony_ci * any number of subcommands of the form "(x|y)[0-9]+". 13862306a36Sopenharmony_ci * 13962306a36Sopenharmony_ci * Returns whether the command is valid. The position arguments are 14062306a36Sopenharmony_ci * only written if the parsing was successful. 14162306a36Sopenharmony_ci * 14262306a36Sopenharmony_ci * For instance: 14362306a36Sopenharmony_ci * - ";" returns (<original x>, <original y>). 14462306a36Sopenharmony_ci * - "x1;" returns (1, <original y>). 14562306a36Sopenharmony_ci * - "y2x1;" returns (1, 2). 14662306a36Sopenharmony_ci * - "x12y34x56;" returns (56, 34). 14762306a36Sopenharmony_ci * - "" fails. 14862306a36Sopenharmony_ci * - "x" fails. 14962306a36Sopenharmony_ci * - "x;" fails. 15062306a36Sopenharmony_ci * - "x1" fails. 15162306a36Sopenharmony_ci * - "xy12;" fails. 15262306a36Sopenharmony_ci * - "x12yy12;" fails. 15362306a36Sopenharmony_ci * - "xx" fails. 15462306a36Sopenharmony_ci */ 15562306a36Sopenharmony_cistatic bool parse_xy(const char *s, unsigned long *x, unsigned long *y) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci unsigned long new_x = *x; 15862306a36Sopenharmony_ci unsigned long new_y = *y; 15962306a36Sopenharmony_ci char *p; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci for (;;) { 16262306a36Sopenharmony_ci if (!*s) 16362306a36Sopenharmony_ci return false; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci if (*s == ';') 16662306a36Sopenharmony_ci break; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci if (*s == 'x') { 16962306a36Sopenharmony_ci new_x = simple_strtoul(s + 1, &p, 10); 17062306a36Sopenharmony_ci if (p == s + 1) 17162306a36Sopenharmony_ci return false; 17262306a36Sopenharmony_ci s = p; 17362306a36Sopenharmony_ci } else if (*s == 'y') { 17462306a36Sopenharmony_ci new_y = simple_strtoul(s + 1, &p, 10); 17562306a36Sopenharmony_ci if (p == s + 1) 17662306a36Sopenharmony_ci return false; 17762306a36Sopenharmony_ci s = p; 17862306a36Sopenharmony_ci } else { 17962306a36Sopenharmony_ci return false; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci *x = new_x; 18462306a36Sopenharmony_ci *y = new_y; 18562306a36Sopenharmony_ci return true; 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci/* 18962306a36Sopenharmony_ci * These are the file operation function for user access to /dev/lcd 19062306a36Sopenharmony_ci * This function can also be called from inside the kernel, by 19162306a36Sopenharmony_ci * setting file and ppos to NULL. 19262306a36Sopenharmony_ci * 19362306a36Sopenharmony_ci */ 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_cistatic inline int handle_lcd_special_code(struct charlcd *lcd) 19662306a36Sopenharmony_ci{ 19762306a36Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci /* LCD special codes */ 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci int processed = 0; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci char *esc = priv->esc_seq.buf + 2; 20462306a36Sopenharmony_ci int oldflags = priv->flags; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci /* check for display mode flags */ 20762306a36Sopenharmony_ci switch (*esc) { 20862306a36Sopenharmony_ci case 'D': /* Display ON */ 20962306a36Sopenharmony_ci priv->flags |= LCD_FLAG_D; 21062306a36Sopenharmony_ci if (priv->flags != oldflags) 21162306a36Sopenharmony_ci lcd->ops->display(lcd, CHARLCD_ON); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci processed = 1; 21462306a36Sopenharmony_ci break; 21562306a36Sopenharmony_ci case 'd': /* Display OFF */ 21662306a36Sopenharmony_ci priv->flags &= ~LCD_FLAG_D; 21762306a36Sopenharmony_ci if (priv->flags != oldflags) 21862306a36Sopenharmony_ci lcd->ops->display(lcd, CHARLCD_OFF); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci processed = 1; 22162306a36Sopenharmony_ci break; 22262306a36Sopenharmony_ci case 'C': /* Cursor ON */ 22362306a36Sopenharmony_ci priv->flags |= LCD_FLAG_C; 22462306a36Sopenharmony_ci if (priv->flags != oldflags) 22562306a36Sopenharmony_ci lcd->ops->cursor(lcd, CHARLCD_ON); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci processed = 1; 22862306a36Sopenharmony_ci break; 22962306a36Sopenharmony_ci case 'c': /* Cursor OFF */ 23062306a36Sopenharmony_ci priv->flags &= ~LCD_FLAG_C; 23162306a36Sopenharmony_ci if (priv->flags != oldflags) 23262306a36Sopenharmony_ci lcd->ops->cursor(lcd, CHARLCD_OFF); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci processed = 1; 23562306a36Sopenharmony_ci break; 23662306a36Sopenharmony_ci case 'B': /* Blink ON */ 23762306a36Sopenharmony_ci priv->flags |= LCD_FLAG_B; 23862306a36Sopenharmony_ci if (priv->flags != oldflags) 23962306a36Sopenharmony_ci lcd->ops->blink(lcd, CHARLCD_ON); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci processed = 1; 24262306a36Sopenharmony_ci break; 24362306a36Sopenharmony_ci case 'b': /* Blink OFF */ 24462306a36Sopenharmony_ci priv->flags &= ~LCD_FLAG_B; 24562306a36Sopenharmony_ci if (priv->flags != oldflags) 24662306a36Sopenharmony_ci lcd->ops->blink(lcd, CHARLCD_OFF); 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci processed = 1; 24962306a36Sopenharmony_ci break; 25062306a36Sopenharmony_ci case '+': /* Back light ON */ 25162306a36Sopenharmony_ci priv->flags |= LCD_FLAG_L; 25262306a36Sopenharmony_ci if (priv->flags != oldflags) 25362306a36Sopenharmony_ci charlcd_backlight(lcd, CHARLCD_ON); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci processed = 1; 25662306a36Sopenharmony_ci break; 25762306a36Sopenharmony_ci case '-': /* Back light OFF */ 25862306a36Sopenharmony_ci priv->flags &= ~LCD_FLAG_L; 25962306a36Sopenharmony_ci if (priv->flags != oldflags) 26062306a36Sopenharmony_ci charlcd_backlight(lcd, CHARLCD_OFF); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci processed = 1; 26362306a36Sopenharmony_ci break; 26462306a36Sopenharmony_ci case '*': /* Flash back light */ 26562306a36Sopenharmony_ci charlcd_poke(lcd); 26662306a36Sopenharmony_ci processed = 1; 26762306a36Sopenharmony_ci break; 26862306a36Sopenharmony_ci case 'f': /* Small Font */ 26962306a36Sopenharmony_ci priv->flags &= ~LCD_FLAG_F; 27062306a36Sopenharmony_ci if (priv->flags != oldflags) 27162306a36Sopenharmony_ci lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_SMALL); 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci processed = 1; 27462306a36Sopenharmony_ci break; 27562306a36Sopenharmony_ci case 'F': /* Large Font */ 27662306a36Sopenharmony_ci priv->flags |= LCD_FLAG_F; 27762306a36Sopenharmony_ci if (priv->flags != oldflags) 27862306a36Sopenharmony_ci lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_LARGE); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci processed = 1; 28162306a36Sopenharmony_ci break; 28262306a36Sopenharmony_ci case 'n': /* One Line */ 28362306a36Sopenharmony_ci priv->flags &= ~LCD_FLAG_N; 28462306a36Sopenharmony_ci if (priv->flags != oldflags) 28562306a36Sopenharmony_ci lcd->ops->lines(lcd, CHARLCD_LINES_1); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci processed = 1; 28862306a36Sopenharmony_ci break; 28962306a36Sopenharmony_ci case 'N': /* Two Lines */ 29062306a36Sopenharmony_ci priv->flags |= LCD_FLAG_N; 29162306a36Sopenharmony_ci if (priv->flags != oldflags) 29262306a36Sopenharmony_ci lcd->ops->lines(lcd, CHARLCD_LINES_2); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci processed = 1; 29562306a36Sopenharmony_ci break; 29662306a36Sopenharmony_ci case 'l': /* Shift Cursor Left */ 29762306a36Sopenharmony_ci if (lcd->addr.x > 0) { 29862306a36Sopenharmony_ci if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT)) 29962306a36Sopenharmony_ci lcd->addr.x--; 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci processed = 1; 30362306a36Sopenharmony_ci break; 30462306a36Sopenharmony_ci case 'r': /* shift cursor right */ 30562306a36Sopenharmony_ci if (lcd->addr.x < lcd->width) { 30662306a36Sopenharmony_ci if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_RIGHT)) 30762306a36Sopenharmony_ci lcd->addr.x++; 30862306a36Sopenharmony_ci } 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci processed = 1; 31162306a36Sopenharmony_ci break; 31262306a36Sopenharmony_ci case 'L': /* shift display left */ 31362306a36Sopenharmony_ci lcd->ops->shift_display(lcd, CHARLCD_SHIFT_LEFT); 31462306a36Sopenharmony_ci processed = 1; 31562306a36Sopenharmony_ci break; 31662306a36Sopenharmony_ci case 'R': /* shift display right */ 31762306a36Sopenharmony_ci lcd->ops->shift_display(lcd, CHARLCD_SHIFT_RIGHT); 31862306a36Sopenharmony_ci processed = 1; 31962306a36Sopenharmony_ci break; 32062306a36Sopenharmony_ci case 'k': { /* kill end of line */ 32162306a36Sopenharmony_ci int x, xs, ys; 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci xs = lcd->addr.x; 32462306a36Sopenharmony_ci ys = lcd->addr.y; 32562306a36Sopenharmony_ci for (x = lcd->addr.x; x < lcd->width; x++) 32662306a36Sopenharmony_ci lcd->ops->print(lcd, ' '); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci /* restore cursor position */ 32962306a36Sopenharmony_ci lcd->addr.x = xs; 33062306a36Sopenharmony_ci lcd->addr.y = ys; 33162306a36Sopenharmony_ci lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y); 33262306a36Sopenharmony_ci processed = 1; 33362306a36Sopenharmony_ci break; 33462306a36Sopenharmony_ci } 33562306a36Sopenharmony_ci case 'I': /* reinitialize display */ 33662306a36Sopenharmony_ci lcd->ops->init_display(lcd); 33762306a36Sopenharmony_ci priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | 33862306a36Sopenharmony_ci LCD_FLAG_C | LCD_FLAG_B; 33962306a36Sopenharmony_ci processed = 1; 34062306a36Sopenharmony_ci break; 34162306a36Sopenharmony_ci case 'G': 34262306a36Sopenharmony_ci if (lcd->ops->redefine_char) 34362306a36Sopenharmony_ci processed = lcd->ops->redefine_char(lcd, esc); 34462306a36Sopenharmony_ci else 34562306a36Sopenharmony_ci processed = 1; 34662306a36Sopenharmony_ci break; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci case 'x': /* gotoxy : LxXXX[yYYY]; */ 34962306a36Sopenharmony_ci case 'y': /* gotoxy : LyYYY[xXXX]; */ 35062306a36Sopenharmony_ci if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';') 35162306a36Sopenharmony_ci break; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci /* If the command is valid, move to the new address */ 35462306a36Sopenharmony_ci if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y)) 35562306a36Sopenharmony_ci lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y); 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci /* Regardless of its validity, mark as processed */ 35862306a36Sopenharmony_ci processed = 1; 35962306a36Sopenharmony_ci break; 36062306a36Sopenharmony_ci } 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci return processed; 36362306a36Sopenharmony_ci} 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_cistatic void charlcd_write_char(struct charlcd *lcd, char c) 36662306a36Sopenharmony_ci{ 36762306a36Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci /* first, we'll test if we're in escape mode */ 37062306a36Sopenharmony_ci if ((c != '\n') && priv->esc_seq.len >= 0) { 37162306a36Sopenharmony_ci /* yes, let's add this char to the buffer */ 37262306a36Sopenharmony_ci priv->esc_seq.buf[priv->esc_seq.len++] = c; 37362306a36Sopenharmony_ci priv->esc_seq.buf[priv->esc_seq.len] = '\0'; 37462306a36Sopenharmony_ci } else { 37562306a36Sopenharmony_ci /* aborts any previous escape sequence */ 37662306a36Sopenharmony_ci priv->esc_seq.len = -1; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci switch (c) { 37962306a36Sopenharmony_ci case LCD_ESCAPE_CHAR: 38062306a36Sopenharmony_ci /* start of an escape sequence */ 38162306a36Sopenharmony_ci priv->esc_seq.len = 0; 38262306a36Sopenharmony_ci priv->esc_seq.buf[priv->esc_seq.len] = '\0'; 38362306a36Sopenharmony_ci break; 38462306a36Sopenharmony_ci case '\b': 38562306a36Sopenharmony_ci /* go back one char and clear it */ 38662306a36Sopenharmony_ci if (lcd->addr.x > 0) { 38762306a36Sopenharmony_ci /* back one char */ 38862306a36Sopenharmony_ci if (!lcd->ops->shift_cursor(lcd, 38962306a36Sopenharmony_ci CHARLCD_SHIFT_LEFT)) 39062306a36Sopenharmony_ci lcd->addr.x--; 39162306a36Sopenharmony_ci } 39262306a36Sopenharmony_ci /* replace with a space */ 39362306a36Sopenharmony_ci charlcd_print(lcd, ' '); 39462306a36Sopenharmony_ci /* back one char again */ 39562306a36Sopenharmony_ci if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT)) 39662306a36Sopenharmony_ci lcd->addr.x--; 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci break; 39962306a36Sopenharmony_ci case '\f': 40062306a36Sopenharmony_ci /* quickly clear the display */ 40162306a36Sopenharmony_ci charlcd_clear_display(lcd); 40262306a36Sopenharmony_ci break; 40362306a36Sopenharmony_ci case '\n': 40462306a36Sopenharmony_ci /* 40562306a36Sopenharmony_ci * flush the remainder of the current line and 40662306a36Sopenharmony_ci * go to the beginning of the next line 40762306a36Sopenharmony_ci */ 40862306a36Sopenharmony_ci for (; lcd->addr.x < lcd->width; lcd->addr.x++) 40962306a36Sopenharmony_ci lcd->ops->print(lcd, ' '); 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci lcd->addr.x = 0; 41262306a36Sopenharmony_ci lcd->addr.y = (lcd->addr.y + 1) % lcd->height; 41362306a36Sopenharmony_ci lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y); 41462306a36Sopenharmony_ci break; 41562306a36Sopenharmony_ci case '\r': 41662306a36Sopenharmony_ci /* go to the beginning of the same line */ 41762306a36Sopenharmony_ci lcd->addr.x = 0; 41862306a36Sopenharmony_ci lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y); 41962306a36Sopenharmony_ci break; 42062306a36Sopenharmony_ci case '\t': 42162306a36Sopenharmony_ci /* print a space instead of the tab */ 42262306a36Sopenharmony_ci charlcd_print(lcd, ' '); 42362306a36Sopenharmony_ci break; 42462306a36Sopenharmony_ci default: 42562306a36Sopenharmony_ci /* simply print this char */ 42662306a36Sopenharmony_ci charlcd_print(lcd, c); 42762306a36Sopenharmony_ci break; 42862306a36Sopenharmony_ci } 42962306a36Sopenharmony_ci } 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci /* 43262306a36Sopenharmony_ci * now we'll see if we're in an escape mode and if the current 43362306a36Sopenharmony_ci * escape sequence can be understood. 43462306a36Sopenharmony_ci */ 43562306a36Sopenharmony_ci if (priv->esc_seq.len >= 2) { 43662306a36Sopenharmony_ci int processed = 0; 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci if (!strcmp(priv->esc_seq.buf, "[2J")) { 43962306a36Sopenharmony_ci /* clear the display */ 44062306a36Sopenharmony_ci charlcd_clear_display(lcd); 44162306a36Sopenharmony_ci processed = 1; 44262306a36Sopenharmony_ci } else if (!strcmp(priv->esc_seq.buf, "[H")) { 44362306a36Sopenharmony_ci /* cursor to home */ 44462306a36Sopenharmony_ci charlcd_home(lcd); 44562306a36Sopenharmony_ci processed = 1; 44662306a36Sopenharmony_ci } 44762306a36Sopenharmony_ci /* codes starting with ^[[L */ 44862306a36Sopenharmony_ci else if ((priv->esc_seq.len >= 3) && 44962306a36Sopenharmony_ci (priv->esc_seq.buf[0] == '[') && 45062306a36Sopenharmony_ci (priv->esc_seq.buf[1] == 'L')) { 45162306a36Sopenharmony_ci processed = handle_lcd_special_code(lcd); 45262306a36Sopenharmony_ci } 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci /* LCD special escape codes */ 45562306a36Sopenharmony_ci /* 45662306a36Sopenharmony_ci * flush the escape sequence if it's been processed 45762306a36Sopenharmony_ci * or if it is getting too long. 45862306a36Sopenharmony_ci */ 45962306a36Sopenharmony_ci if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN)) 46062306a36Sopenharmony_ci priv->esc_seq.len = -1; 46162306a36Sopenharmony_ci } /* escape codes */ 46262306a36Sopenharmony_ci} 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_cistatic struct charlcd *the_charlcd; 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_cistatic ssize_t charlcd_write(struct file *file, const char __user *buf, 46762306a36Sopenharmony_ci size_t count, loff_t *ppos) 46862306a36Sopenharmony_ci{ 46962306a36Sopenharmony_ci const char __user *tmp = buf; 47062306a36Sopenharmony_ci char c; 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci for (; count-- > 0; (*ppos)++, tmp++) { 47362306a36Sopenharmony_ci if (((count + 1) & 0x1f) == 0) { 47462306a36Sopenharmony_ci /* 47562306a36Sopenharmony_ci * charlcd_write() is invoked as a VFS->write() callback 47662306a36Sopenharmony_ci * and as such it is always invoked from preemptible 47762306a36Sopenharmony_ci * context and may sleep. 47862306a36Sopenharmony_ci */ 47962306a36Sopenharmony_ci cond_resched(); 48062306a36Sopenharmony_ci } 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci if (get_user(c, tmp)) 48362306a36Sopenharmony_ci return -EFAULT; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci charlcd_write_char(the_charlcd, c); 48662306a36Sopenharmony_ci } 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci return tmp - buf; 48962306a36Sopenharmony_ci} 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_cistatic int charlcd_open(struct inode *inode, struct file *file) 49262306a36Sopenharmony_ci{ 49362306a36Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(the_charlcd); 49462306a36Sopenharmony_ci int ret; 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci ret = -EBUSY; 49762306a36Sopenharmony_ci if (!atomic_dec_and_test(&charlcd_available)) 49862306a36Sopenharmony_ci goto fail; /* open only once at a time */ 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci ret = -EPERM; 50162306a36Sopenharmony_ci if (file->f_mode & FMODE_READ) /* device is write-only */ 50262306a36Sopenharmony_ci goto fail; 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci if (priv->must_clear) { 50562306a36Sopenharmony_ci priv->lcd.ops->clear_display(&priv->lcd); 50662306a36Sopenharmony_ci priv->must_clear = false; 50762306a36Sopenharmony_ci priv->lcd.addr.x = 0; 50862306a36Sopenharmony_ci priv->lcd.addr.y = 0; 50962306a36Sopenharmony_ci } 51062306a36Sopenharmony_ci return nonseekable_open(inode, file); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci fail: 51362306a36Sopenharmony_ci atomic_inc(&charlcd_available); 51462306a36Sopenharmony_ci return ret; 51562306a36Sopenharmony_ci} 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_cistatic int charlcd_release(struct inode *inode, struct file *file) 51862306a36Sopenharmony_ci{ 51962306a36Sopenharmony_ci atomic_inc(&charlcd_available); 52062306a36Sopenharmony_ci return 0; 52162306a36Sopenharmony_ci} 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_cistatic const struct file_operations charlcd_fops = { 52462306a36Sopenharmony_ci .write = charlcd_write, 52562306a36Sopenharmony_ci .open = charlcd_open, 52662306a36Sopenharmony_ci .release = charlcd_release, 52762306a36Sopenharmony_ci .llseek = no_llseek, 52862306a36Sopenharmony_ci}; 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_cistatic struct miscdevice charlcd_dev = { 53162306a36Sopenharmony_ci .minor = LCD_MINOR, 53262306a36Sopenharmony_ci .name = "lcd", 53362306a36Sopenharmony_ci .fops = &charlcd_fops, 53462306a36Sopenharmony_ci}; 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_cistatic void charlcd_puts(struct charlcd *lcd, const char *s) 53762306a36Sopenharmony_ci{ 53862306a36Sopenharmony_ci const char *tmp = s; 53962306a36Sopenharmony_ci int count = strlen(s); 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci for (; count-- > 0; tmp++) { 54262306a36Sopenharmony_ci if (((count + 1) & 0x1f) == 0) 54362306a36Sopenharmony_ci cond_resched(); 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci charlcd_write_char(lcd, *tmp); 54662306a36Sopenharmony_ci } 54762306a36Sopenharmony_ci} 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci#ifdef CONFIG_PANEL_BOOT_MESSAGE 55062306a36Sopenharmony_ci#define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE 55162306a36Sopenharmony_ci#else 55262306a36Sopenharmony_ci#define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n" 55362306a36Sopenharmony_ci#endif 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci#ifdef CONFIG_CHARLCD_BL_ON 55662306a36Sopenharmony_ci#define LCD_INIT_BL "\x1b[L+" 55762306a36Sopenharmony_ci#elif defined(CONFIG_CHARLCD_BL_FLASH) 55862306a36Sopenharmony_ci#define LCD_INIT_BL "\x1b[L*" 55962306a36Sopenharmony_ci#else 56062306a36Sopenharmony_ci#define LCD_INIT_BL "\x1b[L-" 56162306a36Sopenharmony_ci#endif 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci/* initialize the LCD driver */ 56462306a36Sopenharmony_cistatic int charlcd_init(struct charlcd *lcd) 56562306a36Sopenharmony_ci{ 56662306a36Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 56762306a36Sopenharmony_ci int ret; 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | 57062306a36Sopenharmony_ci LCD_FLAG_C | LCD_FLAG_B; 57162306a36Sopenharmony_ci if (lcd->ops->backlight) { 57262306a36Sopenharmony_ci mutex_init(&priv->bl_tempo_lock); 57362306a36Sopenharmony_ci INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off); 57462306a36Sopenharmony_ci } 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci /* 57762306a36Sopenharmony_ci * before this line, we must NOT send anything to the display. 57862306a36Sopenharmony_ci * Since charlcd_init_display() needs to write data, we have to 57962306a36Sopenharmony_ci * enable mark the LCD initialized just before. 58062306a36Sopenharmony_ci */ 58162306a36Sopenharmony_ci if (WARN_ON(!lcd->ops->init_display)) 58262306a36Sopenharmony_ci return -EINVAL; 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci ret = lcd->ops->init_display(lcd); 58562306a36Sopenharmony_ci if (ret) 58662306a36Sopenharmony_ci return ret; 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci /* display a short message */ 58962306a36Sopenharmony_ci charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT); 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci /* clear the display on the next device opening */ 59262306a36Sopenharmony_ci priv->must_clear = true; 59362306a36Sopenharmony_ci charlcd_home(lcd); 59462306a36Sopenharmony_ci return 0; 59562306a36Sopenharmony_ci} 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_cistruct charlcd *charlcd_alloc(void) 59862306a36Sopenharmony_ci{ 59962306a36Sopenharmony_ci struct charlcd_priv *priv; 60062306a36Sopenharmony_ci struct charlcd *lcd; 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci priv = kzalloc(sizeof(*priv), GFP_KERNEL); 60362306a36Sopenharmony_ci if (!priv) 60462306a36Sopenharmony_ci return NULL; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci priv->esc_seq.len = -1; 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci lcd = &priv->lcd; 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci return lcd; 61162306a36Sopenharmony_ci} 61262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(charlcd_alloc); 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_civoid charlcd_free(struct charlcd *lcd) 61562306a36Sopenharmony_ci{ 61662306a36Sopenharmony_ci kfree(charlcd_to_priv(lcd)); 61762306a36Sopenharmony_ci} 61862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(charlcd_free); 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_cistatic int panel_notify_sys(struct notifier_block *this, unsigned long code, 62162306a36Sopenharmony_ci void *unused) 62262306a36Sopenharmony_ci{ 62362306a36Sopenharmony_ci struct charlcd *lcd = the_charlcd; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci switch (code) { 62662306a36Sopenharmony_ci case SYS_DOWN: 62762306a36Sopenharmony_ci charlcd_puts(lcd, 62862306a36Sopenharmony_ci "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); 62962306a36Sopenharmony_ci break; 63062306a36Sopenharmony_ci case SYS_HALT: 63162306a36Sopenharmony_ci charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); 63262306a36Sopenharmony_ci break; 63362306a36Sopenharmony_ci case SYS_POWER_OFF: 63462306a36Sopenharmony_ci charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); 63562306a36Sopenharmony_ci break; 63662306a36Sopenharmony_ci default: 63762306a36Sopenharmony_ci break; 63862306a36Sopenharmony_ci } 63962306a36Sopenharmony_ci return NOTIFY_DONE; 64062306a36Sopenharmony_ci} 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_cistatic struct notifier_block panel_notifier = { 64362306a36Sopenharmony_ci .notifier_call = panel_notify_sys, 64462306a36Sopenharmony_ci}; 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_ciint charlcd_register(struct charlcd *lcd) 64762306a36Sopenharmony_ci{ 64862306a36Sopenharmony_ci int ret; 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci ret = charlcd_init(lcd); 65162306a36Sopenharmony_ci if (ret) 65262306a36Sopenharmony_ci return ret; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci ret = misc_register(&charlcd_dev); 65562306a36Sopenharmony_ci if (ret) 65662306a36Sopenharmony_ci return ret; 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci the_charlcd = lcd; 65962306a36Sopenharmony_ci register_reboot_notifier(&panel_notifier); 66062306a36Sopenharmony_ci return 0; 66162306a36Sopenharmony_ci} 66262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(charlcd_register); 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ciint charlcd_unregister(struct charlcd *lcd) 66562306a36Sopenharmony_ci{ 66662306a36Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_ci unregister_reboot_notifier(&panel_notifier); 66962306a36Sopenharmony_ci charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-"); 67062306a36Sopenharmony_ci misc_deregister(&charlcd_dev); 67162306a36Sopenharmony_ci the_charlcd = NULL; 67262306a36Sopenharmony_ci if (lcd->ops->backlight) { 67362306a36Sopenharmony_ci cancel_delayed_work_sync(&priv->bl_work); 67462306a36Sopenharmony_ci priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF); 67562306a36Sopenharmony_ci } 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci return 0; 67862306a36Sopenharmony_ci} 67962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(charlcd_unregister); 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 682