blob: 1b37d4bc38f96735d894ac4dca258469df01f583 [file] [log] [blame]
Miguel Ojeda351f683b2018-02-17 20:33:13 +01001// SPDX-License-Identifier: GPL-2.0+
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +01002/*
3 * Character LCD driver for Linux
4 *
5 * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
6 * Copyright (C) 2016-2017 Glider bvba
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +01007 */
8
9#include <linux/atomic.h>
Miguel Ojedab34050f2018-02-27 23:09:52 +010010#include <linux/ctype.h>
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010011#include <linux/delay.h>
12#include <linux/fs.h>
13#include <linux/miscdevice.h>
14#include <linux/module.h>
15#include <linux/notifier.h>
16#include <linux/reboot.h>
17#include <linux/slab.h>
18#include <linux/uaccess.h>
19#include <linux/workqueue.h>
20
21#include <generated/utsrelease.h>
22
Masahiro Yamada75354282019-08-06 16:14:44 +090023#include "charlcd.h"
Lars Poeschel2545c1c2020-11-03 10:58:06 +010024#include "hd44780_common.h"
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010025
26/* Keep the backlight on this many seconds for each flash */
27#define LCD_BL_TEMPO_PERIOD 4
28
29#define LCD_FLAG_B 0x0004 /* Blink on */
30#define LCD_FLAG_C 0x0008 /* Cursor on */
31#define LCD_FLAG_D 0x0010 /* Display on */
32#define LCD_FLAG_F 0x0020 /* Large font mode */
33#define LCD_FLAG_N 0x0040 /* 2-rows mode */
34#define LCD_FLAG_L 0x0080 /* Backlight enabled */
35
36/* LCD commands */
37#define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */
38
39#define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */
40#define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */
41
42#define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */
43#define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */
44#define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */
45#define LCD_CMD_BLINK_ON 0x01 /* Set blink on */
46
47#define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */
48#define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */
49#define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */
50
51#define LCD_CMD_FUNCTION_SET 0x20 /* Set function */
52#define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */
53#define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */
54#define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */
55
56#define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */
57
58#define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */
59
60#define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */
61#define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */
62
63struct charlcd_priv {
64 struct charlcd lcd;
65
66 struct delayed_work bl_work;
67 struct mutex bl_tempo_lock; /* Protects access to bl_tempo */
68 bool bl_tempo;
69
70 bool must_clear;
71
72 /* contains the LCD config state */
73 unsigned long int flags;
74
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010075 /* Current escape sequence and it's length or -1 if outside */
76 struct {
77 char buf[LCD_ESCAPE_LEN + 1];
78 int len;
79 } esc_seq;
80
Gustavo A. R. Silva2f920c02020-02-12 13:52:31 -060081 unsigned long long drvdata[];
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010082};
83
Andy Shevchenkob658a212019-03-12 16:44:29 +020084#define charlcd_to_priv(p) container_of(p, struct charlcd_priv, lcd)
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010085
86/* Device single-open policy control */
87static atomic_t charlcd_available = ATOMIC_INIT(1);
88
89/* sleeps that many milliseconds with a reschedule */
90static void long_sleep(int ms)
91{
Jia-Ju Bai17161392018-01-26 23:19:15 +080092 schedule_timeout_interruptible(msecs_to_jiffies(ms));
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010093}
94
95/* turn the backlight on or off */
Lars Poeschel66ce7d52020-11-03 10:58:04 +010096static void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on)
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010097{
Andy Shevchenkob658a212019-03-12 16:44:29 +020098 struct charlcd_priv *priv = charlcd_to_priv(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010099
100 if (!lcd->ops->backlight)
101 return;
102
103 mutex_lock(&priv->bl_tempo_lock);
104 if (!priv->bl_tempo)
105 lcd->ops->backlight(lcd, on);
106 mutex_unlock(&priv->bl_tempo_lock);
107}
108
109static void charlcd_bl_off(struct work_struct *work)
110{
111 struct delayed_work *dwork = to_delayed_work(work);
112 struct charlcd_priv *priv =
113 container_of(dwork, struct charlcd_priv, bl_work);
114
115 mutex_lock(&priv->bl_tempo_lock);
116 if (priv->bl_tempo) {
117 priv->bl_tempo = false;
118 if (!(priv->flags & LCD_FLAG_L))
119 priv->lcd.ops->backlight(&priv->lcd, 0);
120 }
121 mutex_unlock(&priv->bl_tempo_lock);
122}
123
124/* turn the backlight on for a little while */
125void charlcd_poke(struct charlcd *lcd)
126{
Andy Shevchenkob658a212019-03-12 16:44:29 +0200127 struct charlcd_priv *priv = charlcd_to_priv(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100128
129 if (!lcd->ops->backlight)
130 return;
131
132 cancel_delayed_work_sync(&priv->bl_work);
133
134 mutex_lock(&priv->bl_tempo_lock);
135 if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L))
136 lcd->ops->backlight(lcd, 1);
137 priv->bl_tempo = true;
138 schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ);
139 mutex_unlock(&priv->bl_tempo_lock);
140}
141EXPORT_SYMBOL_GPL(charlcd_poke);
142
143static void charlcd_gotoxy(struct charlcd *lcd)
144{
Lars Poeschel2545c1c2020-11-03 10:58:06 +0100145 struct hd44780_common *hdc = lcd->drvdata;
Geert Uytterhoeven1d3b2af2017-03-10 15:15:19 +0100146 unsigned int addr;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100147
Geert Uytterhoeven1d3b2af2017-03-10 15:15:19 +0100148 /*
149 * we force the cursor to stay at the end of the
150 * line if it wants to go farther
151 */
Lars Poeschel11588b52020-11-03 10:58:10 +0100152 addr = lcd->addr.x < hdc->bwidth ? lcd->addr.x & (hdc->hwidth - 1)
Lars Poeschel2545c1c2020-11-03 10:58:06 +0100153 : hdc->bwidth - 1;
Lars Poeschel11588b52020-11-03 10:58:10 +0100154 if (lcd->addr.y & 1)
Lars Poeschel2545c1c2020-11-03 10:58:06 +0100155 addr += hdc->hwidth;
Lars Poeschel11588b52020-11-03 10:58:10 +0100156 if (lcd->addr.y & 2)
Lars Poeschel2545c1c2020-11-03 10:58:06 +0100157 addr += hdc->bwidth;
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100158 hdc->write_cmd(hdc, LCD_CMD_SET_DDRAM_ADDR | addr);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100159}
160
161static void charlcd_home(struct charlcd *lcd)
162{
Lars Poeschel11588b52020-11-03 10:58:10 +0100163 lcd->addr.x = 0;
164 lcd->addr.y = 0;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100165 charlcd_gotoxy(lcd);
166}
167
168static void charlcd_print(struct charlcd *lcd, char c)
169{
Lars Poeschel2545c1c2020-11-03 10:58:06 +0100170 struct hd44780_common *hdc = lcd->drvdata;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100171
Lars Poeschel11588b52020-11-03 10:58:10 +0100172 if (lcd->addr.x < hdc->bwidth) {
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100173 if (lcd->char_conv)
174 c = lcd->char_conv[(unsigned char)c];
Lars Poeschel71ff7012020-11-03 10:58:08 +0100175 hdc->write_data(hdc, c);
Lars Poeschel11588b52020-11-03 10:58:10 +0100176 lcd->addr.x++;
Sean Young54bc937f2018-01-15 09:43:48 +0000177
178 /* prevents the cursor from wrapping onto the next line */
Lars Poeschel11588b52020-11-03 10:58:10 +0100179 if (lcd->addr.x == hdc->bwidth)
Sean Young54bc937f2018-01-15 09:43:48 +0000180 charlcd_gotoxy(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100181 }
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100182}
183
184static void charlcd_clear_fast(struct charlcd *lcd)
185{
Lars Poeschel2545c1c2020-11-03 10:58:06 +0100186 struct hd44780_common *hdc = lcd->drvdata;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100187 int pos;
188
189 charlcd_home(lcd);
190
191 if (lcd->ops->clear_fast)
192 lcd->ops->clear_fast(lcd);
193 else
Lars Poeschel2545c1c2020-11-03 10:58:06 +0100194 for (pos = 0; pos < min(2, lcd->height) * hdc->hwidth; pos++)
Lars Poeschel71ff7012020-11-03 10:58:08 +0100195 hdc->write_data(hdc, ' ');
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100196
197 charlcd_home(lcd);
198}
199
200/* clears the display and resets X/Y */
201static void charlcd_clear_display(struct charlcd *lcd)
202{
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100203 struct hd44780_common *hdc = lcd->drvdata;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100204
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100205 hdc->write_cmd(hdc, LCD_CMD_DISPLAY_CLEAR);
Lars Poeschel11588b52020-11-03 10:58:10 +0100206 lcd->addr.x = 0;
207 lcd->addr.y = 0;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100208 /* we must wait a few milliseconds (15) */
209 long_sleep(15);
210}
211
212static int charlcd_init_display(struct charlcd *lcd)
213{
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100214 void (*write_cmd_raw)(struct hd44780_common *hdc, int cmd);
Andy Shevchenkob658a212019-03-12 16:44:29 +0200215 struct charlcd_priv *priv = charlcd_to_priv(lcd);
Lars Poeschel3fc04dd2020-11-03 10:58:07 +0100216 struct hd44780_common *hdc = lcd->drvdata;
Geert Uytterhoevenac201472017-03-10 15:15:18 +0100217 u8 init;
218
Lars Poeschel3fc04dd2020-11-03 10:58:07 +0100219 if (hdc->ifwidth != 4 && hdc->ifwidth != 8)
Geert Uytterhoevenac201472017-03-10 15:15:18 +0100220 return -EINVAL;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100221
222 priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
223 LCD_FLAG_C | LCD_FLAG_B;
224
225 long_sleep(20); /* wait 20 ms after power-up for the paranoid */
226
Geert Uytterhoevenac201472017-03-10 15:15:18 +0100227 /*
228 * 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure
229 * the LCD is in 8-bit mode afterwards
230 */
231 init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS;
Lars Poeschel3fc04dd2020-11-03 10:58:07 +0100232 if (hdc->ifwidth == 4) {
Geert Uytterhoevenac201472017-03-10 15:15:18 +0100233 init >>= 4;
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100234 write_cmd_raw = hdc->write_cmd_raw4;
Geert Uytterhoevenac201472017-03-10 15:15:18 +0100235 } else {
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100236 write_cmd_raw = hdc->write_cmd;
Geert Uytterhoevenac201472017-03-10 15:15:18 +0100237 }
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100238 write_cmd_raw(hdc, init);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100239 long_sleep(10);
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100240 write_cmd_raw(hdc, init);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100241 long_sleep(10);
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100242 write_cmd_raw(hdc, init);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100243 long_sleep(10);
244
Lars Poeschel3fc04dd2020-11-03 10:58:07 +0100245 if (hdc->ifwidth == 4) {
Geert Uytterhoevenac201472017-03-10 15:15:18 +0100246 /* Switch to 4-bit mode, 1 line, small fonts */
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100247 hdc->write_cmd_raw4(hdc, LCD_CMD_FUNCTION_SET >> 4);
Geert Uytterhoevenac201472017-03-10 15:15:18 +0100248 long_sleep(10);
249 }
250
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100251 /* set font height and lines number */
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100252 hdc->write_cmd(hdc,
Geert Uytterhoevenac201472017-03-10 15:15:18 +0100253 LCD_CMD_FUNCTION_SET |
Lars Poeschel3fc04dd2020-11-03 10:58:07 +0100254 ((hdc->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100255 ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) |
256 ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0));
257 long_sleep(10);
258
259 /* display off, cursor off, blink off */
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100260 hdc->write_cmd(hdc, LCD_CMD_DISPLAY_CTRL);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100261 long_sleep(10);
262
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100263 hdc->write_cmd(hdc,
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100264 LCD_CMD_DISPLAY_CTRL | /* set display mode */
265 ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) |
266 ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) |
267 ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0));
268
269 charlcd_backlight(lcd, (priv->flags & LCD_FLAG_L) ? 1 : 0);
270
271 long_sleep(10);
272
273 /* entry mode set : increment, cursor shifting */
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100274 hdc->write_cmd(hdc, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100275
276 charlcd_clear_display(lcd);
277 return 0;
278}
279
280/*
Miguel Ojedab34050f2018-02-27 23:09:52 +0100281 * Parses a movement command of the form "(.*);", where the group can be
282 * any number of subcommands of the form "(x|y)[0-9]+".
283 *
284 * Returns whether the command is valid. The position arguments are
285 * only written if the parsing was successful.
286 *
287 * For instance:
288 * - ";" returns (<original x>, <original y>).
289 * - "x1;" returns (1, <original y>).
290 * - "y2x1;" returns (1, 2).
291 * - "x12y34x56;" returns (56, 34).
292 * - "" fails.
293 * - "x" fails.
294 * - "x;" fails.
295 * - "x1" fails.
296 * - "xy12;" fails.
297 * - "x12yy12;" fails.
298 * - "xx" fails.
299 */
300static bool parse_xy(const char *s, unsigned long *x, unsigned long *y)
301{
302 unsigned long new_x = *x;
303 unsigned long new_y = *y;
Andy Shevchenkod717e7d2019-12-04 16:50:36 -0800304 char *p;
Miguel Ojedab34050f2018-02-27 23:09:52 +0100305
306 for (;;) {
307 if (!*s)
308 return false;
309
310 if (*s == ';')
311 break;
312
313 if (*s == 'x') {
Andy Shevchenkod717e7d2019-12-04 16:50:36 -0800314 new_x = simple_strtoul(s + 1, &p, 10);
315 if (p == s + 1)
Miguel Ojedab34050f2018-02-27 23:09:52 +0100316 return false;
Andy Shevchenkod717e7d2019-12-04 16:50:36 -0800317 s = p;
Miguel Ojedab34050f2018-02-27 23:09:52 +0100318 } else if (*s == 'y') {
Andy Shevchenkod717e7d2019-12-04 16:50:36 -0800319 new_y = simple_strtoul(s + 1, &p, 10);
320 if (p == s + 1)
Miguel Ojedab34050f2018-02-27 23:09:52 +0100321 return false;
Andy Shevchenkod717e7d2019-12-04 16:50:36 -0800322 s = p;
Miguel Ojedab34050f2018-02-27 23:09:52 +0100323 } else {
324 return false;
325 }
326 }
327
328 *x = new_x;
329 *y = new_y;
330 return true;
331}
332
333/*
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100334 * These are the file operation function for user access to /dev/lcd
335 * This function can also be called from inside the kernel, by
336 * setting file and ppos to NULL.
337 *
338 */
339
340static inline int handle_lcd_special_code(struct charlcd *lcd)
341{
Andy Shevchenkob658a212019-03-12 16:44:29 +0200342 struct charlcd_priv *priv = charlcd_to_priv(lcd);
Lars Poeschel2545c1c2020-11-03 10:58:06 +0100343 struct hd44780_common *hdc = lcd->drvdata;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100344
345 /* LCD special codes */
346
347 int processed = 0;
348
349 char *esc = priv->esc_seq.buf + 2;
350 int oldflags = priv->flags;
351
352 /* check for display mode flags */
353 switch (*esc) {
354 case 'D': /* Display ON */
355 priv->flags |= LCD_FLAG_D;
356 processed = 1;
357 break;
358 case 'd': /* Display OFF */
359 priv->flags &= ~LCD_FLAG_D;
360 processed = 1;
361 break;
362 case 'C': /* Cursor ON */
363 priv->flags |= LCD_FLAG_C;
364 processed = 1;
365 break;
366 case 'c': /* Cursor OFF */
367 priv->flags &= ~LCD_FLAG_C;
368 processed = 1;
369 break;
370 case 'B': /* Blink ON */
371 priv->flags |= LCD_FLAG_B;
372 processed = 1;
373 break;
374 case 'b': /* Blink OFF */
375 priv->flags &= ~LCD_FLAG_B;
376 processed = 1;
377 break;
378 case '+': /* Back light ON */
379 priv->flags |= LCD_FLAG_L;
380 processed = 1;
381 break;
382 case '-': /* Back light OFF */
383 priv->flags &= ~LCD_FLAG_L;
384 processed = 1;
385 break;
386 case '*': /* Flash back light */
387 charlcd_poke(lcd);
388 processed = 1;
389 break;
390 case 'f': /* Small Font */
391 priv->flags &= ~LCD_FLAG_F;
392 processed = 1;
393 break;
394 case 'F': /* Large Font */
395 priv->flags |= LCD_FLAG_F;
396 processed = 1;
397 break;
398 case 'n': /* One Line */
399 priv->flags &= ~LCD_FLAG_N;
400 processed = 1;
401 break;
402 case 'N': /* Two Lines */
403 priv->flags |= LCD_FLAG_N;
Robert Abel99b9b492018-02-26 00:54:29 +0100404 processed = 1;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100405 break;
406 case 'l': /* Shift Cursor Left */
Lars Poeschel11588b52020-11-03 10:58:10 +0100407 if (lcd->addr.x > 0) {
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100408 /* back one char if not at end of line */
Lars Poeschel11588b52020-11-03 10:58:10 +0100409 if (lcd->addr.x < hdc->bwidth)
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100410 hdc->write_cmd(hdc, LCD_CMD_SHIFT);
Lars Poeschel11588b52020-11-03 10:58:10 +0100411 lcd->addr.x--;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100412 }
413 processed = 1;
414 break;
415 case 'r': /* shift cursor right */
Lars Poeschel11588b52020-11-03 10:58:10 +0100416 if (lcd->addr.x < lcd->width) {
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100417 /* allow the cursor to pass the end of the line */
Lars Poeschel11588b52020-11-03 10:58:10 +0100418 if (lcd->addr.x < (hdc->bwidth - 1))
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100419 hdc->write_cmd(hdc,
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100420 LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT);
Lars Poeschel11588b52020-11-03 10:58:10 +0100421 lcd->addr.x++;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100422 }
423 processed = 1;
424 break;
425 case 'L': /* shift display left */
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100426 hdc->write_cmd(hdc, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100427 processed = 1;
428 break;
429 case 'R': /* shift display right */
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100430 hdc->write_cmd(hdc,
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100431 LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT |
432 LCD_CMD_SHIFT_RIGHT);
433 processed = 1;
434 break;
435 case 'k': { /* kill end of line */
436 int x;
437
Lars Poeschel11588b52020-11-03 10:58:10 +0100438 for (x = lcd->addr.x; x < hdc->bwidth; x++)
Lars Poeschel71ff7012020-11-03 10:58:08 +0100439 hdc->write_data(hdc, ' ');
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100440
441 /* restore cursor position */
442 charlcd_gotoxy(lcd);
443 processed = 1;
444 break;
445 }
446 case 'I': /* reinitialize display */
447 charlcd_init_display(lcd);
448 processed = 1;
449 break;
450 case 'G': {
451 /* Generator : LGcxxxxx...xx; must have <c> between '0'
452 * and '7', representing the numerical ASCII code of the
453 * redefined character, and <xx...xx> a sequence of 16
454 * hex digits representing 8 bytes for each character.
455 * Most LCDs will only use 5 lower bits of the 7 first
456 * bytes.
457 */
458
459 unsigned char cgbytes[8];
460 unsigned char cgaddr;
461 int cgoffset;
462 int shift;
463 char value;
464 int addr;
465
466 if (!strchr(esc, ';'))
467 break;
468
469 esc++;
470
471 cgaddr = *(esc++) - '0';
472 if (cgaddr > 7) {
473 processed = 1;
474 break;
475 }
476
477 cgoffset = 0;
478 shift = 0;
479 value = 0;
480 while (*esc && cgoffset < 8) {
Andy Shevchenko3f03b642020-05-18 22:36:17 +0300481 int half;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100482
Andy Shevchenko3f03b642020-05-18 22:36:17 +0300483 shift ^= 4;
484
485 half = hex_to_bin(*esc++);
486 if (half < 0)
487 continue;
488
489 value |= half << shift;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100490 if (shift == 0) {
491 cgbytes[cgoffset++] = value;
492 value = 0;
493 }
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100494 }
495
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100496 hdc->write_cmd(hdc, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8));
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100497 for (addr = 0; addr < cgoffset; addr++)
Lars Poeschel71ff7012020-11-03 10:58:08 +0100498 hdc->write_data(hdc, cgbytes[addr]);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100499
500 /* ensures that we stop writing to CGRAM */
501 charlcd_gotoxy(lcd);
502 processed = 1;
503 break;
504 }
505 case 'x': /* gotoxy : LxXXX[yYYY]; */
506 case 'y': /* gotoxy : LyYYY[xXXX]; */
Mans Rullgard9bc30ab2018-12-05 13:52:47 +0000507 if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';')
508 break;
509
Miguel Ojedab34050f2018-02-27 23:09:52 +0100510 /* If the command is valid, move to the new address */
Lars Poeschel11588b52020-11-03 10:58:10 +0100511 if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y))
Miguel Ojedab34050f2018-02-27 23:09:52 +0100512 charlcd_gotoxy(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100513
Miguel Ojedab34050f2018-02-27 23:09:52 +0100514 /* Regardless of its validity, mark as processed */
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100515 processed = 1;
516 break;
517 }
518
519 /* TODO: This indent party here got ugly, clean it! */
520 /* Check whether one flag was changed */
521 if (oldflags == priv->flags)
522 return processed;
523
524 /* check whether one of B,C,D flags were changed */
525 if ((oldflags ^ priv->flags) &
526 (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D))
527 /* set display mode */
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100528 hdc->write_cmd(hdc,
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100529 LCD_CMD_DISPLAY_CTRL |
530 ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) |
531 ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) |
532 ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0));
533 /* check whether one of F,N flags was changed */
534 else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N))
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100535 hdc->write_cmd(hdc,
Geert Uytterhoevenac201472017-03-10 15:15:18 +0100536 LCD_CMD_FUNCTION_SET |
Lars Poeschel3fc04dd2020-11-03 10:58:07 +0100537 ((hdc->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100538 ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) |
539 ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0));
540 /* check whether L flag was changed */
541 else if ((oldflags ^ priv->flags) & LCD_FLAG_L)
542 charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L));
543
544 return processed;
545}
546
547static void charlcd_write_char(struct charlcd *lcd, char c)
548{
Andy Shevchenkob658a212019-03-12 16:44:29 +0200549 struct charlcd_priv *priv = charlcd_to_priv(lcd);
Lars Poeschel2545c1c2020-11-03 10:58:06 +0100550 struct hd44780_common *hdc = lcd->drvdata;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100551
552 /* first, we'll test if we're in escape mode */
553 if ((c != '\n') && priv->esc_seq.len >= 0) {
554 /* yes, let's add this char to the buffer */
555 priv->esc_seq.buf[priv->esc_seq.len++] = c;
Robert Abel8c483752018-02-10 00:50:11 +0100556 priv->esc_seq.buf[priv->esc_seq.len] = '\0';
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100557 } else {
558 /* aborts any previous escape sequence */
559 priv->esc_seq.len = -1;
560
561 switch (c) {
562 case LCD_ESCAPE_CHAR:
563 /* start of an escape sequence */
564 priv->esc_seq.len = 0;
Robert Abel8c483752018-02-10 00:50:11 +0100565 priv->esc_seq.buf[priv->esc_seq.len] = '\0';
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100566 break;
567 case '\b':
568 /* go back one char and clear it */
Lars Poeschel11588b52020-11-03 10:58:10 +0100569 if (lcd->addr.x > 0) {
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100570 /*
571 * check if we're not at the
572 * end of the line
573 */
Lars Poeschel11588b52020-11-03 10:58:10 +0100574 if (lcd->addr.x < hdc->bwidth)
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100575 /* back one char */
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100576 hdc->write_cmd(hdc, LCD_CMD_SHIFT);
Lars Poeschel11588b52020-11-03 10:58:10 +0100577 lcd->addr.x--;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100578 }
579 /* replace with a space */
Lars Poeschel71ff7012020-11-03 10:58:08 +0100580 hdc->write_data(hdc, ' ');
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100581 /* back one char again */
Lars Poeschel2c6a82f2020-11-03 10:58:09 +0100582 hdc->write_cmd(hdc, LCD_CMD_SHIFT);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100583 break;
Robert Abel9629ccc2018-02-10 00:50:12 +0100584 case '\f':
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100585 /* quickly clear the display */
586 charlcd_clear_fast(lcd);
587 break;
588 case '\n':
589 /*
590 * flush the remainder of the current line and
591 * go to the beginning of the next line
592 */
Lars Poeschel11588b52020-11-03 10:58:10 +0100593 for (; lcd->addr.x < hdc->bwidth; lcd->addr.x++)
Lars Poeschel71ff7012020-11-03 10:58:08 +0100594 hdc->write_data(hdc, ' ');
Lars Poeschel11588b52020-11-03 10:58:10 +0100595 lcd->addr.x = 0;
596 lcd->addr.y = (lcd->addr.y + 1) % lcd->height;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100597 charlcd_gotoxy(lcd);
598 break;
599 case '\r':
600 /* go to the beginning of the same line */
Lars Poeschel11588b52020-11-03 10:58:10 +0100601 lcd->addr.x = 0;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100602 charlcd_gotoxy(lcd);
603 break;
604 case '\t':
605 /* print a space instead of the tab */
606 charlcd_print(lcd, ' ');
607 break;
608 default:
609 /* simply print this char */
610 charlcd_print(lcd, c);
611 break;
612 }
613 }
614
615 /*
616 * now we'll see if we're in an escape mode and if the current
617 * escape sequence can be understood.
618 */
619 if (priv->esc_seq.len >= 2) {
620 int processed = 0;
621
622 if (!strcmp(priv->esc_seq.buf, "[2J")) {
623 /* clear the display */
624 charlcd_clear_fast(lcd);
625 processed = 1;
626 } else if (!strcmp(priv->esc_seq.buf, "[H")) {
627 /* cursor to home */
628 charlcd_home(lcd);
629 processed = 1;
630 }
631 /* codes starting with ^[[L */
632 else if ((priv->esc_seq.len >= 3) &&
633 (priv->esc_seq.buf[0] == '[') &&
634 (priv->esc_seq.buf[1] == 'L')) {
635 processed = handle_lcd_special_code(lcd);
636 }
637
638 /* LCD special escape codes */
639 /*
640 * flush the escape sequence if it's been processed
641 * or if it is getting too long.
642 */
643 if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN))
644 priv->esc_seq.len = -1;
645 } /* escape codes */
646}
647
648static struct charlcd *the_charlcd;
649
650static ssize_t charlcd_write(struct file *file, const char __user *buf,
651 size_t count, loff_t *ppos)
652{
653 const char __user *tmp = buf;
654 char c;
655
656 for (; count-- > 0; (*ppos)++, tmp++) {
657 if (!in_interrupt() && (((count + 1) & 0x1f) == 0))
658 /*
659 * let's be a little nice with other processes
660 * that need some CPU
661 */
662 schedule();
663
664 if (get_user(c, tmp))
665 return -EFAULT;
666
667 charlcd_write_char(the_charlcd, c);
668 }
669
670 return tmp - buf;
671}
672
673static int charlcd_open(struct inode *inode, struct file *file)
674{
Andy Shevchenkob658a212019-03-12 16:44:29 +0200675 struct charlcd_priv *priv = charlcd_to_priv(the_charlcd);
Willy Tarreau93dc1772017-09-07 15:37:30 +0200676 int ret;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100677
Willy Tarreau93dc1772017-09-07 15:37:30 +0200678 ret = -EBUSY;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100679 if (!atomic_dec_and_test(&charlcd_available))
Willy Tarreau93dc1772017-09-07 15:37:30 +0200680 goto fail; /* open only once at a time */
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100681
Willy Tarreau93dc1772017-09-07 15:37:30 +0200682 ret = -EPERM;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100683 if (file->f_mode & FMODE_READ) /* device is write-only */
Willy Tarreau93dc1772017-09-07 15:37:30 +0200684 goto fail;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100685
686 if (priv->must_clear) {
687 charlcd_clear_display(&priv->lcd);
688 priv->must_clear = false;
689 }
690 return nonseekable_open(inode, file);
Willy Tarreau93dc1772017-09-07 15:37:30 +0200691
692 fail:
693 atomic_inc(&charlcd_available);
694 return ret;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100695}
696
697static int charlcd_release(struct inode *inode, struct file *file)
698{
699 atomic_inc(&charlcd_available);
700 return 0;
701}
702
703static const struct file_operations charlcd_fops = {
704 .write = charlcd_write,
705 .open = charlcd_open,
706 .release = charlcd_release,
707 .llseek = no_llseek,
708};
709
710static struct miscdevice charlcd_dev = {
711 .minor = LCD_MINOR,
712 .name = "lcd",
713 .fops = &charlcd_fops,
714};
715
716static void charlcd_puts(struct charlcd *lcd, const char *s)
717{
718 const char *tmp = s;
719 int count = strlen(s);
720
721 for (; count-- > 0; tmp++) {
722 if (!in_interrupt() && (((count + 1) & 0x1f) == 0))
723 /*
724 * let's be a little nice with other processes
725 * that need some CPU
726 */
727 schedule();
728
729 charlcd_write_char(lcd, *tmp);
730 }
731}
732
Mans Rullgardc9171722019-03-01 18:48:15 +0000733#ifdef CONFIG_PANEL_BOOT_MESSAGE
734#define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
735#else
736#define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n"
737#endif
738
Mans Rullgardcc5d04d2019-03-01 18:48:16 +0000739#ifdef CONFIG_CHARLCD_BL_ON
740#define LCD_INIT_BL "\x1b[L+"
741#elif defined(CONFIG_CHARLCD_BL_FLASH)
742#define LCD_INIT_BL "\x1b[L*"
743#else
744#define LCD_INIT_BL "\x1b[L-"
745#endif
746
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100747/* initialize the LCD driver */
748static int charlcd_init(struct charlcd *lcd)
749{
Andy Shevchenkob658a212019-03-12 16:44:29 +0200750 struct charlcd_priv *priv = charlcd_to_priv(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100751 int ret;
752
753 if (lcd->ops->backlight) {
754 mutex_init(&priv->bl_tempo_lock);
755 INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off);
756 }
757
758 /*
759 * before this line, we must NOT send anything to the display.
760 * Since charlcd_init_display() needs to write data, we have to
761 * enable mark the LCD initialized just before.
762 */
763 ret = charlcd_init_display(lcd);
764 if (ret)
765 return ret;
766
767 /* display a short message */
Mans Rullgardcc5d04d2019-03-01 18:48:16 +0000768 charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT);
Mans Rullgardc9171722019-03-01 18:48:15 +0000769
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100770 /* clear the display on the next device opening */
771 priv->must_clear = true;
772 charlcd_home(lcd);
773 return 0;
774}
775
Lars Poeschel2545c1c2020-11-03 10:58:06 +0100776struct charlcd *charlcd_alloc(void)
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100777{
778 struct charlcd_priv *priv;
779 struct charlcd *lcd;
780
Lars Poeschel2545c1c2020-11-03 10:58:06 +0100781 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100782 if (!priv)
783 return NULL;
784
785 priv->esc_seq.len = -1;
786
787 lcd = &priv->lcd;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100788
789 return lcd;
790}
791EXPORT_SYMBOL_GPL(charlcd_alloc);
792
Andy Shevchenko8e44fc82019-03-12 16:44:30 +0200793void charlcd_free(struct charlcd *lcd)
794{
795 kfree(charlcd_to_priv(lcd));
796}
797EXPORT_SYMBOL_GPL(charlcd_free);
798
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100799static int panel_notify_sys(struct notifier_block *this, unsigned long code,
800 void *unused)
801{
802 struct charlcd *lcd = the_charlcd;
803
804 switch (code) {
805 case SYS_DOWN:
806 charlcd_puts(lcd,
807 "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+");
808 break;
809 case SYS_HALT:
810 charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+");
811 break;
812 case SYS_POWER_OFF:
813 charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");
814 break;
815 default:
816 break;
817 }
818 return NOTIFY_DONE;
819}
820
821static struct notifier_block panel_notifier = {
822 panel_notify_sys,
823 NULL,
824 0
825};
826
827int charlcd_register(struct charlcd *lcd)
828{
829 int ret;
830
831 ret = charlcd_init(lcd);
832 if (ret)
833 return ret;
834
835 ret = misc_register(&charlcd_dev);
836 if (ret)
837 return ret;
838
839 the_charlcd = lcd;
840 register_reboot_notifier(&panel_notifier);
841 return 0;
842}
843EXPORT_SYMBOL_GPL(charlcd_register);
844
845int charlcd_unregister(struct charlcd *lcd)
846{
Andy Shevchenkob658a212019-03-12 16:44:29 +0200847 struct charlcd_priv *priv = charlcd_to_priv(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100848
849 unregister_reboot_notifier(&panel_notifier);
850 charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-");
851 misc_deregister(&charlcd_dev);
852 the_charlcd = NULL;
853 if (lcd->ops->backlight) {
854 cancel_delayed_work_sync(&priv->bl_work);
855 priv->lcd.ops->backlight(&priv->lcd, 0);
856 }
857
858 return 0;
859}
860EXPORT_SYMBOL_GPL(charlcd_unregister);
861
862MODULE_LICENSE("GPL");