18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Character LCD driver for Linux 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu> 68c2ecf20Sopenharmony_ci * Copyright (C) 2016-2017 Glider bvba 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/atomic.h> 108c2ecf20Sopenharmony_ci#include <linux/ctype.h> 118c2ecf20Sopenharmony_ci#include <linux/delay.h> 128c2ecf20Sopenharmony_ci#include <linux/fs.h> 138c2ecf20Sopenharmony_ci#include <linux/miscdevice.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/notifier.h> 168c2ecf20Sopenharmony_ci#include <linux/reboot.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 198c2ecf20Sopenharmony_ci#include <linux/workqueue.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include <generated/utsrelease.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#include "charlcd.h" 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define DEFAULT_LCD_BWIDTH 40 268c2ecf20Sopenharmony_ci#define DEFAULT_LCD_HWIDTH 64 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/* Keep the backlight on this many seconds for each flash */ 298c2ecf20Sopenharmony_ci#define LCD_BL_TEMPO_PERIOD 4 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define LCD_FLAG_B 0x0004 /* Blink on */ 328c2ecf20Sopenharmony_ci#define LCD_FLAG_C 0x0008 /* Cursor on */ 338c2ecf20Sopenharmony_ci#define LCD_FLAG_D 0x0010 /* Display on */ 348c2ecf20Sopenharmony_ci#define LCD_FLAG_F 0x0020 /* Large font mode */ 358c2ecf20Sopenharmony_ci#define LCD_FLAG_N 0x0040 /* 2-rows mode */ 368c2ecf20Sopenharmony_ci#define LCD_FLAG_L 0x0080 /* Backlight enabled */ 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci/* LCD commands */ 398c2ecf20Sopenharmony_ci#define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */ 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci#define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */ 428c2ecf20Sopenharmony_ci#define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */ 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci#define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */ 458c2ecf20Sopenharmony_ci#define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */ 468c2ecf20Sopenharmony_ci#define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */ 478c2ecf20Sopenharmony_ci#define LCD_CMD_BLINK_ON 0x01 /* Set blink on */ 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci#define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */ 508c2ecf20Sopenharmony_ci#define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */ 518c2ecf20Sopenharmony_ci#define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */ 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci#define LCD_CMD_FUNCTION_SET 0x20 /* Set function */ 548c2ecf20Sopenharmony_ci#define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */ 558c2ecf20Sopenharmony_ci#define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */ 568c2ecf20Sopenharmony_ci#define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */ 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci#define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */ 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci#define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */ 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci#define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */ 638c2ecf20Sopenharmony_ci#define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */ 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistruct charlcd_priv { 668c2ecf20Sopenharmony_ci struct charlcd lcd; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci struct delayed_work bl_work; 698c2ecf20Sopenharmony_ci struct mutex bl_tempo_lock; /* Protects access to bl_tempo */ 708c2ecf20Sopenharmony_ci bool bl_tempo; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci bool must_clear; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci /* contains the LCD config state */ 758c2ecf20Sopenharmony_ci unsigned long int flags; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci /* Contains the LCD X and Y offset */ 788c2ecf20Sopenharmony_ci struct { 798c2ecf20Sopenharmony_ci unsigned long int x; 808c2ecf20Sopenharmony_ci unsigned long int y; 818c2ecf20Sopenharmony_ci } addr; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci /* Current escape sequence and it's length or -1 if outside */ 848c2ecf20Sopenharmony_ci struct { 858c2ecf20Sopenharmony_ci char buf[LCD_ESCAPE_LEN + 1]; 868c2ecf20Sopenharmony_ci int len; 878c2ecf20Sopenharmony_ci } esc_seq; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci unsigned long long drvdata[]; 908c2ecf20Sopenharmony_ci}; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci#define charlcd_to_priv(p) container_of(p, struct charlcd_priv, lcd) 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci/* Device single-open policy control */ 958c2ecf20Sopenharmony_cistatic atomic_t charlcd_available = ATOMIC_INIT(1); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci/* sleeps that many milliseconds with a reschedule */ 988c2ecf20Sopenharmony_cistatic void long_sleep(int ms) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci schedule_timeout_interruptible(msecs_to_jiffies(ms)); 1018c2ecf20Sopenharmony_ci} 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci/* turn the backlight on or off */ 1048c2ecf20Sopenharmony_cistatic void charlcd_backlight(struct charlcd *lcd, int on) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci if (!lcd->ops->backlight) 1098c2ecf20Sopenharmony_ci return; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci mutex_lock(&priv->bl_tempo_lock); 1128c2ecf20Sopenharmony_ci if (!priv->bl_tempo) 1138c2ecf20Sopenharmony_ci lcd->ops->backlight(lcd, on); 1148c2ecf20Sopenharmony_ci mutex_unlock(&priv->bl_tempo_lock); 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic void charlcd_bl_off(struct work_struct *work) 1188c2ecf20Sopenharmony_ci{ 1198c2ecf20Sopenharmony_ci struct delayed_work *dwork = to_delayed_work(work); 1208c2ecf20Sopenharmony_ci struct charlcd_priv *priv = 1218c2ecf20Sopenharmony_ci container_of(dwork, struct charlcd_priv, bl_work); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci mutex_lock(&priv->bl_tempo_lock); 1248c2ecf20Sopenharmony_ci if (priv->bl_tempo) { 1258c2ecf20Sopenharmony_ci priv->bl_tempo = false; 1268c2ecf20Sopenharmony_ci if (!(priv->flags & LCD_FLAG_L)) 1278c2ecf20Sopenharmony_ci priv->lcd.ops->backlight(&priv->lcd, 0); 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci mutex_unlock(&priv->bl_tempo_lock); 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci/* turn the backlight on for a little while */ 1338c2ecf20Sopenharmony_civoid charlcd_poke(struct charlcd *lcd) 1348c2ecf20Sopenharmony_ci{ 1358c2ecf20Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci if (!lcd->ops->backlight) 1388c2ecf20Sopenharmony_ci return; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&priv->bl_work); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci mutex_lock(&priv->bl_tempo_lock); 1438c2ecf20Sopenharmony_ci if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L)) 1448c2ecf20Sopenharmony_ci lcd->ops->backlight(lcd, 1); 1458c2ecf20Sopenharmony_ci priv->bl_tempo = true; 1468c2ecf20Sopenharmony_ci schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ); 1478c2ecf20Sopenharmony_ci mutex_unlock(&priv->bl_tempo_lock); 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(charlcd_poke); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_cistatic void charlcd_gotoxy(struct charlcd *lcd) 1528c2ecf20Sopenharmony_ci{ 1538c2ecf20Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 1548c2ecf20Sopenharmony_ci unsigned int addr; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci /* 1578c2ecf20Sopenharmony_ci * we force the cursor to stay at the end of the 1588c2ecf20Sopenharmony_ci * line if it wants to go farther 1598c2ecf20Sopenharmony_ci */ 1608c2ecf20Sopenharmony_ci addr = priv->addr.x < lcd->bwidth ? priv->addr.x & (lcd->hwidth - 1) 1618c2ecf20Sopenharmony_ci : lcd->bwidth - 1; 1628c2ecf20Sopenharmony_ci if (priv->addr.y & 1) 1638c2ecf20Sopenharmony_ci addr += lcd->hwidth; 1648c2ecf20Sopenharmony_ci if (priv->addr.y & 2) 1658c2ecf20Sopenharmony_ci addr += lcd->bwidth; 1668c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, LCD_CMD_SET_DDRAM_ADDR | addr); 1678c2ecf20Sopenharmony_ci} 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_cistatic void charlcd_home(struct charlcd *lcd) 1708c2ecf20Sopenharmony_ci{ 1718c2ecf20Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci priv->addr.x = 0; 1748c2ecf20Sopenharmony_ci priv->addr.y = 0; 1758c2ecf20Sopenharmony_ci charlcd_gotoxy(lcd); 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cistatic void charlcd_print(struct charlcd *lcd, char c) 1798c2ecf20Sopenharmony_ci{ 1808c2ecf20Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci if (priv->addr.x < lcd->bwidth) { 1838c2ecf20Sopenharmony_ci if (lcd->char_conv) 1848c2ecf20Sopenharmony_ci c = lcd->char_conv[(unsigned char)c]; 1858c2ecf20Sopenharmony_ci lcd->ops->write_data(lcd, c); 1868c2ecf20Sopenharmony_ci priv->addr.x++; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci /* prevents the cursor from wrapping onto the next line */ 1898c2ecf20Sopenharmony_ci if (priv->addr.x == lcd->bwidth) 1908c2ecf20Sopenharmony_ci charlcd_gotoxy(lcd); 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci} 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_cistatic void charlcd_clear_fast(struct charlcd *lcd) 1958c2ecf20Sopenharmony_ci{ 1968c2ecf20Sopenharmony_ci int pos; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci charlcd_home(lcd); 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci if (lcd->ops->clear_fast) 2018c2ecf20Sopenharmony_ci lcd->ops->clear_fast(lcd); 2028c2ecf20Sopenharmony_ci else 2038c2ecf20Sopenharmony_ci for (pos = 0; pos < min(2, lcd->height) * lcd->hwidth; pos++) 2048c2ecf20Sopenharmony_ci lcd->ops->write_data(lcd, ' '); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci charlcd_home(lcd); 2078c2ecf20Sopenharmony_ci} 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci/* clears the display and resets X/Y */ 2108c2ecf20Sopenharmony_cistatic void charlcd_clear_display(struct charlcd *lcd) 2118c2ecf20Sopenharmony_ci{ 2128c2ecf20Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CLEAR); 2158c2ecf20Sopenharmony_ci priv->addr.x = 0; 2168c2ecf20Sopenharmony_ci priv->addr.y = 0; 2178c2ecf20Sopenharmony_ci /* we must wait a few milliseconds (15) */ 2188c2ecf20Sopenharmony_ci long_sleep(15); 2198c2ecf20Sopenharmony_ci} 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_cistatic int charlcd_init_display(struct charlcd *lcd) 2228c2ecf20Sopenharmony_ci{ 2238c2ecf20Sopenharmony_ci void (*write_cmd_raw)(struct charlcd *lcd, int cmd); 2248c2ecf20Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 2258c2ecf20Sopenharmony_ci u8 init; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci if (lcd->ifwidth != 4 && lcd->ifwidth != 8) 2288c2ecf20Sopenharmony_ci return -EINVAL; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | 2318c2ecf20Sopenharmony_ci LCD_FLAG_C | LCD_FLAG_B; 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci long_sleep(20); /* wait 20 ms after power-up for the paranoid */ 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci /* 2368c2ecf20Sopenharmony_ci * 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure 2378c2ecf20Sopenharmony_ci * the LCD is in 8-bit mode afterwards 2388c2ecf20Sopenharmony_ci */ 2398c2ecf20Sopenharmony_ci init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS; 2408c2ecf20Sopenharmony_ci if (lcd->ifwidth == 4) { 2418c2ecf20Sopenharmony_ci init >>= 4; 2428c2ecf20Sopenharmony_ci write_cmd_raw = lcd->ops->write_cmd_raw4; 2438c2ecf20Sopenharmony_ci } else { 2448c2ecf20Sopenharmony_ci write_cmd_raw = lcd->ops->write_cmd; 2458c2ecf20Sopenharmony_ci } 2468c2ecf20Sopenharmony_ci write_cmd_raw(lcd, init); 2478c2ecf20Sopenharmony_ci long_sleep(10); 2488c2ecf20Sopenharmony_ci write_cmd_raw(lcd, init); 2498c2ecf20Sopenharmony_ci long_sleep(10); 2508c2ecf20Sopenharmony_ci write_cmd_raw(lcd, init); 2518c2ecf20Sopenharmony_ci long_sleep(10); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci if (lcd->ifwidth == 4) { 2548c2ecf20Sopenharmony_ci /* Switch to 4-bit mode, 1 line, small fonts */ 2558c2ecf20Sopenharmony_ci lcd->ops->write_cmd_raw4(lcd, LCD_CMD_FUNCTION_SET >> 4); 2568c2ecf20Sopenharmony_ci long_sleep(10); 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci /* set font height and lines number */ 2608c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, 2618c2ecf20Sopenharmony_ci LCD_CMD_FUNCTION_SET | 2628c2ecf20Sopenharmony_ci ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | 2638c2ecf20Sopenharmony_ci ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | 2648c2ecf20Sopenharmony_ci ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); 2658c2ecf20Sopenharmony_ci long_sleep(10); 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci /* display off, cursor off, blink off */ 2688c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CTRL); 2698c2ecf20Sopenharmony_ci long_sleep(10); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, 2728c2ecf20Sopenharmony_ci LCD_CMD_DISPLAY_CTRL | /* set display mode */ 2738c2ecf20Sopenharmony_ci ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | 2748c2ecf20Sopenharmony_ci ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | 2758c2ecf20Sopenharmony_ci ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci charlcd_backlight(lcd, (priv->flags & LCD_FLAG_L) ? 1 : 0); 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci long_sleep(10); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci /* entry mode set : increment, cursor shifting */ 2828c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC); 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci charlcd_clear_display(lcd); 2858c2ecf20Sopenharmony_ci return 0; 2868c2ecf20Sopenharmony_ci} 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci/* 2898c2ecf20Sopenharmony_ci * Parses a movement command of the form "(.*);", where the group can be 2908c2ecf20Sopenharmony_ci * any number of subcommands of the form "(x|y)[0-9]+". 2918c2ecf20Sopenharmony_ci * 2928c2ecf20Sopenharmony_ci * Returns whether the command is valid. The position arguments are 2938c2ecf20Sopenharmony_ci * only written if the parsing was successful. 2948c2ecf20Sopenharmony_ci * 2958c2ecf20Sopenharmony_ci * For instance: 2968c2ecf20Sopenharmony_ci * - ";" returns (<original x>, <original y>). 2978c2ecf20Sopenharmony_ci * - "x1;" returns (1, <original y>). 2988c2ecf20Sopenharmony_ci * - "y2x1;" returns (1, 2). 2998c2ecf20Sopenharmony_ci * - "x12y34x56;" returns (56, 34). 3008c2ecf20Sopenharmony_ci * - "" fails. 3018c2ecf20Sopenharmony_ci * - "x" fails. 3028c2ecf20Sopenharmony_ci * - "x;" fails. 3038c2ecf20Sopenharmony_ci * - "x1" fails. 3048c2ecf20Sopenharmony_ci * - "xy12;" fails. 3058c2ecf20Sopenharmony_ci * - "x12yy12;" fails. 3068c2ecf20Sopenharmony_ci * - "xx" fails. 3078c2ecf20Sopenharmony_ci */ 3088c2ecf20Sopenharmony_cistatic bool parse_xy(const char *s, unsigned long *x, unsigned long *y) 3098c2ecf20Sopenharmony_ci{ 3108c2ecf20Sopenharmony_ci unsigned long new_x = *x; 3118c2ecf20Sopenharmony_ci unsigned long new_y = *y; 3128c2ecf20Sopenharmony_ci char *p; 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci for (;;) { 3158c2ecf20Sopenharmony_ci if (!*s) 3168c2ecf20Sopenharmony_ci return false; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci if (*s == ';') 3198c2ecf20Sopenharmony_ci break; 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci if (*s == 'x') { 3228c2ecf20Sopenharmony_ci new_x = simple_strtoul(s + 1, &p, 10); 3238c2ecf20Sopenharmony_ci if (p == s + 1) 3248c2ecf20Sopenharmony_ci return false; 3258c2ecf20Sopenharmony_ci s = p; 3268c2ecf20Sopenharmony_ci } else if (*s == 'y') { 3278c2ecf20Sopenharmony_ci new_y = simple_strtoul(s + 1, &p, 10); 3288c2ecf20Sopenharmony_ci if (p == s + 1) 3298c2ecf20Sopenharmony_ci return false; 3308c2ecf20Sopenharmony_ci s = p; 3318c2ecf20Sopenharmony_ci } else { 3328c2ecf20Sopenharmony_ci return false; 3338c2ecf20Sopenharmony_ci } 3348c2ecf20Sopenharmony_ci } 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci *x = new_x; 3378c2ecf20Sopenharmony_ci *y = new_y; 3388c2ecf20Sopenharmony_ci return true; 3398c2ecf20Sopenharmony_ci} 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci/* 3428c2ecf20Sopenharmony_ci * These are the file operation function for user access to /dev/lcd 3438c2ecf20Sopenharmony_ci * This function can also be called from inside the kernel, by 3448c2ecf20Sopenharmony_ci * setting file and ppos to NULL. 3458c2ecf20Sopenharmony_ci * 3468c2ecf20Sopenharmony_ci */ 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_cistatic inline int handle_lcd_special_code(struct charlcd *lcd) 3498c2ecf20Sopenharmony_ci{ 3508c2ecf20Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_ci /* LCD special codes */ 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ci int processed = 0; 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci char *esc = priv->esc_seq.buf + 2; 3578c2ecf20Sopenharmony_ci int oldflags = priv->flags; 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci /* check for display mode flags */ 3608c2ecf20Sopenharmony_ci switch (*esc) { 3618c2ecf20Sopenharmony_ci case 'D': /* Display ON */ 3628c2ecf20Sopenharmony_ci priv->flags |= LCD_FLAG_D; 3638c2ecf20Sopenharmony_ci processed = 1; 3648c2ecf20Sopenharmony_ci break; 3658c2ecf20Sopenharmony_ci case 'd': /* Display OFF */ 3668c2ecf20Sopenharmony_ci priv->flags &= ~LCD_FLAG_D; 3678c2ecf20Sopenharmony_ci processed = 1; 3688c2ecf20Sopenharmony_ci break; 3698c2ecf20Sopenharmony_ci case 'C': /* Cursor ON */ 3708c2ecf20Sopenharmony_ci priv->flags |= LCD_FLAG_C; 3718c2ecf20Sopenharmony_ci processed = 1; 3728c2ecf20Sopenharmony_ci break; 3738c2ecf20Sopenharmony_ci case 'c': /* Cursor OFF */ 3748c2ecf20Sopenharmony_ci priv->flags &= ~LCD_FLAG_C; 3758c2ecf20Sopenharmony_ci processed = 1; 3768c2ecf20Sopenharmony_ci break; 3778c2ecf20Sopenharmony_ci case 'B': /* Blink ON */ 3788c2ecf20Sopenharmony_ci priv->flags |= LCD_FLAG_B; 3798c2ecf20Sopenharmony_ci processed = 1; 3808c2ecf20Sopenharmony_ci break; 3818c2ecf20Sopenharmony_ci case 'b': /* Blink OFF */ 3828c2ecf20Sopenharmony_ci priv->flags &= ~LCD_FLAG_B; 3838c2ecf20Sopenharmony_ci processed = 1; 3848c2ecf20Sopenharmony_ci break; 3858c2ecf20Sopenharmony_ci case '+': /* Back light ON */ 3868c2ecf20Sopenharmony_ci priv->flags |= LCD_FLAG_L; 3878c2ecf20Sopenharmony_ci processed = 1; 3888c2ecf20Sopenharmony_ci break; 3898c2ecf20Sopenharmony_ci case '-': /* Back light OFF */ 3908c2ecf20Sopenharmony_ci priv->flags &= ~LCD_FLAG_L; 3918c2ecf20Sopenharmony_ci processed = 1; 3928c2ecf20Sopenharmony_ci break; 3938c2ecf20Sopenharmony_ci case '*': /* Flash back light */ 3948c2ecf20Sopenharmony_ci charlcd_poke(lcd); 3958c2ecf20Sopenharmony_ci processed = 1; 3968c2ecf20Sopenharmony_ci break; 3978c2ecf20Sopenharmony_ci case 'f': /* Small Font */ 3988c2ecf20Sopenharmony_ci priv->flags &= ~LCD_FLAG_F; 3998c2ecf20Sopenharmony_ci processed = 1; 4008c2ecf20Sopenharmony_ci break; 4018c2ecf20Sopenharmony_ci case 'F': /* Large Font */ 4028c2ecf20Sopenharmony_ci priv->flags |= LCD_FLAG_F; 4038c2ecf20Sopenharmony_ci processed = 1; 4048c2ecf20Sopenharmony_ci break; 4058c2ecf20Sopenharmony_ci case 'n': /* One Line */ 4068c2ecf20Sopenharmony_ci priv->flags &= ~LCD_FLAG_N; 4078c2ecf20Sopenharmony_ci processed = 1; 4088c2ecf20Sopenharmony_ci break; 4098c2ecf20Sopenharmony_ci case 'N': /* Two Lines */ 4108c2ecf20Sopenharmony_ci priv->flags |= LCD_FLAG_N; 4118c2ecf20Sopenharmony_ci processed = 1; 4128c2ecf20Sopenharmony_ci break; 4138c2ecf20Sopenharmony_ci case 'l': /* Shift Cursor Left */ 4148c2ecf20Sopenharmony_ci if (priv->addr.x > 0) { 4158c2ecf20Sopenharmony_ci /* back one char if not at end of line */ 4168c2ecf20Sopenharmony_ci if (priv->addr.x < lcd->bwidth) 4178c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); 4188c2ecf20Sopenharmony_ci priv->addr.x--; 4198c2ecf20Sopenharmony_ci } 4208c2ecf20Sopenharmony_ci processed = 1; 4218c2ecf20Sopenharmony_ci break; 4228c2ecf20Sopenharmony_ci case 'r': /* shift cursor right */ 4238c2ecf20Sopenharmony_ci if (priv->addr.x < lcd->width) { 4248c2ecf20Sopenharmony_ci /* allow the cursor to pass the end of the line */ 4258c2ecf20Sopenharmony_ci if (priv->addr.x < (lcd->bwidth - 1)) 4268c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, 4278c2ecf20Sopenharmony_ci LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT); 4288c2ecf20Sopenharmony_ci priv->addr.x++; 4298c2ecf20Sopenharmony_ci } 4308c2ecf20Sopenharmony_ci processed = 1; 4318c2ecf20Sopenharmony_ci break; 4328c2ecf20Sopenharmony_ci case 'L': /* shift display left */ 4338c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT); 4348c2ecf20Sopenharmony_ci processed = 1; 4358c2ecf20Sopenharmony_ci break; 4368c2ecf20Sopenharmony_ci case 'R': /* shift display right */ 4378c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, 4388c2ecf20Sopenharmony_ci LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT | 4398c2ecf20Sopenharmony_ci LCD_CMD_SHIFT_RIGHT); 4408c2ecf20Sopenharmony_ci processed = 1; 4418c2ecf20Sopenharmony_ci break; 4428c2ecf20Sopenharmony_ci case 'k': { /* kill end of line */ 4438c2ecf20Sopenharmony_ci int x; 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_ci for (x = priv->addr.x; x < lcd->bwidth; x++) 4468c2ecf20Sopenharmony_ci lcd->ops->write_data(lcd, ' '); 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci /* restore cursor position */ 4498c2ecf20Sopenharmony_ci charlcd_gotoxy(lcd); 4508c2ecf20Sopenharmony_ci processed = 1; 4518c2ecf20Sopenharmony_ci break; 4528c2ecf20Sopenharmony_ci } 4538c2ecf20Sopenharmony_ci case 'I': /* reinitialize display */ 4548c2ecf20Sopenharmony_ci charlcd_init_display(lcd); 4558c2ecf20Sopenharmony_ci processed = 1; 4568c2ecf20Sopenharmony_ci break; 4578c2ecf20Sopenharmony_ci case 'G': { 4588c2ecf20Sopenharmony_ci /* Generator : LGcxxxxx...xx; must have <c> between '0' 4598c2ecf20Sopenharmony_ci * and '7', representing the numerical ASCII code of the 4608c2ecf20Sopenharmony_ci * redefined character, and <xx...xx> a sequence of 16 4618c2ecf20Sopenharmony_ci * hex digits representing 8 bytes for each character. 4628c2ecf20Sopenharmony_ci * Most LCDs will only use 5 lower bits of the 7 first 4638c2ecf20Sopenharmony_ci * bytes. 4648c2ecf20Sopenharmony_ci */ 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_ci unsigned char cgbytes[8]; 4678c2ecf20Sopenharmony_ci unsigned char cgaddr; 4688c2ecf20Sopenharmony_ci int cgoffset; 4698c2ecf20Sopenharmony_ci int shift; 4708c2ecf20Sopenharmony_ci char value; 4718c2ecf20Sopenharmony_ci int addr; 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci if (!strchr(esc, ';')) 4748c2ecf20Sopenharmony_ci break; 4758c2ecf20Sopenharmony_ci 4768c2ecf20Sopenharmony_ci esc++; 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_ci cgaddr = *(esc++) - '0'; 4798c2ecf20Sopenharmony_ci if (cgaddr > 7) { 4808c2ecf20Sopenharmony_ci processed = 1; 4818c2ecf20Sopenharmony_ci break; 4828c2ecf20Sopenharmony_ci } 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_ci cgoffset = 0; 4858c2ecf20Sopenharmony_ci shift = 0; 4868c2ecf20Sopenharmony_ci value = 0; 4878c2ecf20Sopenharmony_ci while (*esc && cgoffset < 8) { 4888c2ecf20Sopenharmony_ci int half; 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci shift ^= 4; 4918c2ecf20Sopenharmony_ci 4928c2ecf20Sopenharmony_ci half = hex_to_bin(*esc++); 4938c2ecf20Sopenharmony_ci if (half < 0) 4948c2ecf20Sopenharmony_ci continue; 4958c2ecf20Sopenharmony_ci 4968c2ecf20Sopenharmony_ci value |= half << shift; 4978c2ecf20Sopenharmony_ci if (shift == 0) { 4988c2ecf20Sopenharmony_ci cgbytes[cgoffset++] = value; 4998c2ecf20Sopenharmony_ci value = 0; 5008c2ecf20Sopenharmony_ci } 5018c2ecf20Sopenharmony_ci } 5028c2ecf20Sopenharmony_ci 5038c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8)); 5048c2ecf20Sopenharmony_ci for (addr = 0; addr < cgoffset; addr++) 5058c2ecf20Sopenharmony_ci lcd->ops->write_data(lcd, cgbytes[addr]); 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_ci /* ensures that we stop writing to CGRAM */ 5088c2ecf20Sopenharmony_ci charlcd_gotoxy(lcd); 5098c2ecf20Sopenharmony_ci processed = 1; 5108c2ecf20Sopenharmony_ci break; 5118c2ecf20Sopenharmony_ci } 5128c2ecf20Sopenharmony_ci case 'x': /* gotoxy : LxXXX[yYYY]; */ 5138c2ecf20Sopenharmony_ci case 'y': /* gotoxy : LyYYY[xXXX]; */ 5148c2ecf20Sopenharmony_ci if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';') 5158c2ecf20Sopenharmony_ci break; 5168c2ecf20Sopenharmony_ci 5178c2ecf20Sopenharmony_ci /* If the command is valid, move to the new address */ 5188c2ecf20Sopenharmony_ci if (parse_xy(esc, &priv->addr.x, &priv->addr.y)) 5198c2ecf20Sopenharmony_ci charlcd_gotoxy(lcd); 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci /* Regardless of its validity, mark as processed */ 5228c2ecf20Sopenharmony_ci processed = 1; 5238c2ecf20Sopenharmony_ci break; 5248c2ecf20Sopenharmony_ci } 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_ci /* TODO: This indent party here got ugly, clean it! */ 5278c2ecf20Sopenharmony_ci /* Check whether one flag was changed */ 5288c2ecf20Sopenharmony_ci if (oldflags == priv->flags) 5298c2ecf20Sopenharmony_ci return processed; 5308c2ecf20Sopenharmony_ci 5318c2ecf20Sopenharmony_ci /* check whether one of B,C,D flags were changed */ 5328c2ecf20Sopenharmony_ci if ((oldflags ^ priv->flags) & 5338c2ecf20Sopenharmony_ci (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D)) 5348c2ecf20Sopenharmony_ci /* set display mode */ 5358c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, 5368c2ecf20Sopenharmony_ci LCD_CMD_DISPLAY_CTRL | 5378c2ecf20Sopenharmony_ci ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | 5388c2ecf20Sopenharmony_ci ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | 5398c2ecf20Sopenharmony_ci ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); 5408c2ecf20Sopenharmony_ci /* check whether one of F,N flags was changed */ 5418c2ecf20Sopenharmony_ci else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N)) 5428c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, 5438c2ecf20Sopenharmony_ci LCD_CMD_FUNCTION_SET | 5448c2ecf20Sopenharmony_ci ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | 5458c2ecf20Sopenharmony_ci ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | 5468c2ecf20Sopenharmony_ci ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); 5478c2ecf20Sopenharmony_ci /* check whether L flag was changed */ 5488c2ecf20Sopenharmony_ci else if ((oldflags ^ priv->flags) & LCD_FLAG_L) 5498c2ecf20Sopenharmony_ci charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L)); 5508c2ecf20Sopenharmony_ci 5518c2ecf20Sopenharmony_ci return processed; 5528c2ecf20Sopenharmony_ci} 5538c2ecf20Sopenharmony_ci 5548c2ecf20Sopenharmony_cistatic void charlcd_write_char(struct charlcd *lcd, char c) 5558c2ecf20Sopenharmony_ci{ 5568c2ecf20Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci /* first, we'll test if we're in escape mode */ 5598c2ecf20Sopenharmony_ci if ((c != '\n') && priv->esc_seq.len >= 0) { 5608c2ecf20Sopenharmony_ci /* yes, let's add this char to the buffer */ 5618c2ecf20Sopenharmony_ci priv->esc_seq.buf[priv->esc_seq.len++] = c; 5628c2ecf20Sopenharmony_ci priv->esc_seq.buf[priv->esc_seq.len] = '\0'; 5638c2ecf20Sopenharmony_ci } else { 5648c2ecf20Sopenharmony_ci /* aborts any previous escape sequence */ 5658c2ecf20Sopenharmony_ci priv->esc_seq.len = -1; 5668c2ecf20Sopenharmony_ci 5678c2ecf20Sopenharmony_ci switch (c) { 5688c2ecf20Sopenharmony_ci case LCD_ESCAPE_CHAR: 5698c2ecf20Sopenharmony_ci /* start of an escape sequence */ 5708c2ecf20Sopenharmony_ci priv->esc_seq.len = 0; 5718c2ecf20Sopenharmony_ci priv->esc_seq.buf[priv->esc_seq.len] = '\0'; 5728c2ecf20Sopenharmony_ci break; 5738c2ecf20Sopenharmony_ci case '\b': 5748c2ecf20Sopenharmony_ci /* go back one char and clear it */ 5758c2ecf20Sopenharmony_ci if (priv->addr.x > 0) { 5768c2ecf20Sopenharmony_ci /* 5778c2ecf20Sopenharmony_ci * check if we're not at the 5788c2ecf20Sopenharmony_ci * end of the line 5798c2ecf20Sopenharmony_ci */ 5808c2ecf20Sopenharmony_ci if (priv->addr.x < lcd->bwidth) 5818c2ecf20Sopenharmony_ci /* back one char */ 5828c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); 5838c2ecf20Sopenharmony_ci priv->addr.x--; 5848c2ecf20Sopenharmony_ci } 5858c2ecf20Sopenharmony_ci /* replace with a space */ 5868c2ecf20Sopenharmony_ci lcd->ops->write_data(lcd, ' '); 5878c2ecf20Sopenharmony_ci /* back one char again */ 5888c2ecf20Sopenharmony_ci lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); 5898c2ecf20Sopenharmony_ci break; 5908c2ecf20Sopenharmony_ci case '\f': 5918c2ecf20Sopenharmony_ci /* quickly clear the display */ 5928c2ecf20Sopenharmony_ci charlcd_clear_fast(lcd); 5938c2ecf20Sopenharmony_ci break; 5948c2ecf20Sopenharmony_ci case '\n': 5958c2ecf20Sopenharmony_ci /* 5968c2ecf20Sopenharmony_ci * flush the remainder of the current line and 5978c2ecf20Sopenharmony_ci * go to the beginning of the next line 5988c2ecf20Sopenharmony_ci */ 5998c2ecf20Sopenharmony_ci for (; priv->addr.x < lcd->bwidth; priv->addr.x++) 6008c2ecf20Sopenharmony_ci lcd->ops->write_data(lcd, ' '); 6018c2ecf20Sopenharmony_ci priv->addr.x = 0; 6028c2ecf20Sopenharmony_ci priv->addr.y = (priv->addr.y + 1) % lcd->height; 6038c2ecf20Sopenharmony_ci charlcd_gotoxy(lcd); 6048c2ecf20Sopenharmony_ci break; 6058c2ecf20Sopenharmony_ci case '\r': 6068c2ecf20Sopenharmony_ci /* go to the beginning of the same line */ 6078c2ecf20Sopenharmony_ci priv->addr.x = 0; 6088c2ecf20Sopenharmony_ci charlcd_gotoxy(lcd); 6098c2ecf20Sopenharmony_ci break; 6108c2ecf20Sopenharmony_ci case '\t': 6118c2ecf20Sopenharmony_ci /* print a space instead of the tab */ 6128c2ecf20Sopenharmony_ci charlcd_print(lcd, ' '); 6138c2ecf20Sopenharmony_ci break; 6148c2ecf20Sopenharmony_ci default: 6158c2ecf20Sopenharmony_ci /* simply print this char */ 6168c2ecf20Sopenharmony_ci charlcd_print(lcd, c); 6178c2ecf20Sopenharmony_ci break; 6188c2ecf20Sopenharmony_ci } 6198c2ecf20Sopenharmony_ci } 6208c2ecf20Sopenharmony_ci 6218c2ecf20Sopenharmony_ci /* 6228c2ecf20Sopenharmony_ci * now we'll see if we're in an escape mode and if the current 6238c2ecf20Sopenharmony_ci * escape sequence can be understood. 6248c2ecf20Sopenharmony_ci */ 6258c2ecf20Sopenharmony_ci if (priv->esc_seq.len >= 2) { 6268c2ecf20Sopenharmony_ci int processed = 0; 6278c2ecf20Sopenharmony_ci 6288c2ecf20Sopenharmony_ci if (!strcmp(priv->esc_seq.buf, "[2J")) { 6298c2ecf20Sopenharmony_ci /* clear the display */ 6308c2ecf20Sopenharmony_ci charlcd_clear_fast(lcd); 6318c2ecf20Sopenharmony_ci processed = 1; 6328c2ecf20Sopenharmony_ci } else if (!strcmp(priv->esc_seq.buf, "[H")) { 6338c2ecf20Sopenharmony_ci /* cursor to home */ 6348c2ecf20Sopenharmony_ci charlcd_home(lcd); 6358c2ecf20Sopenharmony_ci processed = 1; 6368c2ecf20Sopenharmony_ci } 6378c2ecf20Sopenharmony_ci /* codes starting with ^[[L */ 6388c2ecf20Sopenharmony_ci else if ((priv->esc_seq.len >= 3) && 6398c2ecf20Sopenharmony_ci (priv->esc_seq.buf[0] == '[') && 6408c2ecf20Sopenharmony_ci (priv->esc_seq.buf[1] == 'L')) { 6418c2ecf20Sopenharmony_ci processed = handle_lcd_special_code(lcd); 6428c2ecf20Sopenharmony_ci } 6438c2ecf20Sopenharmony_ci 6448c2ecf20Sopenharmony_ci /* LCD special escape codes */ 6458c2ecf20Sopenharmony_ci /* 6468c2ecf20Sopenharmony_ci * flush the escape sequence if it's been processed 6478c2ecf20Sopenharmony_ci * or if it is getting too long. 6488c2ecf20Sopenharmony_ci */ 6498c2ecf20Sopenharmony_ci if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN)) 6508c2ecf20Sopenharmony_ci priv->esc_seq.len = -1; 6518c2ecf20Sopenharmony_ci } /* escape codes */ 6528c2ecf20Sopenharmony_ci} 6538c2ecf20Sopenharmony_ci 6548c2ecf20Sopenharmony_cistatic struct charlcd *the_charlcd; 6558c2ecf20Sopenharmony_ci 6568c2ecf20Sopenharmony_cistatic ssize_t charlcd_write(struct file *file, const char __user *buf, 6578c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 6588c2ecf20Sopenharmony_ci{ 6598c2ecf20Sopenharmony_ci const char __user *tmp = buf; 6608c2ecf20Sopenharmony_ci char c; 6618c2ecf20Sopenharmony_ci 6628c2ecf20Sopenharmony_ci for (; count-- > 0; (*ppos)++, tmp++) { 6638c2ecf20Sopenharmony_ci if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) 6648c2ecf20Sopenharmony_ci /* 6658c2ecf20Sopenharmony_ci * let's be a little nice with other processes 6668c2ecf20Sopenharmony_ci * that need some CPU 6678c2ecf20Sopenharmony_ci */ 6688c2ecf20Sopenharmony_ci schedule(); 6698c2ecf20Sopenharmony_ci 6708c2ecf20Sopenharmony_ci if (get_user(c, tmp)) 6718c2ecf20Sopenharmony_ci return -EFAULT; 6728c2ecf20Sopenharmony_ci 6738c2ecf20Sopenharmony_ci charlcd_write_char(the_charlcd, c); 6748c2ecf20Sopenharmony_ci } 6758c2ecf20Sopenharmony_ci 6768c2ecf20Sopenharmony_ci return tmp - buf; 6778c2ecf20Sopenharmony_ci} 6788c2ecf20Sopenharmony_ci 6798c2ecf20Sopenharmony_cistatic int charlcd_open(struct inode *inode, struct file *file) 6808c2ecf20Sopenharmony_ci{ 6818c2ecf20Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(the_charlcd); 6828c2ecf20Sopenharmony_ci int ret; 6838c2ecf20Sopenharmony_ci 6848c2ecf20Sopenharmony_ci ret = -EBUSY; 6858c2ecf20Sopenharmony_ci if (!atomic_dec_and_test(&charlcd_available)) 6868c2ecf20Sopenharmony_ci goto fail; /* open only once at a time */ 6878c2ecf20Sopenharmony_ci 6888c2ecf20Sopenharmony_ci ret = -EPERM; 6898c2ecf20Sopenharmony_ci if (file->f_mode & FMODE_READ) /* device is write-only */ 6908c2ecf20Sopenharmony_ci goto fail; 6918c2ecf20Sopenharmony_ci 6928c2ecf20Sopenharmony_ci if (priv->must_clear) { 6938c2ecf20Sopenharmony_ci charlcd_clear_display(&priv->lcd); 6948c2ecf20Sopenharmony_ci priv->must_clear = false; 6958c2ecf20Sopenharmony_ci } 6968c2ecf20Sopenharmony_ci return nonseekable_open(inode, file); 6978c2ecf20Sopenharmony_ci 6988c2ecf20Sopenharmony_ci fail: 6998c2ecf20Sopenharmony_ci atomic_inc(&charlcd_available); 7008c2ecf20Sopenharmony_ci return ret; 7018c2ecf20Sopenharmony_ci} 7028c2ecf20Sopenharmony_ci 7038c2ecf20Sopenharmony_cistatic int charlcd_release(struct inode *inode, struct file *file) 7048c2ecf20Sopenharmony_ci{ 7058c2ecf20Sopenharmony_ci atomic_inc(&charlcd_available); 7068c2ecf20Sopenharmony_ci return 0; 7078c2ecf20Sopenharmony_ci} 7088c2ecf20Sopenharmony_ci 7098c2ecf20Sopenharmony_cistatic const struct file_operations charlcd_fops = { 7108c2ecf20Sopenharmony_ci .write = charlcd_write, 7118c2ecf20Sopenharmony_ci .open = charlcd_open, 7128c2ecf20Sopenharmony_ci .release = charlcd_release, 7138c2ecf20Sopenharmony_ci .llseek = no_llseek, 7148c2ecf20Sopenharmony_ci}; 7158c2ecf20Sopenharmony_ci 7168c2ecf20Sopenharmony_cistatic struct miscdevice charlcd_dev = { 7178c2ecf20Sopenharmony_ci .minor = LCD_MINOR, 7188c2ecf20Sopenharmony_ci .name = "lcd", 7198c2ecf20Sopenharmony_ci .fops = &charlcd_fops, 7208c2ecf20Sopenharmony_ci}; 7218c2ecf20Sopenharmony_ci 7228c2ecf20Sopenharmony_cistatic void charlcd_puts(struct charlcd *lcd, const char *s) 7238c2ecf20Sopenharmony_ci{ 7248c2ecf20Sopenharmony_ci const char *tmp = s; 7258c2ecf20Sopenharmony_ci int count = strlen(s); 7268c2ecf20Sopenharmony_ci 7278c2ecf20Sopenharmony_ci for (; count-- > 0; tmp++) { 7288c2ecf20Sopenharmony_ci if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) 7298c2ecf20Sopenharmony_ci /* 7308c2ecf20Sopenharmony_ci * let's be a little nice with other processes 7318c2ecf20Sopenharmony_ci * that need some CPU 7328c2ecf20Sopenharmony_ci */ 7338c2ecf20Sopenharmony_ci schedule(); 7348c2ecf20Sopenharmony_ci 7358c2ecf20Sopenharmony_ci charlcd_write_char(lcd, *tmp); 7368c2ecf20Sopenharmony_ci } 7378c2ecf20Sopenharmony_ci} 7388c2ecf20Sopenharmony_ci 7398c2ecf20Sopenharmony_ci#ifdef CONFIG_PANEL_BOOT_MESSAGE 7408c2ecf20Sopenharmony_ci#define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE 7418c2ecf20Sopenharmony_ci#else 7428c2ecf20Sopenharmony_ci#define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n" 7438c2ecf20Sopenharmony_ci#endif 7448c2ecf20Sopenharmony_ci 7458c2ecf20Sopenharmony_ci#ifdef CONFIG_CHARLCD_BL_ON 7468c2ecf20Sopenharmony_ci#define LCD_INIT_BL "\x1b[L+" 7478c2ecf20Sopenharmony_ci#elif defined(CONFIG_CHARLCD_BL_FLASH) 7488c2ecf20Sopenharmony_ci#define LCD_INIT_BL "\x1b[L*" 7498c2ecf20Sopenharmony_ci#else 7508c2ecf20Sopenharmony_ci#define LCD_INIT_BL "\x1b[L-" 7518c2ecf20Sopenharmony_ci#endif 7528c2ecf20Sopenharmony_ci 7538c2ecf20Sopenharmony_ci/* initialize the LCD driver */ 7548c2ecf20Sopenharmony_cistatic int charlcd_init(struct charlcd *lcd) 7558c2ecf20Sopenharmony_ci{ 7568c2ecf20Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 7578c2ecf20Sopenharmony_ci int ret; 7588c2ecf20Sopenharmony_ci 7598c2ecf20Sopenharmony_ci if (lcd->ops->backlight) { 7608c2ecf20Sopenharmony_ci mutex_init(&priv->bl_tempo_lock); 7618c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off); 7628c2ecf20Sopenharmony_ci } 7638c2ecf20Sopenharmony_ci 7648c2ecf20Sopenharmony_ci /* 7658c2ecf20Sopenharmony_ci * before this line, we must NOT send anything to the display. 7668c2ecf20Sopenharmony_ci * Since charlcd_init_display() needs to write data, we have to 7678c2ecf20Sopenharmony_ci * enable mark the LCD initialized just before. 7688c2ecf20Sopenharmony_ci */ 7698c2ecf20Sopenharmony_ci ret = charlcd_init_display(lcd); 7708c2ecf20Sopenharmony_ci if (ret) 7718c2ecf20Sopenharmony_ci return ret; 7728c2ecf20Sopenharmony_ci 7738c2ecf20Sopenharmony_ci /* display a short message */ 7748c2ecf20Sopenharmony_ci charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT); 7758c2ecf20Sopenharmony_ci 7768c2ecf20Sopenharmony_ci /* clear the display on the next device opening */ 7778c2ecf20Sopenharmony_ci priv->must_clear = true; 7788c2ecf20Sopenharmony_ci charlcd_home(lcd); 7798c2ecf20Sopenharmony_ci return 0; 7808c2ecf20Sopenharmony_ci} 7818c2ecf20Sopenharmony_ci 7828c2ecf20Sopenharmony_cistruct charlcd *charlcd_alloc(unsigned int drvdata_size) 7838c2ecf20Sopenharmony_ci{ 7848c2ecf20Sopenharmony_ci struct charlcd_priv *priv; 7858c2ecf20Sopenharmony_ci struct charlcd *lcd; 7868c2ecf20Sopenharmony_ci 7878c2ecf20Sopenharmony_ci priv = kzalloc(sizeof(*priv) + drvdata_size, GFP_KERNEL); 7888c2ecf20Sopenharmony_ci if (!priv) 7898c2ecf20Sopenharmony_ci return NULL; 7908c2ecf20Sopenharmony_ci 7918c2ecf20Sopenharmony_ci priv->esc_seq.len = -1; 7928c2ecf20Sopenharmony_ci 7938c2ecf20Sopenharmony_ci lcd = &priv->lcd; 7948c2ecf20Sopenharmony_ci lcd->ifwidth = 8; 7958c2ecf20Sopenharmony_ci lcd->bwidth = DEFAULT_LCD_BWIDTH; 7968c2ecf20Sopenharmony_ci lcd->hwidth = DEFAULT_LCD_HWIDTH; 7978c2ecf20Sopenharmony_ci lcd->drvdata = priv->drvdata; 7988c2ecf20Sopenharmony_ci 7998c2ecf20Sopenharmony_ci return lcd; 8008c2ecf20Sopenharmony_ci} 8018c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(charlcd_alloc); 8028c2ecf20Sopenharmony_ci 8038c2ecf20Sopenharmony_civoid charlcd_free(struct charlcd *lcd) 8048c2ecf20Sopenharmony_ci{ 8058c2ecf20Sopenharmony_ci kfree(charlcd_to_priv(lcd)); 8068c2ecf20Sopenharmony_ci} 8078c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(charlcd_free); 8088c2ecf20Sopenharmony_ci 8098c2ecf20Sopenharmony_cistatic int panel_notify_sys(struct notifier_block *this, unsigned long code, 8108c2ecf20Sopenharmony_ci void *unused) 8118c2ecf20Sopenharmony_ci{ 8128c2ecf20Sopenharmony_ci struct charlcd *lcd = the_charlcd; 8138c2ecf20Sopenharmony_ci 8148c2ecf20Sopenharmony_ci switch (code) { 8158c2ecf20Sopenharmony_ci case SYS_DOWN: 8168c2ecf20Sopenharmony_ci charlcd_puts(lcd, 8178c2ecf20Sopenharmony_ci "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); 8188c2ecf20Sopenharmony_ci break; 8198c2ecf20Sopenharmony_ci case SYS_HALT: 8208c2ecf20Sopenharmony_ci charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); 8218c2ecf20Sopenharmony_ci break; 8228c2ecf20Sopenharmony_ci case SYS_POWER_OFF: 8238c2ecf20Sopenharmony_ci charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); 8248c2ecf20Sopenharmony_ci break; 8258c2ecf20Sopenharmony_ci default: 8268c2ecf20Sopenharmony_ci break; 8278c2ecf20Sopenharmony_ci } 8288c2ecf20Sopenharmony_ci return NOTIFY_DONE; 8298c2ecf20Sopenharmony_ci} 8308c2ecf20Sopenharmony_ci 8318c2ecf20Sopenharmony_cistatic struct notifier_block panel_notifier = { 8328c2ecf20Sopenharmony_ci panel_notify_sys, 8338c2ecf20Sopenharmony_ci NULL, 8348c2ecf20Sopenharmony_ci 0 8358c2ecf20Sopenharmony_ci}; 8368c2ecf20Sopenharmony_ci 8378c2ecf20Sopenharmony_ciint charlcd_register(struct charlcd *lcd) 8388c2ecf20Sopenharmony_ci{ 8398c2ecf20Sopenharmony_ci int ret; 8408c2ecf20Sopenharmony_ci 8418c2ecf20Sopenharmony_ci ret = charlcd_init(lcd); 8428c2ecf20Sopenharmony_ci if (ret) 8438c2ecf20Sopenharmony_ci return ret; 8448c2ecf20Sopenharmony_ci 8458c2ecf20Sopenharmony_ci ret = misc_register(&charlcd_dev); 8468c2ecf20Sopenharmony_ci if (ret) 8478c2ecf20Sopenharmony_ci return ret; 8488c2ecf20Sopenharmony_ci 8498c2ecf20Sopenharmony_ci the_charlcd = lcd; 8508c2ecf20Sopenharmony_ci register_reboot_notifier(&panel_notifier); 8518c2ecf20Sopenharmony_ci return 0; 8528c2ecf20Sopenharmony_ci} 8538c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(charlcd_register); 8548c2ecf20Sopenharmony_ci 8558c2ecf20Sopenharmony_ciint charlcd_unregister(struct charlcd *lcd) 8568c2ecf20Sopenharmony_ci{ 8578c2ecf20Sopenharmony_ci struct charlcd_priv *priv = charlcd_to_priv(lcd); 8588c2ecf20Sopenharmony_ci 8598c2ecf20Sopenharmony_ci unregister_reboot_notifier(&panel_notifier); 8608c2ecf20Sopenharmony_ci charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-"); 8618c2ecf20Sopenharmony_ci misc_deregister(&charlcd_dev); 8628c2ecf20Sopenharmony_ci the_charlcd = NULL; 8638c2ecf20Sopenharmony_ci if (lcd->ops->backlight) { 8648c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&priv->bl_work); 8658c2ecf20Sopenharmony_ci priv->lcd.ops->backlight(&priv->lcd, 0); 8668c2ecf20Sopenharmony_ci } 8678c2ecf20Sopenharmony_ci 8688c2ecf20Sopenharmony_ci return 0; 8698c2ecf20Sopenharmony_ci} 8708c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(charlcd_unregister); 8718c2ecf20Sopenharmony_ci 8728c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 873