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