blob: ef10b5ca0e16607012967e9e913bb53e69fcdb2a [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/fs.h>
12#include <linux/miscdevice.h>
13#include <linux/module.h>
14#include <linux/notifier.h>
15#include <linux/reboot.h>
16#include <linux/slab.h>
17#include <linux/uaccess.h>
18#include <linux/workqueue.h>
19
20#include <generated/utsrelease.h>
21
Masahiro Yamada75354282019-08-06 16:14:44 +090022#include "charlcd.h"
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010023
24/* Keep the backlight on this many seconds for each flash */
25#define LCD_BL_TEMPO_PERIOD 4
26
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010027#define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */
28#define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */
29
30struct charlcd_priv {
31 struct charlcd lcd;
32
33 struct delayed_work bl_work;
34 struct mutex bl_tempo_lock; /* Protects access to bl_tempo */
35 bool bl_tempo;
36
37 bool must_clear;
38
39 /* contains the LCD config state */
40 unsigned long int flags;
41
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010042 /* Current escape sequence and it's length or -1 if outside */
43 struct {
44 char buf[LCD_ESCAPE_LEN + 1];
45 int len;
46 } esc_seq;
47
Gustavo A. R. Silva2f920c02020-02-12 13:52:31 -060048 unsigned long long drvdata[];
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010049};
50
Andy Shevchenkob658a212019-03-12 16:44:29 +020051#define charlcd_to_priv(p) container_of(p, struct charlcd_priv, lcd)
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010052
53/* Device single-open policy control */
54static atomic_t charlcd_available = ATOMIC_INIT(1);
55
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010056/* turn the backlight on or off */
Lars Poeschel2bf82b52020-11-03 10:58:15 +010057void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on)
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010058{
Andy Shevchenkob658a212019-03-12 16:44:29 +020059 struct charlcd_priv *priv = charlcd_to_priv(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010060
61 if (!lcd->ops->backlight)
62 return;
63
64 mutex_lock(&priv->bl_tempo_lock);
65 if (!priv->bl_tempo)
66 lcd->ops->backlight(lcd, on);
67 mutex_unlock(&priv->bl_tempo_lock);
68}
Lars Poeschel2bf82b52020-11-03 10:58:15 +010069EXPORT_SYMBOL_GPL(charlcd_backlight);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010070
71static void charlcd_bl_off(struct work_struct *work)
72{
73 struct delayed_work *dwork = to_delayed_work(work);
74 struct charlcd_priv *priv =
75 container_of(dwork, struct charlcd_priv, bl_work);
76
77 mutex_lock(&priv->bl_tempo_lock);
78 if (priv->bl_tempo) {
79 priv->bl_tempo = false;
80 if (!(priv->flags & LCD_FLAG_L))
Lars Poeschelbd26b182020-11-03 10:58:16 +010081 priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010082 }
83 mutex_unlock(&priv->bl_tempo_lock);
84}
85
86/* turn the backlight on for a little while */
87void charlcd_poke(struct charlcd *lcd)
88{
Andy Shevchenkob658a212019-03-12 16:44:29 +020089 struct charlcd_priv *priv = charlcd_to_priv(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010090
91 if (!lcd->ops->backlight)
92 return;
93
94 cancel_delayed_work_sync(&priv->bl_work);
95
96 mutex_lock(&priv->bl_tempo_lock);
97 if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L))
Lars Poeschelbd26b182020-11-03 10:58:16 +010098 lcd->ops->backlight(lcd, CHARLCD_ON);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +010099 priv->bl_tempo = true;
100 schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ);
101 mutex_unlock(&priv->bl_tempo_lock);
102}
103EXPORT_SYMBOL_GPL(charlcd_poke);
104
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100105static void charlcd_home(struct charlcd *lcd)
106{
Lars Poeschel11588b52020-11-03 10:58:10 +0100107 lcd->addr.x = 0;
108 lcd->addr.y = 0;
Lars Poeschel88645a82020-11-03 10:58:13 +0100109 lcd->ops->home(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100110}
111
112static void charlcd_print(struct charlcd *lcd, char c)
113{
Lars Poeschelb26deab2020-11-03 10:58:11 +0100114 if (lcd->char_conv)
115 c = lcd->char_conv[(unsigned char)c];
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100116
Lars Poeschelb26deab2020-11-03 10:58:11 +0100117 if (!lcd->ops->print(lcd, c))
Lars Poeschel11588b52020-11-03 10:58:10 +0100118 lcd->addr.x++;
Sean Young54bc937f2018-01-15 09:43:48 +0000119
Lars Poeschelb26deab2020-11-03 10:58:11 +0100120 /* prevents the cursor from wrapping onto the next line */
Lars Poeschel6e49eea2020-11-03 10:58:24 +0100121 if (lcd->addr.x == lcd->width)
Lars Poeschel40c2b722020-11-03 10:58:25 +0100122 lcd->ops->gotoxy(lcd, lcd->addr.x - 1, lcd->addr.y);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100123}
124
Lars Poeschel377cf2c2020-11-03 10:58:23 +0100125static void charlcd_clear_display(struct charlcd *lcd)
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100126{
Lars Poeschel377cf2c2020-11-03 10:58:23 +0100127 lcd->ops->clear_display(lcd);
128 lcd->addr.x = 0;
129 lcd->addr.y = 0;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100130}
131
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100132/*
Miguel Ojedab34050f2018-02-27 23:09:52 +0100133 * Parses a movement command of the form "(.*);", where the group can be
134 * any number of subcommands of the form "(x|y)[0-9]+".
135 *
136 * Returns whether the command is valid. The position arguments are
137 * only written if the parsing was successful.
138 *
139 * For instance:
140 * - ";" returns (<original x>, <original y>).
141 * - "x1;" returns (1, <original y>).
142 * - "y2x1;" returns (1, 2).
143 * - "x12y34x56;" returns (56, 34).
144 * - "" fails.
145 * - "x" fails.
146 * - "x;" fails.
147 * - "x1" fails.
148 * - "xy12;" fails.
149 * - "x12yy12;" fails.
150 * - "xx" fails.
151 */
152static bool parse_xy(const char *s, unsigned long *x, unsigned long *y)
153{
154 unsigned long new_x = *x;
155 unsigned long new_y = *y;
Andy Shevchenkod717e7d2019-12-04 16:50:36 -0800156 char *p;
Miguel Ojedab34050f2018-02-27 23:09:52 +0100157
158 for (;;) {
159 if (!*s)
160 return false;
161
162 if (*s == ';')
163 break;
164
165 if (*s == 'x') {
Andy Shevchenkod717e7d2019-12-04 16:50:36 -0800166 new_x = simple_strtoul(s + 1, &p, 10);
167 if (p == s + 1)
Miguel Ojedab34050f2018-02-27 23:09:52 +0100168 return false;
Andy Shevchenkod717e7d2019-12-04 16:50:36 -0800169 s = p;
Miguel Ojedab34050f2018-02-27 23:09:52 +0100170 } else if (*s == 'y') {
Andy Shevchenkod717e7d2019-12-04 16:50:36 -0800171 new_y = simple_strtoul(s + 1, &p, 10);
172 if (p == s + 1)
Miguel Ojedab34050f2018-02-27 23:09:52 +0100173 return false;
Andy Shevchenkod717e7d2019-12-04 16:50:36 -0800174 s = p;
Miguel Ojedab34050f2018-02-27 23:09:52 +0100175 } else {
176 return false;
177 }
178 }
179
180 *x = new_x;
181 *y = new_y;
182 return true;
183}
184
185/*
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100186 * These are the file operation function for user access to /dev/lcd
187 * This function can also be called from inside the kernel, by
188 * setting file and ppos to NULL.
189 *
190 */
191
192static inline int handle_lcd_special_code(struct charlcd *lcd)
193{
Andy Shevchenkob658a212019-03-12 16:44:29 +0200194 struct charlcd_priv *priv = charlcd_to_priv(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100195
196 /* LCD special codes */
197
198 int processed = 0;
199
200 char *esc = priv->esc_seq.buf + 2;
201 int oldflags = priv->flags;
202
203 /* check for display mode flags */
204 switch (*esc) {
205 case 'D': /* Display ON */
206 priv->flags |= LCD_FLAG_D;
Lars Poescheld2f21872020-11-03 10:58:18 +0100207 if (priv->flags != oldflags)
208 lcd->ops->display(lcd, CHARLCD_ON);
209
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100210 processed = 1;
211 break;
212 case 'd': /* Display OFF */
213 priv->flags &= ~LCD_FLAG_D;
Lars Poescheld2f21872020-11-03 10:58:18 +0100214 if (priv->flags != oldflags)
215 lcd->ops->display(lcd, CHARLCD_OFF);
216
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100217 processed = 1;
218 break;
219 case 'C': /* Cursor ON */
220 priv->flags |= LCD_FLAG_C;
Lars Poescheld2f21872020-11-03 10:58:18 +0100221 if (priv->flags != oldflags)
222 lcd->ops->cursor(lcd, CHARLCD_ON);
223
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100224 processed = 1;
225 break;
226 case 'c': /* Cursor OFF */
227 priv->flags &= ~LCD_FLAG_C;
Lars Poescheld2f21872020-11-03 10:58:18 +0100228 if (priv->flags != oldflags)
229 lcd->ops->cursor(lcd, CHARLCD_OFF);
230
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100231 processed = 1;
232 break;
233 case 'B': /* Blink ON */
234 priv->flags |= LCD_FLAG_B;
Lars Poescheld2f21872020-11-03 10:58:18 +0100235 if (priv->flags != oldflags)
236 lcd->ops->blink(lcd, CHARLCD_ON);
237
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100238 processed = 1;
239 break;
240 case 'b': /* Blink OFF */
241 priv->flags &= ~LCD_FLAG_B;
Lars Poescheld2f21872020-11-03 10:58:18 +0100242 if (priv->flags != oldflags)
243 lcd->ops->blink(lcd, CHARLCD_OFF);
244
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100245 processed = 1;
246 break;
247 case '+': /* Back light ON */
248 priv->flags |= LCD_FLAG_L;
Lars Poeschela2060f22020-11-03 10:58:21 +0100249 if (priv->flags != oldflags)
250 charlcd_backlight(lcd, CHARLCD_ON);
251
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100252 processed = 1;
253 break;
254 case '-': /* Back light OFF */
255 priv->flags &= ~LCD_FLAG_L;
Lars Poeschela2060f22020-11-03 10:58:21 +0100256 if (priv->flags != oldflags)
257 charlcd_backlight(lcd, CHARLCD_OFF);
258
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100259 processed = 1;
260 break;
261 case '*': /* Flash back light */
262 charlcd_poke(lcd);
263 processed = 1;
264 break;
265 case 'f': /* Small Font */
266 priv->flags &= ~LCD_FLAG_F;
Lars Poescheld2f21872020-11-03 10:58:18 +0100267 if (priv->flags != oldflags)
268 lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_SMALL);
269
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100270 processed = 1;
271 break;
272 case 'F': /* Large Font */
273 priv->flags |= LCD_FLAG_F;
Lars Poescheld2f21872020-11-03 10:58:18 +0100274 if (priv->flags != oldflags)
275 lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_LARGE);
276
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100277 processed = 1;
278 break;
279 case 'n': /* One Line */
280 priv->flags &= ~LCD_FLAG_N;
Lars Poescheld2f21872020-11-03 10:58:18 +0100281 if (priv->flags != oldflags)
282 lcd->ops->lines(lcd, CHARLCD_LINES_1);
283
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100284 processed = 1;
285 break;
286 case 'N': /* Two Lines */
287 priv->flags |= LCD_FLAG_N;
Lars Poescheld2f21872020-11-03 10:58:18 +0100288 if (priv->flags != oldflags)
289 lcd->ops->lines(lcd, CHARLCD_LINES_2);
290
Robert Abel99b9b492018-02-26 00:54:29 +0100291 processed = 1;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100292 break;
293 case 'l': /* Shift Cursor Left */
Lars Poeschel11588b52020-11-03 10:58:10 +0100294 if (lcd->addr.x > 0) {
Lars Poescheld2f21872020-11-03 10:58:18 +0100295 if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
296 lcd->addr.x--;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100297 }
Lars Poescheld2f21872020-11-03 10:58:18 +0100298
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100299 processed = 1;
300 break;
301 case 'r': /* shift cursor right */
Lars Poeschel11588b52020-11-03 10:58:10 +0100302 if (lcd->addr.x < lcd->width) {
Lars Poescheld2f21872020-11-03 10:58:18 +0100303 if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_RIGHT))
304 lcd->addr.x++;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100305 }
Lars Poescheld2f21872020-11-03 10:58:18 +0100306
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100307 processed = 1;
308 break;
309 case 'L': /* shift display left */
Lars Poescheld2f21872020-11-03 10:58:18 +0100310 lcd->ops->shift_display(lcd, CHARLCD_SHIFT_LEFT);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100311 processed = 1;
312 break;
313 case 'R': /* shift display right */
Lars Poescheld2f21872020-11-03 10:58:18 +0100314 lcd->ops->shift_display(lcd, CHARLCD_SHIFT_RIGHT);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100315 processed = 1;
316 break;
317 case 'k': { /* kill end of line */
Lars Poeschelb26deab2020-11-03 10:58:11 +0100318 int x, xs, ys;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100319
Lars Poeschelb26deab2020-11-03 10:58:11 +0100320 xs = lcd->addr.x;
321 ys = lcd->addr.y;
Lars Poeschel6e49eea2020-11-03 10:58:24 +0100322 for (x = lcd->addr.x; x < lcd->width; x++)
Lars Poeschelb26deab2020-11-03 10:58:11 +0100323 lcd->ops->print(lcd, ' ');
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100324
325 /* restore cursor position */
Lars Poeschelb26deab2020-11-03 10:58:11 +0100326 lcd->addr.x = xs;
327 lcd->addr.y = ys;
Lars Poeschel40c2b722020-11-03 10:58:25 +0100328 lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100329 processed = 1;
330 break;
331 }
332 case 'I': /* reinitialize display */
Lars Poeschel01ec46d2020-11-03 10:58:17 +0100333 lcd->ops->init_display(lcd);
334 priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
335 LCD_FLAG_C | LCD_FLAG_B;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100336 processed = 1;
337 break;
Lars Poeschel339acb02020-11-03 10:58:20 +0100338 case 'G':
339 if (lcd->ops->redefine_char)
340 processed = lcd->ops->redefine_char(lcd, esc);
341 else
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100342 processed = 1;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100343 break;
Lars Poeschel339acb02020-11-03 10:58:20 +0100344
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100345 case 'x': /* gotoxy : LxXXX[yYYY]; */
346 case 'y': /* gotoxy : LyYYY[xXXX]; */
Mans Rullgard9bc30ab2018-12-05 13:52:47 +0000347 if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';')
348 break;
349
Miguel Ojedab34050f2018-02-27 23:09:52 +0100350 /* If the command is valid, move to the new address */
Lars Poeschel11588b52020-11-03 10:58:10 +0100351 if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y))
Lars Poeschel40c2b722020-11-03 10:58:25 +0100352 lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100353
Miguel Ojedab34050f2018-02-27 23:09:52 +0100354 /* Regardless of its validity, mark as processed */
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100355 processed = 1;
356 break;
357 }
358
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100359 return processed;
360}
361
362static void charlcd_write_char(struct charlcd *lcd, char c)
363{
Andy Shevchenkob658a212019-03-12 16:44:29 +0200364 struct charlcd_priv *priv = charlcd_to_priv(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100365
366 /* first, we'll test if we're in escape mode */
367 if ((c != '\n') && priv->esc_seq.len >= 0) {
368 /* yes, let's add this char to the buffer */
369 priv->esc_seq.buf[priv->esc_seq.len++] = c;
Robert Abel8c483752018-02-10 00:50:11 +0100370 priv->esc_seq.buf[priv->esc_seq.len] = '\0';
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100371 } else {
372 /* aborts any previous escape sequence */
373 priv->esc_seq.len = -1;
374
375 switch (c) {
376 case LCD_ESCAPE_CHAR:
377 /* start of an escape sequence */
378 priv->esc_seq.len = 0;
Robert Abel8c483752018-02-10 00:50:11 +0100379 priv->esc_seq.buf[priv->esc_seq.len] = '\0';
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100380 break;
381 case '\b':
382 /* go back one char and clear it */
Lars Poeschel11588b52020-11-03 10:58:10 +0100383 if (lcd->addr.x > 0) {
Lars Poescheld2f21872020-11-03 10:58:18 +0100384 /* back one char */
385 if (!lcd->ops->shift_cursor(lcd,
386 CHARLCD_SHIFT_LEFT))
387 lcd->addr.x--;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100388 }
389 /* replace with a space */
Lars Poescheld2f21872020-11-03 10:58:18 +0100390 charlcd_print(lcd, ' ');
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100391 /* back one char again */
Lars Poescheld2f21872020-11-03 10:58:18 +0100392 if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
393 lcd->addr.x--;
394
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100395 break;
Robert Abel9629ccc2018-02-10 00:50:12 +0100396 case '\f':
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100397 /* quickly clear the display */
Lars Poeschel377cf2c2020-11-03 10:58:23 +0100398 charlcd_clear_display(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100399 break;
400 case '\n':
401 /*
402 * flush the remainder of the current line and
403 * go to the beginning of the next line
404 */
Lars Poeschel6e49eea2020-11-03 10:58:24 +0100405 for (; lcd->addr.x < lcd->width; lcd->addr.x++)
Lars Poeschelb26deab2020-11-03 10:58:11 +0100406 lcd->ops->print(lcd, ' ');
407
Lars Poeschel11588b52020-11-03 10:58:10 +0100408 lcd->addr.x = 0;
409 lcd->addr.y = (lcd->addr.y + 1) % lcd->height;
Lars Poeschel40c2b722020-11-03 10:58:25 +0100410 lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100411 break;
412 case '\r':
413 /* go to the beginning of the same line */
Lars Poeschel11588b52020-11-03 10:58:10 +0100414 lcd->addr.x = 0;
Lars Poeschel40c2b722020-11-03 10:58:25 +0100415 lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100416 break;
417 case '\t':
418 /* print a space instead of the tab */
419 charlcd_print(lcd, ' ');
420 break;
421 default:
422 /* simply print this char */
423 charlcd_print(lcd, c);
424 break;
425 }
426 }
427
428 /*
429 * now we'll see if we're in an escape mode and if the current
430 * escape sequence can be understood.
431 */
432 if (priv->esc_seq.len >= 2) {
433 int processed = 0;
434
435 if (!strcmp(priv->esc_seq.buf, "[2J")) {
436 /* clear the display */
Lars Poeschel377cf2c2020-11-03 10:58:23 +0100437 charlcd_clear_display(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100438 processed = 1;
439 } else if (!strcmp(priv->esc_seq.buf, "[H")) {
440 /* cursor to home */
441 charlcd_home(lcd);
442 processed = 1;
443 }
444 /* codes starting with ^[[L */
445 else if ((priv->esc_seq.len >= 3) &&
446 (priv->esc_seq.buf[0] == '[') &&
447 (priv->esc_seq.buf[1] == 'L')) {
448 processed = handle_lcd_special_code(lcd);
449 }
450
451 /* LCD special escape codes */
452 /*
453 * flush the escape sequence if it's been processed
454 * or if it is getting too long.
455 */
456 if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN))
457 priv->esc_seq.len = -1;
458 } /* escape codes */
459}
460
461static struct charlcd *the_charlcd;
462
463static ssize_t charlcd_write(struct file *file, const char __user *buf,
464 size_t count, loff_t *ppos)
465{
466 const char __user *tmp = buf;
467 char c;
468
469 for (; count-- > 0; (*ppos)++, tmp++) {
470 if (!in_interrupt() && (((count + 1) & 0x1f) == 0))
471 /*
472 * let's be a little nice with other processes
473 * that need some CPU
474 */
475 schedule();
476
477 if (get_user(c, tmp))
478 return -EFAULT;
479
480 charlcd_write_char(the_charlcd, c);
481 }
482
483 return tmp - buf;
484}
485
486static int charlcd_open(struct inode *inode, struct file *file)
487{
Andy Shevchenkob658a212019-03-12 16:44:29 +0200488 struct charlcd_priv *priv = charlcd_to_priv(the_charlcd);
Willy Tarreau93dc1772017-09-07 15:37:30 +0200489 int ret;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100490
Willy Tarreau93dc1772017-09-07 15:37:30 +0200491 ret = -EBUSY;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100492 if (!atomic_dec_and_test(&charlcd_available))
Willy Tarreau93dc1772017-09-07 15:37:30 +0200493 goto fail; /* open only once at a time */
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100494
Willy Tarreau93dc1772017-09-07 15:37:30 +0200495 ret = -EPERM;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100496 if (file->f_mode & FMODE_READ) /* device is write-only */
Willy Tarreau93dc1772017-09-07 15:37:30 +0200497 goto fail;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100498
499 if (priv->must_clear) {
Lars Poeschel45421ff2020-11-03 10:58:14 +0100500 priv->lcd.ops->clear_display(&priv->lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100501 priv->must_clear = false;
Lars Poeschel45421ff2020-11-03 10:58:14 +0100502 priv->lcd.addr.x = 0;
503 priv->lcd.addr.y = 0;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100504 }
505 return nonseekable_open(inode, file);
Willy Tarreau93dc1772017-09-07 15:37:30 +0200506
507 fail:
508 atomic_inc(&charlcd_available);
509 return ret;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100510}
511
512static int charlcd_release(struct inode *inode, struct file *file)
513{
514 atomic_inc(&charlcd_available);
515 return 0;
516}
517
518static const struct file_operations charlcd_fops = {
519 .write = charlcd_write,
520 .open = charlcd_open,
521 .release = charlcd_release,
522 .llseek = no_llseek,
523};
524
525static struct miscdevice charlcd_dev = {
526 .minor = LCD_MINOR,
527 .name = "lcd",
528 .fops = &charlcd_fops,
529};
530
531static void charlcd_puts(struct charlcd *lcd, const char *s)
532{
533 const char *tmp = s;
534 int count = strlen(s);
535
536 for (; count-- > 0; tmp++) {
537 if (!in_interrupt() && (((count + 1) & 0x1f) == 0))
538 /*
539 * let's be a little nice with other processes
540 * that need some CPU
541 */
542 schedule();
543
544 charlcd_write_char(lcd, *tmp);
545 }
546}
547
Mans Rullgardc9171722019-03-01 18:48:15 +0000548#ifdef CONFIG_PANEL_BOOT_MESSAGE
549#define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
550#else
551#define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n"
552#endif
553
Mans Rullgardcc5d04d2019-03-01 18:48:16 +0000554#ifdef CONFIG_CHARLCD_BL_ON
555#define LCD_INIT_BL "\x1b[L+"
556#elif defined(CONFIG_CHARLCD_BL_FLASH)
557#define LCD_INIT_BL "\x1b[L*"
558#else
559#define LCD_INIT_BL "\x1b[L-"
560#endif
561
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100562/* initialize the LCD driver */
563static int charlcd_init(struct charlcd *lcd)
564{
Andy Shevchenkob658a212019-03-12 16:44:29 +0200565 struct charlcd_priv *priv = charlcd_to_priv(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100566 int ret;
567
Lars Poeschel01ec46d2020-11-03 10:58:17 +0100568 priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
569 LCD_FLAG_C | LCD_FLAG_B;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100570 if (lcd->ops->backlight) {
571 mutex_init(&priv->bl_tempo_lock);
572 INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off);
573 }
574
575 /*
576 * before this line, we must NOT send anything to the display.
577 * Since charlcd_init_display() needs to write data, we have to
578 * enable mark the LCD initialized just before.
579 */
Lars Poeschel01ec46d2020-11-03 10:58:17 +0100580 ret = lcd->ops->init_display(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100581 if (ret)
582 return ret;
583
584 /* display a short message */
Mans Rullgardcc5d04d2019-03-01 18:48:16 +0000585 charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT);
Mans Rullgardc9171722019-03-01 18:48:15 +0000586
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100587 /* clear the display on the next device opening */
588 priv->must_clear = true;
589 charlcd_home(lcd);
590 return 0;
591}
592
Lars Poeschel2545c1c2020-11-03 10:58:06 +0100593struct charlcd *charlcd_alloc(void)
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100594{
595 struct charlcd_priv *priv;
596 struct charlcd *lcd;
597
Lars Poeschel2545c1c2020-11-03 10:58:06 +0100598 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100599 if (!priv)
600 return NULL;
601
602 priv->esc_seq.len = -1;
603
604 lcd = &priv->lcd;
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100605
606 return lcd;
607}
608EXPORT_SYMBOL_GPL(charlcd_alloc);
609
Andy Shevchenko8e44fc82019-03-12 16:44:30 +0200610void charlcd_free(struct charlcd *lcd)
611{
612 kfree(charlcd_to_priv(lcd));
613}
614EXPORT_SYMBOL_GPL(charlcd_free);
615
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100616static int panel_notify_sys(struct notifier_block *this, unsigned long code,
617 void *unused)
618{
619 struct charlcd *lcd = the_charlcd;
620
621 switch (code) {
622 case SYS_DOWN:
623 charlcd_puts(lcd,
624 "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+");
625 break;
626 case SYS_HALT:
627 charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+");
628 break;
629 case SYS_POWER_OFF:
630 charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");
631 break;
632 default:
633 break;
634 }
635 return NOTIFY_DONE;
636}
637
638static struct notifier_block panel_notifier = {
639 panel_notify_sys,
640 NULL,
641 0
642};
643
644int charlcd_register(struct charlcd *lcd)
645{
646 int ret;
647
648 ret = charlcd_init(lcd);
649 if (ret)
650 return ret;
651
652 ret = misc_register(&charlcd_dev);
653 if (ret)
654 return ret;
655
656 the_charlcd = lcd;
657 register_reboot_notifier(&panel_notifier);
658 return 0;
659}
660EXPORT_SYMBOL_GPL(charlcd_register);
661
662int charlcd_unregister(struct charlcd *lcd)
663{
Andy Shevchenkob658a212019-03-12 16:44:29 +0200664 struct charlcd_priv *priv = charlcd_to_priv(lcd);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100665
666 unregister_reboot_notifier(&panel_notifier);
667 charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-");
668 misc_deregister(&charlcd_dev);
669 the_charlcd = NULL;
670 if (lcd->ops->backlight) {
671 cancel_delayed_work_sync(&priv->bl_work);
Lars Poeschelbd26b182020-11-03 10:58:16 +0100672 priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
Geert Uytterhoeven39f8ea42017-03-10 15:15:17 +0100673 }
674
675 return 0;
676}
677EXPORT_SYMBOL_GPL(charlcd_unregister);
678
679MODULE_LICENSE("GPL");