]>
Commit | Line | Data |
---|---|---|
39f8ea46 GU |
1 | /* |
2 | * Character LCD driver for Linux | |
3 | * | |
4 | * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu> | |
5 | * Copyright (C) 2016-2017 Glider bvba | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License | |
9 | * as published by the Free Software Foundation; either version | |
10 | * 2 of the License, or (at your option) any later version. | |
11 | */ | |
12 | ||
13 | #include <linux/atomic.h> | |
14 | #include <linux/delay.h> | |
15 | #include <linux/fs.h> | |
16 | #include <linux/miscdevice.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/notifier.h> | |
19 | #include <linux/reboot.h> | |
20 | #include <linux/slab.h> | |
21 | #include <linux/uaccess.h> | |
22 | #include <linux/workqueue.h> | |
23 | ||
24 | #include <generated/utsrelease.h> | |
25 | ||
26 | #include <misc/charlcd.h> | |
27 | ||
28 | #define LCD_MINOR 156 | |
29 | ||
30 | #define DEFAULT_LCD_BWIDTH 40 | |
31 | #define DEFAULT_LCD_HWIDTH 64 | |
32 | ||
33 | /* Keep the backlight on this many seconds for each flash */ | |
34 | #define LCD_BL_TEMPO_PERIOD 4 | |
35 | ||
36 | #define LCD_FLAG_B 0x0004 /* Blink on */ | |
37 | #define LCD_FLAG_C 0x0008 /* Cursor on */ | |
38 | #define LCD_FLAG_D 0x0010 /* Display on */ | |
39 | #define LCD_FLAG_F 0x0020 /* Large font mode */ | |
40 | #define LCD_FLAG_N 0x0040 /* 2-rows mode */ | |
41 | #define LCD_FLAG_L 0x0080 /* Backlight enabled */ | |
42 | ||
43 | /* LCD commands */ | |
44 | #define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */ | |
45 | ||
46 | #define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */ | |
47 | #define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */ | |
48 | ||
49 | #define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */ | |
50 | #define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */ | |
51 | #define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */ | |
52 | #define LCD_CMD_BLINK_ON 0x01 /* Set blink on */ | |
53 | ||
54 | #define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */ | |
55 | #define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */ | |
56 | #define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */ | |
57 | ||
58 | #define LCD_CMD_FUNCTION_SET 0x20 /* Set function */ | |
59 | #define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */ | |
60 | #define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */ | |
61 | #define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */ | |
62 | ||
63 | #define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */ | |
64 | ||
65 | #define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */ | |
66 | ||
67 | #define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */ | |
68 | #define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */ | |
69 | ||
70 | struct charlcd_priv { | |
71 | struct charlcd lcd; | |
72 | ||
73 | struct delayed_work bl_work; | |
74 | struct mutex bl_tempo_lock; /* Protects access to bl_tempo */ | |
75 | bool bl_tempo; | |
76 | ||
77 | bool must_clear; | |
78 | ||
79 | /* contains the LCD config state */ | |
80 | unsigned long int flags; | |
81 | ||
82 | /* Contains the LCD X and Y offset */ | |
83 | struct { | |
84 | unsigned long int x; | |
85 | unsigned long int y; | |
86 | } addr; | |
87 | ||
88 | /* Current escape sequence and it's length or -1 if outside */ | |
89 | struct { | |
90 | char buf[LCD_ESCAPE_LEN + 1]; | |
91 | int len; | |
92 | } esc_seq; | |
93 | ||
94 | unsigned long long drvdata[0]; | |
95 | }; | |
96 | ||
97 | #define to_priv(p) container_of(p, struct charlcd_priv, lcd) | |
98 | ||
99 | /* Device single-open policy control */ | |
100 | static atomic_t charlcd_available = ATOMIC_INIT(1); | |
101 | ||
102 | /* sleeps that many milliseconds with a reschedule */ | |
103 | static void long_sleep(int ms) | |
104 | { | |
105 | if (in_interrupt()) | |
106 | mdelay(ms); | |
107 | else | |
108 | schedule_timeout_interruptible(msecs_to_jiffies(ms)); | |
109 | } | |
110 | ||
111 | /* turn the backlight on or off */ | |
112 | static void charlcd_backlight(struct charlcd *lcd, int on) | |
113 | { | |
114 | struct charlcd_priv *priv = to_priv(lcd); | |
115 | ||
116 | if (!lcd->ops->backlight) | |
117 | return; | |
118 | ||
119 | mutex_lock(&priv->bl_tempo_lock); | |
120 | if (!priv->bl_tempo) | |
121 | lcd->ops->backlight(lcd, on); | |
122 | mutex_unlock(&priv->bl_tempo_lock); | |
123 | } | |
124 | ||
125 | static void charlcd_bl_off(struct work_struct *work) | |
126 | { | |
127 | struct delayed_work *dwork = to_delayed_work(work); | |
128 | struct charlcd_priv *priv = | |
129 | container_of(dwork, struct charlcd_priv, bl_work); | |
130 | ||
131 | mutex_lock(&priv->bl_tempo_lock); | |
132 | if (priv->bl_tempo) { | |
133 | priv->bl_tempo = false; | |
134 | if (!(priv->flags & LCD_FLAG_L)) | |
135 | priv->lcd.ops->backlight(&priv->lcd, 0); | |
136 | } | |
137 | mutex_unlock(&priv->bl_tempo_lock); | |
138 | } | |
139 | ||
140 | /* turn the backlight on for a little while */ | |
141 | void charlcd_poke(struct charlcd *lcd) | |
142 | { | |
143 | struct charlcd_priv *priv = to_priv(lcd); | |
144 | ||
145 | if (!lcd->ops->backlight) | |
146 | return; | |
147 | ||
148 | cancel_delayed_work_sync(&priv->bl_work); | |
149 | ||
150 | mutex_lock(&priv->bl_tempo_lock); | |
151 | if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L)) | |
152 | lcd->ops->backlight(lcd, 1); | |
153 | priv->bl_tempo = true; | |
154 | schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ); | |
155 | mutex_unlock(&priv->bl_tempo_lock); | |
156 | } | |
157 | EXPORT_SYMBOL_GPL(charlcd_poke); | |
158 | ||
159 | static void charlcd_gotoxy(struct charlcd *lcd) | |
160 | { | |
161 | struct charlcd_priv *priv = to_priv(lcd); | |
1d3b2af2 GU |
162 | unsigned int addr; |
163 | ||
164 | /* | |
165 | * we force the cursor to stay at the end of the | |
166 | * line if it wants to go farther | |
167 | */ | |
168 | addr = priv->addr.x < lcd->bwidth ? priv->addr.x & (lcd->hwidth - 1) | |
169 | : lcd->bwidth - 1; | |
170 | if (priv->addr.y & 1) | |
171 | addr += lcd->hwidth; | |
172 | if (priv->addr.y & 2) | |
173 | addr += lcd->bwidth; | |
174 | lcd->ops->write_cmd(lcd, LCD_CMD_SET_DDRAM_ADDR | addr); | |
39f8ea46 GU |
175 | } |
176 | ||
177 | static void charlcd_home(struct charlcd *lcd) | |
178 | { | |
179 | struct charlcd_priv *priv = to_priv(lcd); | |
180 | ||
181 | priv->addr.x = 0; | |
182 | priv->addr.y = 0; | |
183 | charlcd_gotoxy(lcd); | |
184 | } | |
185 | ||
186 | static void charlcd_print(struct charlcd *lcd, char c) | |
187 | { | |
188 | struct charlcd_priv *priv = to_priv(lcd); | |
189 | ||
190 | if (priv->addr.x < lcd->bwidth) { | |
191 | if (lcd->char_conv) | |
192 | c = lcd->char_conv[(unsigned char)c]; | |
193 | lcd->ops->write_data(lcd, c); | |
194 | priv->addr.x++; | |
195 | } | |
196 | /* prevents the cursor from wrapping onto the next line */ | |
197 | if (priv->addr.x == lcd->bwidth) | |
198 | charlcd_gotoxy(lcd); | |
199 | } | |
200 | ||
201 | static void charlcd_clear_fast(struct charlcd *lcd) | |
202 | { | |
203 | int pos; | |
204 | ||
205 | charlcd_home(lcd); | |
206 | ||
207 | if (lcd->ops->clear_fast) | |
208 | lcd->ops->clear_fast(lcd); | |
209 | else | |
1d3b2af2 | 210 | for (pos = 0; pos < min(2, lcd->height) * lcd->hwidth; pos++) |
39f8ea46 GU |
211 | lcd->ops->write_data(lcd, ' '); |
212 | ||
213 | charlcd_home(lcd); | |
214 | } | |
215 | ||
216 | /* clears the display and resets X/Y */ | |
217 | static void charlcd_clear_display(struct charlcd *lcd) | |
218 | { | |
219 | struct charlcd_priv *priv = to_priv(lcd); | |
220 | ||
221 | lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CLEAR); | |
222 | priv->addr.x = 0; | |
223 | priv->addr.y = 0; | |
224 | /* we must wait a few milliseconds (15) */ | |
225 | long_sleep(15); | |
226 | } | |
227 | ||
228 | static int charlcd_init_display(struct charlcd *lcd) | |
229 | { | |
ac201479 | 230 | void (*write_cmd_raw)(struct charlcd *lcd, int cmd); |
39f8ea46 | 231 | struct charlcd_priv *priv = to_priv(lcd); |
ac201479 GU |
232 | u8 init; |
233 | ||
234 | if (lcd->ifwidth != 4 && lcd->ifwidth != 8) | |
235 | return -EINVAL; | |
39f8ea46 GU |
236 | |
237 | priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | | |
238 | LCD_FLAG_C | LCD_FLAG_B; | |
239 | ||
240 | long_sleep(20); /* wait 20 ms after power-up for the paranoid */ | |
241 | ||
ac201479 GU |
242 | /* |
243 | * 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure | |
244 | * the LCD is in 8-bit mode afterwards | |
245 | */ | |
246 | init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS; | |
247 | if (lcd->ifwidth == 4) { | |
248 | init >>= 4; | |
249 | write_cmd_raw = lcd->ops->write_cmd_raw4; | |
250 | } else { | |
251 | write_cmd_raw = lcd->ops->write_cmd; | |
252 | } | |
253 | write_cmd_raw(lcd, init); | |
39f8ea46 | 254 | long_sleep(10); |
ac201479 | 255 | write_cmd_raw(lcd, init); |
39f8ea46 | 256 | long_sleep(10); |
ac201479 | 257 | write_cmd_raw(lcd, init); |
39f8ea46 GU |
258 | long_sleep(10); |
259 | ||
ac201479 GU |
260 | if (lcd->ifwidth == 4) { |
261 | /* Switch to 4-bit mode, 1 line, small fonts */ | |
262 | lcd->ops->write_cmd_raw4(lcd, LCD_CMD_FUNCTION_SET >> 4); | |
263 | long_sleep(10); | |
264 | } | |
265 | ||
39f8ea46 GU |
266 | /* set font height and lines number */ |
267 | lcd->ops->write_cmd(lcd, | |
ac201479 GU |
268 | LCD_CMD_FUNCTION_SET | |
269 | ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | | |
39f8ea46 GU |
270 | ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | |
271 | ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); | |
272 | long_sleep(10); | |
273 | ||
274 | /* display off, cursor off, blink off */ | |
275 | lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CTRL); | |
276 | long_sleep(10); | |
277 | ||
278 | lcd->ops->write_cmd(lcd, | |
279 | LCD_CMD_DISPLAY_CTRL | /* set display mode */ | |
280 | ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | | |
281 | ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | | |
282 | ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); | |
283 | ||
284 | charlcd_backlight(lcd, (priv->flags & LCD_FLAG_L) ? 1 : 0); | |
285 | ||
286 | long_sleep(10); | |
287 | ||
288 | /* entry mode set : increment, cursor shifting */ | |
289 | lcd->ops->write_cmd(lcd, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC); | |
290 | ||
291 | charlcd_clear_display(lcd); | |
292 | return 0; | |
293 | } | |
294 | ||
295 | /* | |
296 | * These are the file operation function for user access to /dev/lcd | |
297 | * This function can also be called from inside the kernel, by | |
298 | * setting file and ppos to NULL. | |
299 | * | |
300 | */ | |
301 | ||
302 | static inline int handle_lcd_special_code(struct charlcd *lcd) | |
303 | { | |
304 | struct charlcd_priv *priv = to_priv(lcd); | |
305 | ||
306 | /* LCD special codes */ | |
307 | ||
308 | int processed = 0; | |
309 | ||
310 | char *esc = priv->esc_seq.buf + 2; | |
311 | int oldflags = priv->flags; | |
312 | ||
313 | /* check for display mode flags */ | |
314 | switch (*esc) { | |
315 | case 'D': /* Display ON */ | |
316 | priv->flags |= LCD_FLAG_D; | |
317 | processed = 1; | |
318 | break; | |
319 | case 'd': /* Display OFF */ | |
320 | priv->flags &= ~LCD_FLAG_D; | |
321 | processed = 1; | |
322 | break; | |
323 | case 'C': /* Cursor ON */ | |
324 | priv->flags |= LCD_FLAG_C; | |
325 | processed = 1; | |
326 | break; | |
327 | case 'c': /* Cursor OFF */ | |
328 | priv->flags &= ~LCD_FLAG_C; | |
329 | processed = 1; | |
330 | break; | |
331 | case 'B': /* Blink ON */ | |
332 | priv->flags |= LCD_FLAG_B; | |
333 | processed = 1; | |
334 | break; | |
335 | case 'b': /* Blink OFF */ | |
336 | priv->flags &= ~LCD_FLAG_B; | |
337 | processed = 1; | |
338 | break; | |
339 | case '+': /* Back light ON */ | |
340 | priv->flags |= LCD_FLAG_L; | |
341 | processed = 1; | |
342 | break; | |
343 | case '-': /* Back light OFF */ | |
344 | priv->flags &= ~LCD_FLAG_L; | |
345 | processed = 1; | |
346 | break; | |
347 | case '*': /* Flash back light */ | |
348 | charlcd_poke(lcd); | |
349 | processed = 1; | |
350 | break; | |
351 | case 'f': /* Small Font */ | |
352 | priv->flags &= ~LCD_FLAG_F; | |
353 | processed = 1; | |
354 | break; | |
355 | case 'F': /* Large Font */ | |
356 | priv->flags |= LCD_FLAG_F; | |
357 | processed = 1; | |
358 | break; | |
359 | case 'n': /* One Line */ | |
360 | priv->flags &= ~LCD_FLAG_N; | |
361 | processed = 1; | |
362 | break; | |
363 | case 'N': /* Two Lines */ | |
364 | priv->flags |= LCD_FLAG_N; | |
365 | break; | |
366 | case 'l': /* Shift Cursor Left */ | |
367 | if (priv->addr.x > 0) { | |
368 | /* back one char if not at end of line */ | |
369 | if (priv->addr.x < lcd->bwidth) | |
370 | lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); | |
371 | priv->addr.x--; | |
372 | } | |
373 | processed = 1; | |
374 | break; | |
375 | case 'r': /* shift cursor right */ | |
376 | if (priv->addr.x < lcd->width) { | |
377 | /* allow the cursor to pass the end of the line */ | |
378 | if (priv->addr.x < (lcd->bwidth - 1)) | |
379 | lcd->ops->write_cmd(lcd, | |
380 | LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT); | |
381 | priv->addr.x++; | |
382 | } | |
383 | processed = 1; | |
384 | break; | |
385 | case 'L': /* shift display left */ | |
386 | lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT); | |
387 | processed = 1; | |
388 | break; | |
389 | case 'R': /* shift display right */ | |
390 | lcd->ops->write_cmd(lcd, | |
391 | LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT | | |
392 | LCD_CMD_SHIFT_RIGHT); | |
393 | processed = 1; | |
394 | break; | |
395 | case 'k': { /* kill end of line */ | |
396 | int x; | |
397 | ||
398 | for (x = priv->addr.x; x < lcd->bwidth; x++) | |
399 | lcd->ops->write_data(lcd, ' '); | |
400 | ||
401 | /* restore cursor position */ | |
402 | charlcd_gotoxy(lcd); | |
403 | processed = 1; | |
404 | break; | |
405 | } | |
406 | case 'I': /* reinitialize display */ | |
407 | charlcd_init_display(lcd); | |
408 | processed = 1; | |
409 | break; | |
410 | case 'G': { | |
411 | /* Generator : LGcxxxxx...xx; must have <c> between '0' | |
412 | * and '7', representing the numerical ASCII code of the | |
413 | * redefined character, and <xx...xx> a sequence of 16 | |
414 | * hex digits representing 8 bytes for each character. | |
415 | * Most LCDs will only use 5 lower bits of the 7 first | |
416 | * bytes. | |
417 | */ | |
418 | ||
419 | unsigned char cgbytes[8]; | |
420 | unsigned char cgaddr; | |
421 | int cgoffset; | |
422 | int shift; | |
423 | char value; | |
424 | int addr; | |
425 | ||
426 | if (!strchr(esc, ';')) | |
427 | break; | |
428 | ||
429 | esc++; | |
430 | ||
431 | cgaddr = *(esc++) - '0'; | |
432 | if (cgaddr > 7) { | |
433 | processed = 1; | |
434 | break; | |
435 | } | |
436 | ||
437 | cgoffset = 0; | |
438 | shift = 0; | |
439 | value = 0; | |
440 | while (*esc && cgoffset < 8) { | |
441 | shift ^= 4; | |
442 | if (*esc >= '0' && *esc <= '9') { | |
443 | value |= (*esc - '0') << shift; | |
444 | } else if (*esc >= 'A' && *esc <= 'Z') { | |
445 | value |= (*esc - 'A' + 10) << shift; | |
446 | } else if (*esc >= 'a' && *esc <= 'z') { | |
447 | value |= (*esc - 'a' + 10) << shift; | |
448 | } else { | |
449 | esc++; | |
450 | continue; | |
451 | } | |
452 | ||
453 | if (shift == 0) { | |
454 | cgbytes[cgoffset++] = value; | |
455 | value = 0; | |
456 | } | |
457 | ||
458 | esc++; | |
459 | } | |
460 | ||
461 | lcd->ops->write_cmd(lcd, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8)); | |
462 | for (addr = 0; addr < cgoffset; addr++) | |
463 | lcd->ops->write_data(lcd, cgbytes[addr]); | |
464 | ||
465 | /* ensures that we stop writing to CGRAM */ | |
466 | charlcd_gotoxy(lcd); | |
467 | processed = 1; | |
468 | break; | |
469 | } | |
470 | case 'x': /* gotoxy : LxXXX[yYYY]; */ | |
471 | case 'y': /* gotoxy : LyYYY[xXXX]; */ | |
472 | if (!strchr(esc, ';')) | |
473 | break; | |
474 | ||
475 | while (*esc) { | |
476 | if (*esc == 'x') { | |
477 | esc++; | |
478 | if (kstrtoul(esc, 10, &priv->addr.x) < 0) | |
479 | break; | |
480 | } else if (*esc == 'y') { | |
481 | esc++; | |
482 | if (kstrtoul(esc, 10, &priv->addr.y) < 0) | |
483 | break; | |
484 | } else { | |
485 | break; | |
486 | } | |
487 | } | |
488 | ||
489 | charlcd_gotoxy(lcd); | |
490 | processed = 1; | |
491 | break; | |
492 | } | |
493 | ||
494 | /* TODO: This indent party here got ugly, clean it! */ | |
495 | /* Check whether one flag was changed */ | |
496 | if (oldflags == priv->flags) | |
497 | return processed; | |
498 | ||
499 | /* check whether one of B,C,D flags were changed */ | |
500 | if ((oldflags ^ priv->flags) & | |
501 | (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D)) | |
502 | /* set display mode */ | |
503 | lcd->ops->write_cmd(lcd, | |
504 | LCD_CMD_DISPLAY_CTRL | | |
505 | ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | | |
506 | ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | | |
507 | ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); | |
508 | /* check whether one of F,N flags was changed */ | |
509 | else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N)) | |
510 | lcd->ops->write_cmd(lcd, | |
ac201479 GU |
511 | LCD_CMD_FUNCTION_SET | |
512 | ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | | |
39f8ea46 GU |
513 | ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | |
514 | ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); | |
515 | /* check whether L flag was changed */ | |
516 | else if ((oldflags ^ priv->flags) & LCD_FLAG_L) | |
517 | charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L)); | |
518 | ||
519 | return processed; | |
520 | } | |
521 | ||
522 | static void charlcd_write_char(struct charlcd *lcd, char c) | |
523 | { | |
524 | struct charlcd_priv *priv = to_priv(lcd); | |
525 | ||
526 | /* first, we'll test if we're in escape mode */ | |
527 | if ((c != '\n') && priv->esc_seq.len >= 0) { | |
528 | /* yes, let's add this char to the buffer */ | |
529 | priv->esc_seq.buf[priv->esc_seq.len++] = c; | |
530 | priv->esc_seq.buf[priv->esc_seq.len] = 0; | |
531 | } else { | |
532 | /* aborts any previous escape sequence */ | |
533 | priv->esc_seq.len = -1; | |
534 | ||
535 | switch (c) { | |
536 | case LCD_ESCAPE_CHAR: | |
537 | /* start of an escape sequence */ | |
538 | priv->esc_seq.len = 0; | |
539 | priv->esc_seq.buf[priv->esc_seq.len] = 0; | |
540 | break; | |
541 | case '\b': | |
542 | /* go back one char and clear it */ | |
543 | if (priv->addr.x > 0) { | |
544 | /* | |
545 | * check if we're not at the | |
546 | * end of the line | |
547 | */ | |
548 | if (priv->addr.x < lcd->bwidth) | |
549 | /* back one char */ | |
550 | lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); | |
551 | priv->addr.x--; | |
552 | } | |
553 | /* replace with a space */ | |
554 | lcd->ops->write_data(lcd, ' '); | |
555 | /* back one char again */ | |
556 | lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); | |
557 | break; | |
558 | case '\014': | |
559 | /* quickly clear the display */ | |
560 | charlcd_clear_fast(lcd); | |
561 | break; | |
562 | case '\n': | |
563 | /* | |
564 | * flush the remainder of the current line and | |
565 | * go to the beginning of the next line | |
566 | */ | |
567 | for (; priv->addr.x < lcd->bwidth; priv->addr.x++) | |
568 | lcd->ops->write_data(lcd, ' '); | |
569 | priv->addr.x = 0; | |
570 | priv->addr.y = (priv->addr.y + 1) % lcd->height; | |
571 | charlcd_gotoxy(lcd); | |
572 | break; | |
573 | case '\r': | |
574 | /* go to the beginning of the same line */ | |
575 | priv->addr.x = 0; | |
576 | charlcd_gotoxy(lcd); | |
577 | break; | |
578 | case '\t': | |
579 | /* print a space instead of the tab */ | |
580 | charlcd_print(lcd, ' '); | |
581 | break; | |
582 | default: | |
583 | /* simply print this char */ | |
584 | charlcd_print(lcd, c); | |
585 | break; | |
586 | } | |
587 | } | |
588 | ||
589 | /* | |
590 | * now we'll see if we're in an escape mode and if the current | |
591 | * escape sequence can be understood. | |
592 | */ | |
593 | if (priv->esc_seq.len >= 2) { | |
594 | int processed = 0; | |
595 | ||
596 | if (!strcmp(priv->esc_seq.buf, "[2J")) { | |
597 | /* clear the display */ | |
598 | charlcd_clear_fast(lcd); | |
599 | processed = 1; | |
600 | } else if (!strcmp(priv->esc_seq.buf, "[H")) { | |
601 | /* cursor to home */ | |
602 | charlcd_home(lcd); | |
603 | processed = 1; | |
604 | } | |
605 | /* codes starting with ^[[L */ | |
606 | else if ((priv->esc_seq.len >= 3) && | |
607 | (priv->esc_seq.buf[0] == '[') && | |
608 | (priv->esc_seq.buf[1] == 'L')) { | |
609 | processed = handle_lcd_special_code(lcd); | |
610 | } | |
611 | ||
612 | /* LCD special escape codes */ | |
613 | /* | |
614 | * flush the escape sequence if it's been processed | |
615 | * or if it is getting too long. | |
616 | */ | |
617 | if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN)) | |
618 | priv->esc_seq.len = -1; | |
619 | } /* escape codes */ | |
620 | } | |
621 | ||
622 | static struct charlcd *the_charlcd; | |
623 | ||
624 | static ssize_t charlcd_write(struct file *file, const char __user *buf, | |
625 | size_t count, loff_t *ppos) | |
626 | { | |
627 | const char __user *tmp = buf; | |
628 | char c; | |
629 | ||
630 | for (; count-- > 0; (*ppos)++, tmp++) { | |
631 | if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) | |
632 | /* | |
633 | * let's be a little nice with other processes | |
634 | * that need some CPU | |
635 | */ | |
636 | schedule(); | |
637 | ||
638 | if (get_user(c, tmp)) | |
639 | return -EFAULT; | |
640 | ||
641 | charlcd_write_char(the_charlcd, c); | |
642 | } | |
643 | ||
644 | return tmp - buf; | |
645 | } | |
646 | ||
647 | static int charlcd_open(struct inode *inode, struct file *file) | |
648 | { | |
649 | struct charlcd_priv *priv = to_priv(the_charlcd); | |
93dc1774 | 650 | int ret; |
39f8ea46 | 651 | |
93dc1774 | 652 | ret = -EBUSY; |
39f8ea46 | 653 | if (!atomic_dec_and_test(&charlcd_available)) |
93dc1774 | 654 | goto fail; /* open only once at a time */ |
39f8ea46 | 655 | |
93dc1774 | 656 | ret = -EPERM; |
39f8ea46 | 657 | if (file->f_mode & FMODE_READ) /* device is write-only */ |
93dc1774 | 658 | goto fail; |
39f8ea46 GU |
659 | |
660 | if (priv->must_clear) { | |
661 | charlcd_clear_display(&priv->lcd); | |
662 | priv->must_clear = false; | |
663 | } | |
664 | return nonseekable_open(inode, file); | |
93dc1774 WT |
665 | |
666 | fail: | |
667 | atomic_inc(&charlcd_available); | |
668 | return ret; | |
39f8ea46 GU |
669 | } |
670 | ||
671 | static int charlcd_release(struct inode *inode, struct file *file) | |
672 | { | |
673 | atomic_inc(&charlcd_available); | |
674 | return 0; | |
675 | } | |
676 | ||
677 | static const struct file_operations charlcd_fops = { | |
678 | .write = charlcd_write, | |
679 | .open = charlcd_open, | |
680 | .release = charlcd_release, | |
681 | .llseek = no_llseek, | |
682 | }; | |
683 | ||
684 | static struct miscdevice charlcd_dev = { | |
685 | .minor = LCD_MINOR, | |
686 | .name = "lcd", | |
687 | .fops = &charlcd_fops, | |
688 | }; | |
689 | ||
690 | static void charlcd_puts(struct charlcd *lcd, const char *s) | |
691 | { | |
692 | const char *tmp = s; | |
693 | int count = strlen(s); | |
694 | ||
695 | for (; count-- > 0; tmp++) { | |
696 | if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) | |
697 | /* | |
698 | * let's be a little nice with other processes | |
699 | * that need some CPU | |
700 | */ | |
701 | schedule(); | |
702 | ||
703 | charlcd_write_char(lcd, *tmp); | |
704 | } | |
705 | } | |
706 | ||
707 | /* initialize the LCD driver */ | |
708 | static int charlcd_init(struct charlcd *lcd) | |
709 | { | |
710 | struct charlcd_priv *priv = to_priv(lcd); | |
711 | int ret; | |
712 | ||
713 | if (lcd->ops->backlight) { | |
714 | mutex_init(&priv->bl_tempo_lock); | |
715 | INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off); | |
716 | } | |
717 | ||
718 | /* | |
719 | * before this line, we must NOT send anything to the display. | |
720 | * Since charlcd_init_display() needs to write data, we have to | |
721 | * enable mark the LCD initialized just before. | |
722 | */ | |
723 | ret = charlcd_init_display(lcd); | |
724 | if (ret) | |
725 | return ret; | |
726 | ||
727 | /* display a short message */ | |
728 | #ifdef CONFIG_PANEL_CHANGE_MESSAGE | |
729 | #ifdef CONFIG_PANEL_BOOT_MESSAGE | |
730 | charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE); | |
731 | #endif | |
732 | #else | |
733 | charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE "\n"); | |
734 | #endif | |
735 | /* clear the display on the next device opening */ | |
736 | priv->must_clear = true; | |
737 | charlcd_home(lcd); | |
738 | return 0; | |
739 | } | |
740 | ||
741 | struct charlcd *charlcd_alloc(unsigned int drvdata_size) | |
742 | { | |
743 | struct charlcd_priv *priv; | |
744 | struct charlcd *lcd; | |
745 | ||
746 | priv = kzalloc(sizeof(*priv) + drvdata_size, GFP_KERNEL); | |
747 | if (!priv) | |
748 | return NULL; | |
749 | ||
750 | priv->esc_seq.len = -1; | |
751 | ||
752 | lcd = &priv->lcd; | |
ac201479 | 753 | lcd->ifwidth = 8; |
39f8ea46 GU |
754 | lcd->bwidth = DEFAULT_LCD_BWIDTH; |
755 | lcd->hwidth = DEFAULT_LCD_HWIDTH; | |
756 | lcd->drvdata = priv->drvdata; | |
757 | ||
758 | return lcd; | |
759 | } | |
760 | EXPORT_SYMBOL_GPL(charlcd_alloc); | |
761 | ||
762 | static int panel_notify_sys(struct notifier_block *this, unsigned long code, | |
763 | void *unused) | |
764 | { | |
765 | struct charlcd *lcd = the_charlcd; | |
766 | ||
767 | switch (code) { | |
768 | case SYS_DOWN: | |
769 | charlcd_puts(lcd, | |
770 | "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); | |
771 | break; | |
772 | case SYS_HALT: | |
773 | charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); | |
774 | break; | |
775 | case SYS_POWER_OFF: | |
776 | charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); | |
777 | break; | |
778 | default: | |
779 | break; | |
780 | } | |
781 | return NOTIFY_DONE; | |
782 | } | |
783 | ||
784 | static struct notifier_block panel_notifier = { | |
785 | panel_notify_sys, | |
786 | NULL, | |
787 | 0 | |
788 | }; | |
789 | ||
790 | int charlcd_register(struct charlcd *lcd) | |
791 | { | |
792 | int ret; | |
793 | ||
794 | ret = charlcd_init(lcd); | |
795 | if (ret) | |
796 | return ret; | |
797 | ||
798 | ret = misc_register(&charlcd_dev); | |
799 | if (ret) | |
800 | return ret; | |
801 | ||
802 | the_charlcd = lcd; | |
803 | register_reboot_notifier(&panel_notifier); | |
804 | return 0; | |
805 | } | |
806 | EXPORT_SYMBOL_GPL(charlcd_register); | |
807 | ||
808 | int charlcd_unregister(struct charlcd *lcd) | |
809 | { | |
810 | struct charlcd_priv *priv = to_priv(lcd); | |
811 | ||
812 | unregister_reboot_notifier(&panel_notifier); | |
813 | charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-"); | |
814 | misc_deregister(&charlcd_dev); | |
815 | the_charlcd = NULL; | |
816 | if (lcd->ops->backlight) { | |
817 | cancel_delayed_work_sync(&priv->bl_work); | |
818 | priv->lcd.ops->backlight(&priv->lcd, 0); | |
819 | } | |
820 | ||
821 | return 0; | |
822 | } | |
823 | EXPORT_SYMBOL_GPL(charlcd_unregister); | |
824 | ||
825 | MODULE_LICENSE("GPL"); |