Commit | Line | Data |
---|---|---|
351f683b | 1 | // SPDX-License-Identifier: GPL-2.0+ |
39f8ea46 GU |
2 | /* |
3 | * Character LCD driver for Linux | |
4 | * | |
5 | * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu> | |
6 | * Copyright (C) 2016-2017 Glider bvba | |
39f8ea46 GU |
7 | */ |
8 | ||
9 | #include <linux/atomic.h> | |
b34050fa | 10 | #include <linux/ctype.h> |
39f8ea46 GU |
11 | #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 | ||
93ee235f | 20 | #ifndef CONFIG_PANEL_BOOT_MESSAGE |
39f8ea46 | 21 | #include <generated/utsrelease.h> |
93ee235f | 22 | #endif |
39f8ea46 | 23 | |
75354284 | 24 | #include "charlcd.h" |
39f8ea46 GU |
25 | |
26 | /* Keep the backlight on this many seconds for each flash */ | |
27 | #define LCD_BL_TEMPO_PERIOD 4 | |
28 | ||
39f8ea46 GU |
29 | #define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */ |
30 | #define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */ | |
31 | ||
32 | struct charlcd_priv { | |
33 | struct charlcd lcd; | |
34 | ||
35 | struct delayed_work bl_work; | |
36 | struct mutex bl_tempo_lock; /* Protects access to bl_tempo */ | |
37 | bool bl_tempo; | |
38 | ||
39 | bool must_clear; | |
40 | ||
41 | /* contains the LCD config state */ | |
94047df1 | 42 | unsigned long flags; |
39f8ea46 | 43 | |
39f8ea46 GU |
44 | /* Current escape sequence and it's length or -1 if outside */ |
45 | struct { | |
46 | char buf[LCD_ESCAPE_LEN + 1]; | |
47 | int len; | |
48 | } esc_seq; | |
49 | ||
2f920c0f | 50 | unsigned long long drvdata[]; |
39f8ea46 GU |
51 | }; |
52 | ||
b658a211 | 53 | #define charlcd_to_priv(p) container_of(p, struct charlcd_priv, lcd) |
39f8ea46 GU |
54 | |
55 | /* Device single-open policy control */ | |
56 | static atomic_t charlcd_available = ATOMIC_INIT(1); | |
57 | ||
39f8ea46 | 58 | /* turn the backlight on or off */ |
2bf82b5a | 59 | void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on) |
39f8ea46 | 60 | { |
b658a211 | 61 | struct charlcd_priv *priv = charlcd_to_priv(lcd); |
39f8ea46 GU |
62 | |
63 | if (!lcd->ops->backlight) | |
64 | return; | |
65 | ||
66 | mutex_lock(&priv->bl_tempo_lock); | |
67 | if (!priv->bl_tempo) | |
68 | lcd->ops->backlight(lcd, on); | |
69 | mutex_unlock(&priv->bl_tempo_lock); | |
70 | } | |
2bf82b5a | 71 | EXPORT_SYMBOL_GPL(charlcd_backlight); |
39f8ea46 GU |
72 | |
73 | static void charlcd_bl_off(struct work_struct *work) | |
74 | { | |
75 | struct delayed_work *dwork = to_delayed_work(work); | |
76 | struct charlcd_priv *priv = | |
77 | container_of(dwork, struct charlcd_priv, bl_work); | |
78 | ||
79 | mutex_lock(&priv->bl_tempo_lock); | |
80 | if (priv->bl_tempo) { | |
81 | priv->bl_tempo = false; | |
82 | if (!(priv->flags & LCD_FLAG_L)) | |
bd26b181 | 83 | priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF); |
39f8ea46 GU |
84 | } |
85 | mutex_unlock(&priv->bl_tempo_lock); | |
86 | } | |
87 | ||
88 | /* turn the backlight on for a little while */ | |
89 | void charlcd_poke(struct charlcd *lcd) | |
90 | { | |
b658a211 | 91 | struct charlcd_priv *priv = charlcd_to_priv(lcd); |
39f8ea46 GU |
92 | |
93 | if (!lcd->ops->backlight) | |
94 | return; | |
95 | ||
96 | cancel_delayed_work_sync(&priv->bl_work); | |
97 | ||
98 | mutex_lock(&priv->bl_tempo_lock); | |
99 | if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L)) | |
bd26b181 | 100 | lcd->ops->backlight(lcd, CHARLCD_ON); |
39f8ea46 GU |
101 | priv->bl_tempo = true; |
102 | schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ); | |
103 | mutex_unlock(&priv->bl_tempo_lock); | |
104 | } | |
105 | EXPORT_SYMBOL_GPL(charlcd_poke); | |
106 | ||
39f8ea46 GU |
107 | static void charlcd_home(struct charlcd *lcd) |
108 | { | |
11588b59 LP |
109 | lcd->addr.x = 0; |
110 | lcd->addr.y = 0; | |
88645a86 | 111 | lcd->ops->home(lcd); |
39f8ea46 GU |
112 | } |
113 | ||
114 | static void charlcd_print(struct charlcd *lcd, char c) | |
115 | { | |
af9470b2 LP |
116 | if (lcd->addr.x >= lcd->width) |
117 | return; | |
118 | ||
b26deabb LP |
119 | if (lcd->char_conv) |
120 | c = lcd->char_conv[(unsigned char)c]; | |
39f8ea46 | 121 | |
b26deabb | 122 | if (!lcd->ops->print(lcd, c)) |
11588b59 | 123 | lcd->addr.x++; |
54bc937f | 124 | |
b26deabb | 125 | /* prevents the cursor from wrapping onto the next line */ |
6e49eea3 | 126 | if (lcd->addr.x == lcd->width) |
40c2b72e | 127 | lcd->ops->gotoxy(lcd, lcd->addr.x - 1, lcd->addr.y); |
39f8ea46 GU |
128 | } |
129 | ||
377cf2cb | 130 | static void charlcd_clear_display(struct charlcd *lcd) |
39f8ea46 | 131 | { |
377cf2cb LP |
132 | lcd->ops->clear_display(lcd); |
133 | lcd->addr.x = 0; | |
134 | lcd->addr.y = 0; | |
39f8ea46 GU |
135 | } |
136 | ||
b34050fa MO |
137 | /* |
138 | * Parses a movement command of the form "(.*);", where the group can be | |
139 | * any number of subcommands of the form "(x|y)[0-9]+". | |
140 | * | |
141 | * Returns whether the command is valid. The position arguments are | |
142 | * only written if the parsing was successful. | |
143 | * | |
144 | * For instance: | |
145 | * - ";" returns (<original x>, <original y>). | |
146 | * - "x1;" returns (1, <original y>). | |
147 | * - "y2x1;" returns (1, 2). | |
148 | * - "x12y34x56;" returns (56, 34). | |
149 | * - "" fails. | |
150 | * - "x" fails. | |
151 | * - "x;" fails. | |
152 | * - "x1" fails. | |
153 | * - "xy12;" fails. | |
154 | * - "x12yy12;" fails. | |
155 | * - "xx" fails. | |
156 | */ | |
157 | static bool parse_xy(const char *s, unsigned long *x, unsigned long *y) | |
158 | { | |
159 | unsigned long new_x = *x; | |
160 | unsigned long new_y = *y; | |
d717e7da | 161 | char *p; |
b34050fa MO |
162 | |
163 | for (;;) { | |
164 | if (!*s) | |
165 | return false; | |
166 | ||
167 | if (*s == ';') | |
168 | break; | |
169 | ||
170 | if (*s == 'x') { | |
d717e7da AS |
171 | new_x = simple_strtoul(s + 1, &p, 10); |
172 | if (p == s + 1) | |
b34050fa | 173 | return false; |
d717e7da | 174 | s = p; |
b34050fa | 175 | } else if (*s == 'y') { |
d717e7da AS |
176 | new_y = simple_strtoul(s + 1, &p, 10); |
177 | if (p == s + 1) | |
b34050fa | 178 | return false; |
d717e7da | 179 | s = p; |
b34050fa MO |
180 | } else { |
181 | return false; | |
182 | } | |
183 | } | |
184 | ||
185 | *x = new_x; | |
186 | *y = new_y; | |
187 | return true; | |
188 | } | |
189 | ||
39f8ea46 GU |
190 | /* |
191 | * These are the file operation function for user access to /dev/lcd | |
192 | * This function can also be called from inside the kernel, by | |
193 | * setting file and ppos to NULL. | |
194 | * | |
195 | */ | |
196 | ||
197 | static inline int handle_lcd_special_code(struct charlcd *lcd) | |
198 | { | |
b658a211 | 199 | struct charlcd_priv *priv = charlcd_to_priv(lcd); |
39f8ea46 GU |
200 | |
201 | /* LCD special codes */ | |
202 | ||
203 | int processed = 0; | |
204 | ||
205 | char *esc = priv->esc_seq.buf + 2; | |
206 | int oldflags = priv->flags; | |
207 | ||
208 | /* check for display mode flags */ | |
209 | switch (*esc) { | |
210 | case 'D': /* Display ON */ | |
211 | priv->flags |= LCD_FLAG_D; | |
d2f2187e LP |
212 | if (priv->flags != oldflags) |
213 | lcd->ops->display(lcd, CHARLCD_ON); | |
214 | ||
39f8ea46 GU |
215 | processed = 1; |
216 | break; | |
217 | case 'd': /* Display OFF */ | |
218 | priv->flags &= ~LCD_FLAG_D; | |
d2f2187e LP |
219 | if (priv->flags != oldflags) |
220 | lcd->ops->display(lcd, CHARLCD_OFF); | |
221 | ||
39f8ea46 GU |
222 | processed = 1; |
223 | break; | |
224 | case 'C': /* Cursor ON */ | |
225 | priv->flags |= LCD_FLAG_C; | |
d2f2187e LP |
226 | if (priv->flags != oldflags) |
227 | lcd->ops->cursor(lcd, CHARLCD_ON); | |
228 | ||
39f8ea46 GU |
229 | processed = 1; |
230 | break; | |
231 | case 'c': /* Cursor OFF */ | |
232 | priv->flags &= ~LCD_FLAG_C; | |
d2f2187e LP |
233 | if (priv->flags != oldflags) |
234 | lcd->ops->cursor(lcd, CHARLCD_OFF); | |
235 | ||
39f8ea46 GU |
236 | processed = 1; |
237 | break; | |
238 | case 'B': /* Blink ON */ | |
239 | priv->flags |= LCD_FLAG_B; | |
d2f2187e LP |
240 | if (priv->flags != oldflags) |
241 | lcd->ops->blink(lcd, CHARLCD_ON); | |
242 | ||
39f8ea46 GU |
243 | processed = 1; |
244 | break; | |
245 | case 'b': /* Blink OFF */ | |
246 | priv->flags &= ~LCD_FLAG_B; | |
d2f2187e LP |
247 | if (priv->flags != oldflags) |
248 | lcd->ops->blink(lcd, CHARLCD_OFF); | |
249 | ||
39f8ea46 GU |
250 | processed = 1; |
251 | break; | |
252 | case '+': /* Back light ON */ | |
253 | priv->flags |= LCD_FLAG_L; | |
a2060f28 LP |
254 | if (priv->flags != oldflags) |
255 | charlcd_backlight(lcd, CHARLCD_ON); | |
256 | ||
39f8ea46 GU |
257 | processed = 1; |
258 | break; | |
259 | case '-': /* Back light OFF */ | |
260 | priv->flags &= ~LCD_FLAG_L; | |
a2060f28 LP |
261 | if (priv->flags != oldflags) |
262 | charlcd_backlight(lcd, CHARLCD_OFF); | |
263 | ||
39f8ea46 GU |
264 | processed = 1; |
265 | break; | |
266 | case '*': /* Flash back light */ | |
267 | charlcd_poke(lcd); | |
268 | processed = 1; | |
269 | break; | |
270 | case 'f': /* Small Font */ | |
271 | priv->flags &= ~LCD_FLAG_F; | |
d2f2187e LP |
272 | if (priv->flags != oldflags) |
273 | lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_SMALL); | |
274 | ||
39f8ea46 GU |
275 | processed = 1; |
276 | break; | |
277 | case 'F': /* Large Font */ | |
278 | priv->flags |= LCD_FLAG_F; | |
d2f2187e LP |
279 | if (priv->flags != oldflags) |
280 | lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_LARGE); | |
281 | ||
39f8ea46 GU |
282 | processed = 1; |
283 | break; | |
284 | case 'n': /* One Line */ | |
285 | priv->flags &= ~LCD_FLAG_N; | |
d2f2187e LP |
286 | if (priv->flags != oldflags) |
287 | lcd->ops->lines(lcd, CHARLCD_LINES_1); | |
288 | ||
39f8ea46 GU |
289 | processed = 1; |
290 | break; | |
291 | case 'N': /* Two Lines */ | |
292 | priv->flags |= LCD_FLAG_N; | |
d2f2187e LP |
293 | if (priv->flags != oldflags) |
294 | lcd->ops->lines(lcd, CHARLCD_LINES_2); | |
295 | ||
99b9b490 | 296 | processed = 1; |
39f8ea46 GU |
297 | break; |
298 | case 'l': /* Shift Cursor Left */ | |
11588b59 | 299 | if (lcd->addr.x > 0) { |
d2f2187e LP |
300 | if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT)) |
301 | lcd->addr.x--; | |
39f8ea46 | 302 | } |
d2f2187e | 303 | |
39f8ea46 GU |
304 | processed = 1; |
305 | break; | |
306 | case 'r': /* shift cursor right */ | |
11588b59 | 307 | if (lcd->addr.x < lcd->width) { |
d2f2187e LP |
308 | if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_RIGHT)) |
309 | lcd->addr.x++; | |
39f8ea46 | 310 | } |
d2f2187e | 311 | |
39f8ea46 GU |
312 | processed = 1; |
313 | break; | |
314 | case 'L': /* shift display left */ | |
d2f2187e | 315 | lcd->ops->shift_display(lcd, CHARLCD_SHIFT_LEFT); |
39f8ea46 GU |
316 | processed = 1; |
317 | break; | |
318 | case 'R': /* shift display right */ | |
d2f2187e | 319 | lcd->ops->shift_display(lcd, CHARLCD_SHIFT_RIGHT); |
39f8ea46 GU |
320 | processed = 1; |
321 | break; | |
322 | case 'k': { /* kill end of line */ | |
b26deabb | 323 | int x, xs, ys; |
39f8ea46 | 324 | |
b26deabb LP |
325 | xs = lcd->addr.x; |
326 | ys = lcd->addr.y; | |
6e49eea3 | 327 | for (x = lcd->addr.x; x < lcd->width; x++) |
b26deabb | 328 | lcd->ops->print(lcd, ' '); |
39f8ea46 GU |
329 | |
330 | /* restore cursor position */ | |
b26deabb LP |
331 | lcd->addr.x = xs; |
332 | lcd->addr.y = ys; | |
40c2b72e | 333 | lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y); |
39f8ea46 GU |
334 | processed = 1; |
335 | break; | |
336 | } | |
337 | case 'I': /* reinitialize display */ | |
01ec46df LP |
338 | lcd->ops->init_display(lcd); |
339 | priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | | |
340 | LCD_FLAG_C | LCD_FLAG_B; | |
39f8ea46 GU |
341 | processed = 1; |
342 | break; | |
339acb08 LP |
343 | case 'G': |
344 | if (lcd->ops->redefine_char) | |
345 | processed = lcd->ops->redefine_char(lcd, esc); | |
346 | else | |
39f8ea46 | 347 | processed = 1; |
39f8ea46 | 348 | break; |
339acb08 | 349 | |
39f8ea46 GU |
350 | case 'x': /* gotoxy : LxXXX[yYYY]; */ |
351 | case 'y': /* gotoxy : LyYYY[xXXX]; */ | |
9bc30ab8 MR |
352 | if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';') |
353 | break; | |
354 | ||
b34050fa | 355 | /* If the command is valid, move to the new address */ |
11588b59 | 356 | if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y)) |
40c2b72e | 357 | lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y); |
39f8ea46 | 358 | |
b34050fa | 359 | /* Regardless of its validity, mark as processed */ |
39f8ea46 GU |
360 | processed = 1; |
361 | break; | |
362 | } | |
363 | ||
39f8ea46 GU |
364 | return processed; |
365 | } | |
366 | ||
367 | static void charlcd_write_char(struct charlcd *lcd, char c) | |
368 | { | |
b658a211 | 369 | struct charlcd_priv *priv = charlcd_to_priv(lcd); |
39f8ea46 GU |
370 | |
371 | /* first, we'll test if we're in escape mode */ | |
372 | if ((c != '\n') && priv->esc_seq.len >= 0) { | |
373 | /* yes, let's add this char to the buffer */ | |
374 | priv->esc_seq.buf[priv->esc_seq.len++] = c; | |
8c483758 | 375 | priv->esc_seq.buf[priv->esc_seq.len] = '\0'; |
39f8ea46 GU |
376 | } else { |
377 | /* aborts any previous escape sequence */ | |
378 | priv->esc_seq.len = -1; | |
379 | ||
380 | switch (c) { | |
381 | case LCD_ESCAPE_CHAR: | |
382 | /* start of an escape sequence */ | |
383 | priv->esc_seq.len = 0; | |
8c483758 | 384 | priv->esc_seq.buf[priv->esc_seq.len] = '\0'; |
39f8ea46 GU |
385 | break; |
386 | case '\b': | |
387 | /* go back one char and clear it */ | |
11588b59 | 388 | if (lcd->addr.x > 0) { |
d2f2187e LP |
389 | /* back one char */ |
390 | if (!lcd->ops->shift_cursor(lcd, | |
391 | CHARLCD_SHIFT_LEFT)) | |
392 | lcd->addr.x--; | |
39f8ea46 GU |
393 | } |
394 | /* replace with a space */ | |
d2f2187e | 395 | charlcd_print(lcd, ' '); |
39f8ea46 | 396 | /* back one char again */ |
d2f2187e LP |
397 | if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT)) |
398 | lcd->addr.x--; | |
399 | ||
39f8ea46 | 400 | break; |
9629ccca | 401 | case '\f': |
39f8ea46 | 402 | /* quickly clear the display */ |
377cf2cb | 403 | charlcd_clear_display(lcd); |
39f8ea46 GU |
404 | break; |
405 | case '\n': | |
406 | /* | |
407 | * flush the remainder of the current line and | |
408 | * go to the beginning of the next line | |
409 | */ | |
6e49eea3 | 410 | for (; lcd->addr.x < lcd->width; lcd->addr.x++) |
b26deabb LP |
411 | lcd->ops->print(lcd, ' '); |
412 | ||
11588b59 LP |
413 | lcd->addr.x = 0; |
414 | lcd->addr.y = (lcd->addr.y + 1) % lcd->height; | |
40c2b72e | 415 | lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y); |
39f8ea46 GU |
416 | break; |
417 | case '\r': | |
418 | /* go to the beginning of the same line */ | |
11588b59 | 419 | lcd->addr.x = 0; |
40c2b72e | 420 | lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y); |
39f8ea46 GU |
421 | break; |
422 | case '\t': | |
423 | /* print a space instead of the tab */ | |
424 | charlcd_print(lcd, ' '); | |
425 | break; | |
426 | default: | |
427 | /* simply print this char */ | |
428 | charlcd_print(lcd, c); | |
429 | break; | |
430 | } | |
431 | } | |
432 | ||
433 | /* | |
434 | * now we'll see if we're in an escape mode and if the current | |
435 | * escape sequence can be understood. | |
436 | */ | |
437 | if (priv->esc_seq.len >= 2) { | |
438 | int processed = 0; | |
439 | ||
440 | if (!strcmp(priv->esc_seq.buf, "[2J")) { | |
441 | /* clear the display */ | |
377cf2cb | 442 | charlcd_clear_display(lcd); |
39f8ea46 GU |
443 | processed = 1; |
444 | } else if (!strcmp(priv->esc_seq.buf, "[H")) { | |
445 | /* cursor to home */ | |
446 | charlcd_home(lcd); | |
447 | processed = 1; | |
448 | } | |
449 | /* codes starting with ^[[L */ | |
450 | else if ((priv->esc_seq.len >= 3) && | |
451 | (priv->esc_seq.buf[0] == '[') && | |
452 | (priv->esc_seq.buf[1] == 'L')) { | |
453 | processed = handle_lcd_special_code(lcd); | |
454 | } | |
455 | ||
456 | /* LCD special escape codes */ | |
457 | /* | |
458 | * flush the escape sequence if it's been processed | |
459 | * or if it is getting too long. | |
460 | */ | |
461 | if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN)) | |
462 | priv->esc_seq.len = -1; | |
463 | } /* escape codes */ | |
464 | } | |
465 | ||
466 | static struct charlcd *the_charlcd; | |
467 | ||
468 | static ssize_t charlcd_write(struct file *file, const char __user *buf, | |
469 | size_t count, loff_t *ppos) | |
470 | { | |
471 | const char __user *tmp = buf; | |
472 | char c; | |
473 | ||
474 | for (; count-- > 0; (*ppos)++, tmp++) { | |
701454bc | 475 | if (((count + 1) & 0x1f) == 0) { |
39f8ea46 | 476 | /* |
701454bc SAS |
477 | * charlcd_write() is invoked as a VFS->write() callback |
478 | * and as such it is always invoked from preemptible | |
479 | * context and may sleep. | |
39f8ea46 | 480 | */ |
701454bc SAS |
481 | cond_resched(); |
482 | } | |
39f8ea46 GU |
483 | |
484 | if (get_user(c, tmp)) | |
485 | return -EFAULT; | |
486 | ||
487 | charlcd_write_char(the_charlcd, c); | |
488 | } | |
489 | ||
490 | return tmp - buf; | |
491 | } | |
492 | ||
493 | static int charlcd_open(struct inode *inode, struct file *file) | |
494 | { | |
b658a211 | 495 | struct charlcd_priv *priv = charlcd_to_priv(the_charlcd); |
93dc1774 | 496 | int ret; |
39f8ea46 | 497 | |
93dc1774 | 498 | ret = -EBUSY; |
39f8ea46 | 499 | if (!atomic_dec_and_test(&charlcd_available)) |
93dc1774 | 500 | goto fail; /* open only once at a time */ |
39f8ea46 | 501 | |
93dc1774 | 502 | ret = -EPERM; |
39f8ea46 | 503 | if (file->f_mode & FMODE_READ) /* device is write-only */ |
93dc1774 | 504 | goto fail; |
39f8ea46 GU |
505 | |
506 | if (priv->must_clear) { | |
45421ffe | 507 | priv->lcd.ops->clear_display(&priv->lcd); |
39f8ea46 | 508 | priv->must_clear = false; |
45421ffe LP |
509 | priv->lcd.addr.x = 0; |
510 | priv->lcd.addr.y = 0; | |
39f8ea46 GU |
511 | } |
512 | return nonseekable_open(inode, file); | |
93dc1774 WT |
513 | |
514 | fail: | |
515 | atomic_inc(&charlcd_available); | |
516 | return ret; | |
39f8ea46 GU |
517 | } |
518 | ||
519 | static int charlcd_release(struct inode *inode, struct file *file) | |
520 | { | |
521 | atomic_inc(&charlcd_available); | |
522 | return 0; | |
523 | } | |
524 | ||
525 | static const struct file_operations charlcd_fops = { | |
526 | .write = charlcd_write, | |
527 | .open = charlcd_open, | |
528 | .release = charlcd_release, | |
39f8ea46 GU |
529 | }; |
530 | ||
531 | static struct miscdevice charlcd_dev = { | |
532 | .minor = LCD_MINOR, | |
533 | .name = "lcd", | |
534 | .fops = &charlcd_fops, | |
535 | }; | |
536 | ||
537 | static void charlcd_puts(struct charlcd *lcd, const char *s) | |
538 | { | |
539 | const char *tmp = s; | |
540 | int count = strlen(s); | |
541 | ||
542 | for (; count-- > 0; tmp++) { | |
701454bc SAS |
543 | if (((count + 1) & 0x1f) == 0) |
544 | cond_resched(); | |
39f8ea46 GU |
545 | |
546 | charlcd_write_char(lcd, *tmp); | |
547 | } | |
548 | } | |
549 | ||
c9171722 MR |
550 | #ifdef CONFIG_PANEL_BOOT_MESSAGE |
551 | #define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE | |
552 | #else | |
553 | #define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n" | |
554 | #endif | |
555 | ||
cc5d04d8 MR |
556 | #ifdef CONFIG_CHARLCD_BL_ON |
557 | #define LCD_INIT_BL "\x1b[L+" | |
558 | #elif defined(CONFIG_CHARLCD_BL_FLASH) | |
559 | #define LCD_INIT_BL "\x1b[L*" | |
560 | #else | |
561 | #define LCD_INIT_BL "\x1b[L-" | |
562 | #endif | |
563 | ||
39f8ea46 GU |
564 | /* initialize the LCD driver */ |
565 | static int charlcd_init(struct charlcd *lcd) | |
566 | { | |
b658a211 | 567 | struct charlcd_priv *priv = charlcd_to_priv(lcd); |
39f8ea46 GU |
568 | int ret; |
569 | ||
01ec46df LP |
570 | priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | |
571 | LCD_FLAG_C | LCD_FLAG_B; | |
39f8ea46 GU |
572 | if (lcd->ops->backlight) { |
573 | mutex_init(&priv->bl_tempo_lock); | |
574 | INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off); | |
575 | } | |
576 | ||
577 | /* | |
578 | * before this line, we must NOT send anything to the display. | |
579 | * Since charlcd_init_display() needs to write data, we have to | |
580 | * enable mark the LCD initialized just before. | |
581 | */ | |
4daa9ff8 LS |
582 | if (WARN_ON(!lcd->ops->init_display)) |
583 | return -EINVAL; | |
584 | ||
01ec46df | 585 | ret = lcd->ops->init_display(lcd); |
39f8ea46 GU |
586 | if (ret) |
587 | return ret; | |
588 | ||
589 | /* display a short message */ | |
cc5d04d8 | 590 | charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT); |
c9171722 | 591 | |
39f8ea46 GU |
592 | /* clear the display on the next device opening */ |
593 | priv->must_clear = true; | |
594 | charlcd_home(lcd); | |
595 | return 0; | |
596 | } | |
597 | ||
09965a14 | 598 | struct charlcd *charlcd_alloc(unsigned int drvdata_size) |
39f8ea46 GU |
599 | { |
600 | struct charlcd_priv *priv; | |
601 | struct charlcd *lcd; | |
602 | ||
09965a14 | 603 | priv = kzalloc(sizeof(*priv) + drvdata_size, GFP_KERNEL); |
39f8ea46 GU |
604 | if (!priv) |
605 | return NULL; | |
606 | ||
607 | priv->esc_seq.len = -1; | |
608 | ||
609 | lcd = &priv->lcd; | |
09965a14 | 610 | lcd->drvdata = priv->drvdata; |
39f8ea46 GU |
611 | |
612 | return lcd; | |
613 | } | |
614 | EXPORT_SYMBOL_GPL(charlcd_alloc); | |
615 | ||
8e44fc85 AS |
616 | void charlcd_free(struct charlcd *lcd) |
617 | { | |
618 | kfree(charlcd_to_priv(lcd)); | |
619 | } | |
620 | EXPORT_SYMBOL_GPL(charlcd_free); | |
621 | ||
39f8ea46 GU |
622 | static int panel_notify_sys(struct notifier_block *this, unsigned long code, |
623 | void *unused) | |
624 | { | |
625 | struct charlcd *lcd = the_charlcd; | |
626 | ||
627 | switch (code) { | |
628 | case SYS_DOWN: | |
629 | charlcd_puts(lcd, | |
630 | "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); | |
631 | break; | |
632 | case SYS_HALT: | |
633 | charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); | |
634 | break; | |
635 | case SYS_POWER_OFF: | |
636 | charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); | |
637 | break; | |
638 | default: | |
639 | break; | |
640 | } | |
641 | return NOTIFY_DONE; | |
642 | } | |
643 | ||
644 | static struct notifier_block panel_notifier = { | |
ac8c8fa0 | 645 | .notifier_call = panel_notify_sys, |
39f8ea46 GU |
646 | }; |
647 | ||
648 | int charlcd_register(struct charlcd *lcd) | |
649 | { | |
650 | int ret; | |
651 | ||
652 | ret = charlcd_init(lcd); | |
653 | if (ret) | |
654 | return ret; | |
655 | ||
656 | ret = misc_register(&charlcd_dev); | |
657 | if (ret) | |
658 | return ret; | |
659 | ||
660 | the_charlcd = lcd; | |
661 | register_reboot_notifier(&panel_notifier); | |
662 | return 0; | |
663 | } | |
664 | EXPORT_SYMBOL_GPL(charlcd_register); | |
665 | ||
666 | int charlcd_unregister(struct charlcd *lcd) | |
667 | { | |
b658a211 | 668 | struct charlcd_priv *priv = charlcd_to_priv(lcd); |
39f8ea46 GU |
669 | |
670 | unregister_reboot_notifier(&panel_notifier); | |
671 | charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-"); | |
672 | misc_deregister(&charlcd_dev); | |
673 | the_charlcd = NULL; | |
674 | if (lcd->ops->backlight) { | |
675 | cancel_delayed_work_sync(&priv->bl_work); | |
bd26b181 | 676 | priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF); |
39f8ea46 GU |
677 | } |
678 | ||
679 | return 0; | |
680 | } | |
681 | EXPORT_SYMBOL_GPL(charlcd_unregister); | |
682 | ||
29b08729 | 683 | MODULE_DESCRIPTION("Character LCD core support"); |
39f8ea46 | 684 | MODULE_LICENSE("GPL"); |